1.5.3 progress. Added multi-confirmation and code refactorings

NebulaAuth:
- Added MAAC and PortableMaClient for multi-confirmations
- Code clean-ups, removed unused classes, refactoring of old files
- SteamLib updated
- Localization and UI updates
- Dependcies updated to the last version

LegacyConverter:
 - Added mode to decrypt mafiles
This commit is contained in:
Давид Чернопятов
2024-10-16 16:31:53 +03:00
parent 99be9d64c6
commit 1e65cd4a06
105 changed files with 1827 additions and 1140 deletions
@@ -0,0 +1,48 @@
using Newtonsoft.Json;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
//Pragma disable for legacy code
namespace NebulaAuth.LegacyConverter;
public class Manifest
{
[JsonProperty("encrypted")]
public bool Encrypted { get; set; }
[JsonProperty("first_run")]
public bool FirstRun { get; set; }
[JsonProperty("entries")]
public Entry[] Entries { get; set; }
[JsonProperty("periodic_checking")]
public bool PeriodicChecking { get; set; }
[JsonProperty("periodic_checking_interval")]
public long PeriodicCheckingInterval { get; set; }
[JsonProperty("periodic_checking_checkall")]
public bool PeriodicCheckingCheckAll { get; set; }
[JsonProperty("auto_confirm_market_transactions")]
public bool AutoConfirmMarketTransactions { get; set; }
[JsonProperty("auto_confirm_trades")]
public bool AutoConfirmTrades { get; set; }
}
public class Entry
{
[JsonProperty("encryption_iv")]
public string EncryptionIv { get; set; }
[JsonProperty("encryption_salt")]
public string EncryptionSalt { get; set; }
[JsonProperty("filename")]
public string Filename { get; set; }
[JsonProperty("steamid")]
public ulong SteamId { get; set; }
}
+158 -37
View File
@@ -1,51 +1,172 @@
using Newtonsoft.Json;
using AchiesUtilities.Extensions;
using NebulaAuth.LegacyConverter;
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)
try
{
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (currentPath != null)
Environment.CurrentDirectory = currentPath;
const string TO_STORE_FOLDER = "ConvertedMafiles";
if (Path.Exists(path) == false)
if (Directory.Exists(TO_STORE_FOLDER) == false)
{
Console.WriteLine($"NOT VALID PATH: '{path}'");
continue;
Directory.CreateDirectory(TO_STORE_FOLDER);
}
Console.WriteLine("Reading: " + path);
try
else
{
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);
var isEmpty = Directory.GetFiles(TO_STORE_FOLDER).Length == 0;
Console.ForegroundColor = ConsoleColor.Yellow;
if (!isEmpty)
{
Console.WriteLine(
"WARNING! 'ConverterdMafiles' folder is not empty. Please backup data from it and then continue.");
Console.ResetColor();
Console.WriteLine("Press Y to continue");
while (Console.ReadKey(true).Key != ConsoleKey.Y)
{
}
}
}
var decryptMode = false;
while (true)
{
Console.WriteLine("Press 'D' to select decrypt mode. Press 'C' to convert mode. Press ESC to exit");
var key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.D:
decryptMode = true;
break;
case ConsoleKey.C:
break;
case ConsoleKey.Escape:
return;
default:
continue;
}
Console.WriteLine("DONE: " + name);
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex);
}
finally
{
Console.WriteLine("-----------------------------------------");
break;
}
Manifest? manifest = null;
string? password = null;
if (decryptMode)
{
var files = Directory.GetFiles(currentPath, "manifest.json");
var manifestPath = files.FirstOrDefault();
if (manifestPath == null)
{
Console.WriteLine("No manifest.json found in current directory");
return;
}
var manifestText = File.ReadAllText(manifestPath);
try
{
manifest = JsonConvert.DeserializeObject<Manifest>(manifestText)!;
}
catch (Exception ex)
{
Console.WriteLine("Can't read manifest: " + ex);
return;
}
if (manifest.Encrypted == false)
{
Console.WriteLine("Manifest is not encrypted");
return;
}
while (true)
{
Console.WriteLine("Please enter your encryption password: ");
password = Console.ReadLine();
if (string.IsNullOrWhiteSpace(password))
continue;
break;
}
}
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);
if (decryptMode)
{
var fileName = Path.GetFileName(path);
text = DecryptMafile(fileName, text);
if (text == null)
{
Console.WriteLine(path + " not found in manifest. Skipped");
continue;
}
}
var maf = MafileSerializer.Deserialize(text, out _);
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
Write(legacy, fileNameWithoutExtension);
Console.WriteLine("DONE: " + fileNameWithoutExtension);
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex);
}
finally
{
Console.WriteLine("-----------------------------------------");
}
}
//Local Functions
void Write(string maf, string name)
{
var path = Path.Combine(TO_STORE_FOLDER, name + "_legacy.mafile");
File.WriteAllText(path, maf);
}
string? DecryptMafile(string fileName, string cipherText)
{
if (password == null) return null;
var entry = manifest?.Entries.FirstOrDefault(x => x.Filename.EqualsIgnoreCase(fileName));
if (entry == null)
{
return null;
}
var iv = entry.EncryptionIv;
var salt = entry.EncryptionSalt;
return SDAEncryptor.DecryptData(password, salt, iv, cipherText);
}
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
void Write(string maf, string name)
finally
{
var path = Path.Combine(name + "_legacy.mafile");
File.WriteAllText(path, maf);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
+203
View File
@@ -0,0 +1,203 @@
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
namespace NebulaAuth.LegacyConverter;
using System;
using System.IO;
using System.Security.Cryptography;
#pragma warning disable all
#pragma warning disable SYSLIB0023
#pragma warning disable CS8603 // Possible null reference return.
#pragma warning disable SYSLIB0022
#pragma warning disable SYSLIB0041
//Resharper disable all
//Pragma is disabled because it's legacy code from SDA
/// <summary>
/// This class provides the controls that will encrypt and decrypt the *.maFile files
///
/// Passwords entered will be passed into 100k rounds of PBKDF2 (RFC2898) with a cryptographically random salt.
/// The generated key will then be passed into AES-256 (RijndalManaged) which will encrypt the data
/// in cypher block chaining (CBC) mode, and then write both the PBKDF2 salt and encrypted data onto the disk.
/// </summary>
public static class SDAEncryptor
{
private const int PBKDF2_ITERATIONS = 50000; //Set to 50k to make program not unbearably slow. May increase in future.
private const int SALT_LENGTH = 8;
private const int KEY_SIZE_BYTES = 32;
private const int IV_LENGTH = 16;
/// <summary>
/// Returns an 8-byte cryptographically random salt in base64 encoding
/// </summary>
/// <returns></returns>
public static string GetRandomSalt()
{
byte[] salt = new byte[SALT_LENGTH];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(salt);
}
return Convert.ToBase64String(salt);
}
/// <summary>
/// Returns a 16-byte cryptographically random initialization vector (IV) in base64 encoding
/// </summary>
/// <returns></returns>
public static string GetInitializationVector()
{
byte[] IV = new byte[IV_LENGTH];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(IV);
}
return Convert.ToBase64String(IV);
}
/// <summary>
/// Generates an encryption key derived using a password, a random salt, and specified number of rounds of PBKDF2
///
/// TODO: pass in password via SecureString?
/// </summary>
/// <param name="password"></param>
/// <param name="salt"></param>
/// <returns></returns>
private static byte[] GetEncryptionKey(string password, string salt)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Password is empty");
}
if (string.IsNullOrEmpty(salt))
{
throw new ArgumentException("Salt is empty");
}
using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(salt), PBKDF2_ITERATIONS))
{
return pbkdf2.GetBytes(KEY_SIZE_BYTES);
}
}
/// <summary>
/// Tries to decrypt and return data given an encrypted base64 encoded string. Must use the same
/// password, salt, IV, and ciphertext that was used during the original encryption of the data.
/// </summary>
/// <param name="password"></param>
/// <param name="passwordSalt"></param>
/// <param name="IV">Initialization Vector</param>
/// <param name="encryptedData"></param>
/// <returns></returns>
public static string DecryptData(string password, string passwordSalt, string IV, string encryptedData)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Password is empty");
}
if (string.IsNullOrEmpty(passwordSalt))
{
throw new ArgumentException("Salt is empty");
}
if (string.IsNullOrEmpty(IV))
{
throw new ArgumentException("Initialization Vector is empty");
}
if (string.IsNullOrEmpty(encryptedData))
{
throw new ArgumentException("Encrypted data is empty");
}
byte[] cipherText = Convert.FromBase64String(encryptedData);
byte[] key = GetEncryptionKey(password, passwordSalt);
string plaintext = null;
using (RijndaelManaged aes256 = new RijndaelManaged())
{
aes256.IV = Convert.FromBase64String(IV);
aes256.Key = key;
aes256.Padding = PaddingMode.PKCS7;
aes256.Mode = CipherMode.CBC;
//create decryptor to perform the stream transform
ICryptoTransform decryptor = aes256.CreateDecryptor(aes256.Key, aes256.IV);
//wrap in a try since a bad password yields a bad key, which would throw an exception on decrypt
try
{
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
catch (CryptographicException)
{
plaintext = null;
}
}
return plaintext;
}
/// <summary>
/// Encrypts a string given a password, salt, and initialization vector, then returns result in base64 encoded string.
///
/// To retrieve this data, you must decrypt with the same password, salt, IV, and cyphertext that was used during encryption
/// </summary>
/// <param name="password"></param>
/// <param name="passwordSalt"></param>
/// <param name="IV"></param>
/// <param name="plaintext"></param>
/// <returns></returns>
public static string EncryptData(string password, string passwordSalt, string IV, string plaintext)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Password is empty");
}
if (string.IsNullOrEmpty(passwordSalt))
{
throw new ArgumentException("Salt is empty");
}
if (string.IsNullOrEmpty(IV))
{
throw new ArgumentException("Initialization Vector is empty");
}
if (string.IsNullOrEmpty(plaintext))
{
throw new ArgumentException("Plaintext data is empty");
}
byte[] key = GetEncryptionKey(password, passwordSalt);
byte[] ciphertext;
using (RijndaelManaged aes256 = new RijndaelManaged())
{
aes256.Key = key;
aes256.IV = Convert.FromBase64String(IV);
aes256.Padding = PaddingMode.PKCS7;
aes256.Mode = CipherMode.CBC;
ICryptoTransform encryptor = aes256.CreateEncryptor(aes256.Key, aes256.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncypt = new StreamWriter(csEncrypt))
{
swEncypt.Write(plaintext);
}
ciphertext = msEncrypt.ToArray();
}
}
}
return Convert.ToBase64String(ciphertext);
}
}
+2 -1
View File
@@ -1,2 +1,3 @@
<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>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MAAC/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+1 -4
View File
@@ -1,9 +1,7 @@
<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"
@@ -15,13 +13,13 @@
<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:ProxyDataTextConverter x:Key="ProxyDataTextConverter"/>
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter"/>
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter"/>
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter"/>
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter"/>
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter"/>
@@ -34,7 +32,6 @@
<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" />
+1 -1
View File
@@ -8,7 +8,7 @@ using CodingSeb.Localization;
namespace NebulaAuth;
public partial class App : Application
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
@@ -7,10 +7,10 @@ namespace NebulaAuth.Converters;
public class AnyMafilesToVisibilityConverter : IValueConverter
{
private static bool EverAnyMafiles;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
private static bool _everAnyMafiles;
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (EverAnyMafiles)
if (_everAnyMafiles)
{
return Visibility.Collapsed;
}
@@ -19,11 +19,11 @@ public class AnyMafilesToVisibilityConverter : IValueConverter
return Visibility.Visible;
}
EverAnyMafiles = true;
_everAnyMafiles = true;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
@@ -8,13 +8,13 @@ namespace NebulaAuth.Converters.Background;
public class BackgroundImageVisibleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
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)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
@@ -9,7 +9,7 @@ namespace NebulaAuth.Converters.Background;
public class BackgroundSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is BackgroundMode.Custom)
{
@@ -21,7 +21,7 @@ public class BackgroundSourceConverter : IValueConverter
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg"));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
@@ -7,13 +7,16 @@ namespace NebulaAuth.Converters;
[ValueConversion(typeof(double), typeof(double))]
public class CoefficientConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
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)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return (double)value / (double)parameter;
var first = value as double? ?? 0;
var second = parameter as double? ?? 0;
if (second == 0) second = 1;
return first / second;
}
}
@@ -1,23 +0,0 @@
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.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using System;
namespace NebulaAuth.Converters;
public class PortableMaClientStatusToColorConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is false)
{
return new SolidColorBrush(Color.FromRgb(187, 224, 139));
}
return new SolidColorBrush(Color.FromRgb(224, 139, 139));
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
+6 -6
View File
@@ -8,7 +8,7 @@ namespace NebulaAuth.Converters;
public class ProxyTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not MaProxy p)
{
@@ -18,15 +18,15 @@ public class ProxyTextConverter : IValueConverter
return $"{p.Id}: {p.Data.Address}:{p.Data.Port}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
public class ProxyDataTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not ProxyData p)
{
@@ -36,8 +36,8 @@ public class ProxyDataTextConverter : IValueConverter
return $"{p.Address}:{p.Port}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
@@ -6,13 +6,23 @@ namespace NebulaAuth.Converters;
public class ReverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return !(bool)value;
if (value is bool boolValue)
{
return !boolValue;
}
throw new ArgumentException("Value must be of type bool", nameof(value));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return !(bool)value;
if (value is bool boolValue)
{
return !boolValue;
}
throw new ArgumentException("Value must be of type bool", nameof(value));
}
}
}
+2 -2
View File
@@ -9,12 +9,12 @@ public class ValueConverterGroup : List<IValueConverter>, IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
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)
public object ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
+2
View File
@@ -24,7 +24,9 @@ public class SnackbarController
///
/// </summary>
/// <param name="text"></param>
/// <param name="action"></param>
/// <param name="duration">Default duration is 1 second</param>
/// <param name="actionText">Default: 'OK'</param>
public static void SendSnackbarWithButton(string text, string actionText = "OK", Action? action = null, TimeSpan? duration = null)
{
duration ??= GetSnackbarTime(text);
+14 -8
View File
@@ -1,28 +1,30 @@
using System;
using NebulaAuth.Model;
using System;
using System.Diagnostics.CodeAnalysis;
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 NotifyIcon? _notifyIcon;
private static readonly Window MainWindow = Application.Current.MainWindow!;
public static bool IsEnabled => Settings.Instance.HideToTray;
private static bool IsEnabled => Settings.Instance.HideToTray;
[MemberNotNullWhen(true, nameof(_notifyIcon))]
private static bool Init { get; set; }
public static void InitializeTray()
{
_notifyIcon = new NotifyIcon();
_notifyIcon.Text = "NebulaAuth";
Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
var iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
_notifyIcon.Icon = new Icon(iconStream);
_notifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick;
var contextMenu = new ContextMenuStrip();
@@ -32,6 +34,7 @@ public static class TrayManager
_notifyIcon.ContextMenuStrip = contextMenu;
MainWindow.StateChanged += MainWindow_StateChanged;
Init = true;
}
private static void OnExitClick(object? sender, EventArgs e)
@@ -57,6 +60,7 @@ public static class TrayManager
private static void ShowMainWindow()
{
if (!Init) return;
MainWindow.Show();
MainWindow.WindowState = WindowState.Normal;
_notifyIcon.Visible = false;
@@ -64,13 +68,15 @@ public static class TrayManager
private static void HideMainWindow()
{
if(IsEnabled == false) return;
if (!Init) return;
if (IsEnabled == false) return;
_notifyIcon.Visible = true;
MainWindow.Hide();
}
private static void ExitApplication()
{
if (!Init) return;
_notifyIcon.Dispose();
Application.Current.Shutdown();
}
+70 -30
View File
@@ -25,7 +25,7 @@
</b:Interaction.Behaviors>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=CopyMafileFromBufferCommand}"
<KeyBinding Command="{Binding Path=PasteMafilesFromClipboardCommand}"
Key="V"
Modifiers="Control"/>
</Window.InputBindings>
@@ -44,27 +44,27 @@
<TextBlock FontWeight="Bold" Foreground="#9a65b8" Text="{Tr MainWindow.Global.DragNDropHint}"/>
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36" Kind="FileReplaceOutline" />
</StackPanel>
<ToolBarTray IsLocked="True" Grid.Row="0">
<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>
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.File.Import}" Command="{Binding AddMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}" 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>
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<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 IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
</MenuItem>
</Menu>
<Separator />
@@ -85,7 +85,15 @@
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}" CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
</UIElement.InputBindings>
</ComboBox>
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}" 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}">
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}"
MinWidth="80"
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}" />
@@ -118,10 +126,17 @@
</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="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" ></md:PackIcon>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" IsChecked="{Binding TradeTimerEnabled}" Content="{Tr MainWindow.AppBar.TradeTimerHint}"/>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" 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" />
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding MarketTimerEnabled}" >
<md:PackIcon Kind="ShoppingCart"/>
</ToggleButton>
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding TradeTimerEnabled}" >
<md:PackIcon Kind="AccountArrowRight" />
</ToggleButton>
<TextBox 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" />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}" HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}" Foreground="WhiteSmoke" VerticalAlignment="Center" Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
<md:PackIcon Kind="Accounts" />
</ToggleButton>
</ToolBar>
</ToolBarTray>
<Grid Row="1">
@@ -134,10 +149,10 @@
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="10,15,10,15" DisplayMemberPath="AccountName" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Grid.Row="0" Margin="10,15,0,0" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}" Command="{Binding CopyLoginCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}"></MenuItem>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}" Command="{Binding CopyLoginCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}" ItemsSource="{Binding Groups}">
<ItemsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignMenuItem}" TargetType="{x:Type MenuItem}">
@@ -154,19 +169,46 @@
</ItemsControl.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}" Command="{Binding Path=RemoveGroupCommand}" CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromMAAC}" Command="{Binding Path=RemoveFromMAACCommand}" CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch" Text="{Binding AccountName}">
<TextBlock.Foreground>
<Binding Mode="OneWay" Path="LinkedClient.IsError" Converter="{StaticResource PortableMaClientStatusToColorConverter}">
<Binding.FallbackValue>
<SolidColorBrush Color="WhiteSmoke"/>
</Binding.FallbackValue>
</Binding>
</TextBlock.Foreground>
</TextBlock>
<StackPanel Visibility="{Binding LinkedClient, Converter={StaticResource NullableToVisibilityConverter}}" Grid.Column="1" Orientation="Horizontal">
<md:PackIcon Kind="ShoppingCart" Visibility="{Binding LinkedClient.AutoConfirmMarket, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"/>
<md:PackIcon Kind="AccountArrowRight" Visibility="{Binding LinkedClient.AutoConfirmTrades, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border Grid.Row="0" Margin="10" Padding="5" Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}, Mode=OneWay}">
<Border.Background>
<SolidColorBrush Color="DarkGray" Opacity="0.5"/>
</Border.Background>
<TextBlock TextWrapping="WrapWithOverflow" FontSize="16" Text="{Tr MainWindow.Global.StartTip}">
</TextBlock>
<TextBlock TextWrapping="WrapWithOverflow" FontSize="16" Text="{Tr MainWindow.Global.StartTip}"/>
</Border>
<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}" />
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}" md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10,5,10,10" md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<md:Card Grid.Column="1" Margin="10,10,15,10" UniformCornerRadius="15">
<Control.Background>
@@ -184,7 +226,11 @@
<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" />
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True" IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF" Style="{StaticResource MaterialDesignFilledTextBox}" Text="{Binding Code, FallbackValue=Code}">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
</TextBox.InputBindings>
</TextBox>
<Grid Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
@@ -193,16 +239,10 @@
<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}}">
<Button IsEnabled="{Binding IsMafileSelected}" 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>
<ItemsControl 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}"/>
<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>
@@ -220,7 +260,7 @@
<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://github.com/achiez" Foreground="{DynamicResource PrimaryHueMidBrush}" RequestNavigate="Hyperlink_OnRequestNavigate">by achies</Hyperlink>
<Hyperlink NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies" Foreground="{DynamicResource PrimaryHueMidBrush}" RequestNavigate="Hyperlink_OnRequestNavigate">by achies</Hyperlink>
</TextBlock>
</Grid>
</Border>
+4 -36
View File
@@ -9,7 +9,6 @@ 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;
@@ -18,17 +17,15 @@ namespace NebulaAuth;
public partial class MainWindow
{
private static string CodeCopiedString => LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied");
public MainWindow()
{
InitializeComponent();
base.DataContext = new MainVM();
DataContext = new MainVM();
Application.Current.MainWindow = this;
TrayManager.InitializeTray();
ThemeManager.InitializeTheme();
base.Title = base.Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
this.Loaded += OnApplicationStarted;
Title = Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
Loaded += OnApplicationStarted;
}
private async void OnApplicationStarted(object? sender, EventArgs e)
@@ -51,35 +48,6 @@ public partial class MainWindow
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
@@ -106,7 +74,7 @@ public partial class MainWindow
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)
if (DataContext is MainVM mainVm)
{
await mainVm.AddMafile(filePaths);
}
@@ -1,31 +0,0 @@
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);
}
}
@@ -6,8 +6,8 @@ public class LoginConfirmationResult
{
[MemberNotNullWhen(false, nameof(Error))]
public bool Success { get; set; }
public string IP { get; set; }
public string Country { get; set; }
public string IP { get; set; } = null!;
public string Country { get; set; } = null!;
public LoginConfirmationError? Error { get; set; }
+25 -4
View File
@@ -1,13 +1,36 @@
using SteamLib;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Model.MAAC;
using Newtonsoft.Json;
using SteamLib;
using SteamLib.Account;
using System;
namespace NebulaAuth.Model.Entities;
public class Mafile : MobileDataExtended
[INotifyPropertyChanged]
public partial class Mafile : MobileDataExtended
{
public MaProxy? Proxy { get; set; }
public string? Group { get; set; }
public string? Password { get; set; }
[JsonIgnore]
public PortableMaClient? LinkedClient
{
get => _linkedClient;
set => SetProperty(ref _linkedClient, value);
}
[JsonIgnore]
private PortableMaClient? _linkedClient;
public void SetSessionData(MobileSessionData? sessionData)
{
SessionData = sessionData;
OnPropertyChanged(nameof(SessionData));
}
public static Mafile FromMobileDataExtended(MobileDataExtended data)
{
return new Mafile
@@ -25,8 +48,6 @@ public class Mafile : MobileDataExtended
Uri = data.Uri,
};
}
public static Mafile FromMobileDataExtended(MobileDataExtended data, MaProxy? proxy, string? group, string? password)
{
var result = FromMobileDataExtended(data);
@@ -1,5 +1,4 @@
using System;
using System.Runtime.Serialization;
namespace NebulaAuth.Model.Exceptions;
@@ -17,10 +16,4 @@ public class CantAlignTimeException : Exception
public CantAlignTimeException(string message, Exception inner) : base(message, inner)
{
}
protected CantAlignTimeException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
@@ -0,0 +1,128 @@
using AchiesUtilities.Extensions;
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NebulaAuth.Model.MAAC;
public static class MultiAccountAutoConfirmer
{
public static ObservableCollection<Mafile> Clients { get; }
private static Timer Timer { get; }
private static readonly ReaderWriterLockSlim Lock = new();
private const string LOC_PATH = "MAAC";
static MultiAccountAutoConfirmer()
{
Clients = [];
Timer = new Timer(TimerConfirm);
Settings.Instance.PropertyChanged += SettingsOnPropertyChanged;
UpdateTimer();
}
private static async void TimerConfirm(object? state)
{
var clients = Lock.ReadLock(() => Clients.ToArray());
var enabledClients = clients.Where(x => x.LinkedClient is { IsError: false }).ToArray();
enabledClients = DistributeEvenly(enabledClients).ToArray();
var confirmed = 0;
await Task.Run(async () =>
{
foreach (var client in enabledClients)
{
var conf = 0;
try
{
conf = await client.LinkedClient!.Confirm();
}
catch (ObjectDisposedException)
{
//Ignored
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Internal error while confirming {accountName}", client.AccountName);
}
confirmed += conf;
}
}).ConfigureAwait(false);
if (confirmed > 0)
SnackbarController.SendSnackbar(GetLocalization("TimerConfirmed") + confirmed);
return;
//This function helps us to prevent 429 as much as it possible.
//It's not perfect but better than nothing
static IEnumerable<Mafile> DistributeEvenly(IEnumerable<Mafile> input)
{
var elementCounts = input
.GroupBy(x => x.Proxy?.Id ?? -1)
.ToDictionary(g => g.Key, g => new Queue<Mafile>(g));
var result = new List<Mafile>();
bool added;
do
{
added = false;
foreach (var key in elementCounts.Keys.ToList())
{
if (elementCounts[key].Count > 0)
{
result.Add(elementCounts[key].Dequeue());
added = true;
}
}
}
while (added);
return result;
}
}
public static bool TryAddToConfirm(Mafile mafile)
{
return Lock.WriteLock(() =>
{
if (Clients.Contains(mafile)) return false;
Clients.Add(mafile);
mafile.LinkedClient = new PortableMaClient(mafile);
return true;
});
}
public static void RemoveFromConfirm(Mafile mafile)
{
Lock.WriteLock(() =>
{
mafile.LinkedClient?.Dispose();
mafile.LinkedClient = null;
Clients.Remove(mafile);
});
}
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(Settings.TimerSeconds)) return;
UpdateTimer();
}
private static void UpdateTimer()
{
var timerInterval = Settings.Instance.TimerSeconds;
var intervalTimeSpan = TimeSpan.FromSeconds(timerInterval);
Timer.Change(intervalTimeSpan, intervalTimeSpan);
}
private static string GetLocalization(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
}
}
+172
View File
@@ -0,0 +1,172 @@
using AchiesUtilities.Web.Proxy;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using SteamLib.Api.Mobile;
using SteamLib.Authentication;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
using SteamLib.SteamMobile.Confirmations;
using SteamLib.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace NebulaAuth.Model.MAAC;
public partial class PortableMaClient : ObservableObject, IDisposable
{
public Mafile Mafile { get; }
private HttpClient Client { get; }
private HttpClientHandler ClientHandler { get; }
private DynamicProxy Proxy { get; }
[ObservableProperty] private bool _autoConfirmTrades;
[ObservableProperty] private bool _autoConfirmMarket;
[ObservableProperty] private bool _isError;
private const string LOC_PATH = "MAAC";
private readonly CancellationTokenSource _cts = new();
public PortableMaClient(Mafile mafile)
{
Mafile = mafile;
Proxy = new DynamicProxy();
Proxy.SetData(mafile.Proxy?.Data);
var pair = ClientBuilder.BuildMobileClient(Proxy, mafile.SessionData);
Client = pair.Client;
ClientHandler = pair.Handler;
UpdateCookies(mafile.SessionData);
Mafile.PropertyChanged += Mafile_PropertyChanged;
}
private void Mafile_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Mafile.SessionData))
{
UpdateCookies(Mafile.SessionData);
}
}
private void UpdateCookies(IMobileSessionData? sessionData)
{
Application.Current.Dispatcher.Invoke(() => IsError = sessionData == null);
ClientHandler.CookieContainer.ClearAllCookies();
if (sessionData != null)
{
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData);
}
else
{
ClientHandler.CookieContainer.ClearSteamCookies();
ClientHandler.CookieContainer.AddMinimalMobileCookies();
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
}
}
public async Task<int> Confirm()
{
Proxy.SetData(Mafile.Proxy?.Data ?? MaClient.DefaultProxy);
List<Confirmation> conf;
try
{
conf = (await HandleTimerRequest(GetConfirmations)).ToList();
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "Error GetConf in timer.");
return 0;
}
var toConfirm = new List<Confirmation>();
if (AutoConfirmMarket)
{
var market = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction);
toConfirm.AddRange(market);
}
if (AutoConfirmTrades)
{
var trade = conf.Where(c => c.ConfType == ConfirmationType.Trade);
toConfirm.AddRange(trade);
}
if (toConfirm.Count == 0) return 0;
try
{
Shell.Logger.Debug("Sending confirmations. Count: {count}", toConfirm.Count);
await HandleTimerRequest(() => SendConfirmations(toConfirm));
return toConfirm.Count;
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "MultiConf error in Timer.");
return 0;
}
}
private async Task<IEnumerable<Confirmation>> GetConfirmations()
{
return await SteamMobileConfirmationsApi.GetConfirmations(Client, Mafile, Mafile.SessionData!.SteamId, _cts.Token);
}
private async Task<bool> SendConfirmations(IEnumerable<Confirmation> confirmations)
{
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, confirmations, Mafile.SessionData!.SteamId, Mafile, true, _cts.Token);
}
private async Task<T> HandleTimerRequest<T>(Func<Task<T>> func)
{
Exception innerException;
try
{
return await SessionHandler.Handle(func, Mafile, GetTimerPrefix());
}
catch (OperationCanceledException ex)
{
innerException = ex; //Ignored
}
catch (SessionInvalidException ex)
{
Shell.Logger.Warn("Timer {accountName}: Session error while requesting in timer. Timer disabled", Mafile.AccountName);
SetError();
innerException = ex;
}
catch (Exception ex) when (ExceptionHandler.Handle(ex, prefix: GetTimerPrefix()))
{
innerException = ex;
}
throw new ApplicationException("Swallowed", innerException);
}
private static string GetLocalization(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
}
private string GetTimerPrefix()
{
return GetLocalization("TimerPrefix") + Mafile.AccountName + ": ";
}
public void SetError()
{
Application.Current.Dispatcher.Invoke(() => IsError = true);
Shell.Logger.Warn("Timer {accountName}: disabled due to error.", Mafile.AccountName);
SnackbarController.SendSnackbar(GetTimerPrefix() + GetLocalization("TimerSessionError"));
}
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
Mafile.PropertyChanged -= Mafile_PropertyChanged;
Client.Dispose();
ClientHandler.Dispose();
}
}
+22 -49
View File
@@ -1,12 +1,13 @@
using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Entities;
using SteamLib.Account;
using SteamLib.Api;
using SteamLib.Api.Mobile;
using SteamLib.Authentication;
using SteamLib.Authentication.LoginV2;
using SteamLib.Core.Enums;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Services;
using SteamLib.SteamMobile;
using SteamLib.SteamMobile.Confirmations;
@@ -14,11 +15,8 @@ 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;
@@ -35,23 +33,16 @@ public static class MaClient
static MaClient()
{
Proxy = new DynamicProxy(null);
Proxy = new DynamicProxy();
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();
ClientHandler.CookieContainer.ClearAllCookies();
if (account != null)
{
if (account.SessionData != null)
@@ -72,7 +63,7 @@ public static class MaClient
{
ValidateMafile(mafile);
SetProxy(mafile);
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId.Steam64);
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId);
}
public static async Task LoginAgain(Mafile mafile, string password, bool savePassword, ICaptchaResolver? resolver)
@@ -89,8 +80,8 @@ public static class MaClient
ClientHandler.CookieContainer.ClearMobileSessionCookies();
var result = await LoginV2Executor.DoLogin(options, mafile.AccountName, password);
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
mafile.SessionData = (MobileSessionData)result;
if(PHandler.IsPasswordSet)
mafile.SetSessionData((MobileSessionData)result);
if (PHandler.IsPasswordSet)
mafile.Password = (savePassword ? PHandler.Encrypt(password) : null);
Storage.UpdateMafile(mafile);
}
@@ -125,7 +116,7 @@ public static class MaClient
{
ValidateMafile(mafile);
SetProxy(mafile);
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId, mafile, confirm);
}
public static Task<bool> SendMultipleConfirmation(Mafile mafile, IEnumerable<Confirmation> confirmations, bool confirm)
@@ -138,7 +129,7 @@ public static class MaClient
ValidateMafile(mafile);
SetProxy(mafile);
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId, mafile, confirm);
}
public static Task<RemoveAuthenticator_Response> RemoveAuthenticator(Mafile mafile)
@@ -162,58 +153,41 @@ public static class MaClient
{
if (mafile.SessionData == null) throw new SessionInvalidException();
if (mafile.SessionData.RefreshToken.IsExpired)
throw new SessionExpiredException();
throw new SessionPermanentlyExpiredException();
if (ignoreAccessToken == false)
{
var access = mafile.SessionData.GetMobileToken();
if (access == null || access.Value.IsExpired)
throw new SessionExpiredException();
throw new SessionPermanentlyExpiredException();
}
}
public static async Task<LoginConfirmationResult> ConfirmLoginRequest(Mafile mafile) //TODO: move into library
public static async Task<LoginConfirmationResult> ConfirmLoginRequest(Mafile mafile)
{
ValidateMafile(mafile);
SetProxy(mafile);
var token = mafile.SessionData!.GetMobileToken()!.Value;
var sessions = await SteamMobileAuthenticatorApi.GetAuthSessionsForAccount(Client, token.Token);
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)
if (sessions.ClientIds.Count == 0)
{
return new LoginConfirmationResult
{
Error = LoginConfirmationError.NoRequests
};
}
if (getsess.ClientIds.Count > 1)
if (sessions.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 clientId = sessions.ClientIds.Single();
var clientInfo = await SteamMobileAuthenticatorApi.GetAuthSessionInfo(Client, token.Token, clientId);
var updateReq = new UpdateAuthSessionWithMobileConfirmation_Request
{
ClientId = clientId,
@@ -222,12 +196,11 @@ public static class MaClient
Steamid = mafile.SessionData.SteamId.Steam64.ToUlong(),
Version = 1
};
updateReq.ComputeSignature(mafile.SharedSecret);
await Client.PostProtoEnsureSuccess(updateUri, updateReq);
await SteamMobileAuthenticatorApi.UpdateAuthSessionStatus(Client, token.Token, mafile.SharedSecret, updateReq);
return new LoginConfirmationResult
{
Country = infoResp.Country,
IP = infoResp.IP,
Country = clientInfo.Country,
IP = clientInfo.IP,
Success = true
};
}
+12 -7
View File
@@ -5,22 +5,28 @@ using System.Text;
namespace NebulaAuth.Model;
public static class PHandler //RETHINK: Use SecureString?
public static class PHandler
{
public static bool IsPasswordSet => _k.Length > 0;
private static byte[] _k = Array.Empty<byte>();
private static byte[] _k = [];
public static void SetPassword(string? password)
/// <summary>
///
/// </summary>
/// <param name="password"></param>
/// <returns><see langword="true"/> if password was set and not empty. Otherwise - <see langword="false"/></returns>
public static bool SetPassword(string? password)
{
if (string.IsNullOrWhiteSpace(password))
{
_k = Array.Empty<byte>();
return;
_k = [];
return false;
}
var keyBytes = Encoding.UTF8.GetBytes(password);
_k = SHA256.HashData(keyBytes);
return _k.Length > 0;
}
public static string Encrypt(string plainText)
@@ -56,7 +62,6 @@ public static class PHandler //RETHINK: Use SecureString?
{
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;
@@ -70,7 +75,7 @@ public static class PHandler //RETHINK: Use SecureString?
using var ms = new MemoryStream(encryptedBytes);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var reader = new StreamReader(cs);
decryptedText = reader.ReadToEnd();
var decryptedText = reader.ReadToEnd();
return decryptedText;
}
+5 -4
View File
@@ -16,10 +16,11 @@ public static class ProxyStorage
public const string FORMAT = ADDRESS_FORMAT + ":{USER}:{PASS}";
public const string ADDRESS_FORMAT = "{IP}:{PORT}";
public static readonly ProxyScheme DefaultScheme = new ProxyScheme(
ProxyDefaultFormats.UniversalHostFirstColonDelimiter, false, ProxyProtocol.HTTP,
ProxyPatternProtocol.HTTP | ProxyPatternProtocol.HTTPs,
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4, PatternRequirement.Optional,
public static readonly ProxyParser DefaultScheme = new(
ProxyDefaultFormats.UniversalColon, false, ProxyProtocol.HTTP,
ProxyPatternProtocol.All,
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4,
PatternRequirement.Optional,
PatternRequirement.Optional);
+4 -4
View File
@@ -11,7 +11,7 @@ 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)
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile, string? snackbarPrefix = null)
{
string? password = null;
try
@@ -51,7 +51,7 @@ public static class SessionHandler
return await func();
}
catch (Exception ex3)
when (password != null && ex3 is SessionExpiredException or SessionInvalidException)
when (password != null && ex3 is SessionPermanentlyExpiredException or SessionInvalidException)
{
}
@@ -78,12 +78,12 @@ public static class SessionHandler
return await func();
}
private static async Task<bool> TryRefresh(Mafile mafile)
private static async Task<bool> TryRefresh(Mafile mafile, string? snackbarPrefix = null)
{
try
{
await MaClient.RefreshSession(mafile);
SnackbarController.SendSnackbar(LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically", "SessionHandler", "SessionWasRefreshedAutomatically"));
SnackbarController.SendSnackbar(snackbarPrefix + LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically", "SessionHandler", "SessionWasRefreshedAutomatically"));
return true;
}
catch (SessionInvalidException)
-1
View File
@@ -12,7 +12,6 @@ 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;
+1 -1
View File
@@ -17,7 +17,7 @@ public static class Shell
var lp = new NLog.Extensions.Logging.NLogLoggerProvider();
var logger = lp.CreateLogger("SteamLib");
HealthMonitor.FatalLogger = logger;
SteamLibErrorMonitor.MonitorLogger = logger;
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
var loggerFactory = new NLog.Extensions.Logging.NLogLoggerFactory();
+3 -3
View File
@@ -87,7 +87,7 @@ public static class Storage
try
{
var code = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
_ = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
}
catch (Exception ex)
{
@@ -271,7 +271,7 @@ internal class MafileNameComparer : IComparer<string>
{
public bool MafileNameMode { get; }
private const string MAF_64_START = "765";
private static readonly IComparer<string> _defaultComparer = Comparer<string>.Default;
private static readonly IComparer<string> DefaultComparer = Comparer<string>.Default;
public MafileNameComparer(bool mafileNameMode)
{
MafileNameMode = mafileNameMode;
@@ -300,7 +300,7 @@ internal class MafileNameComparer : IComparer<string>
}
}
return _defaultComparer.Compare(x, y);
return DefaultComparer.Compare(x, y);
}
}
+4 -4
View File
@@ -11,6 +11,7 @@
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.5.3</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@@ -25,13 +26,12 @@
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.1" />
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.3" />
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
<PackageReference Include="MaterialDesignExtensions" Version="4.0.0-a02" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
<PackageReference Include="NLog" Version="5.3.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.12" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.14" />
</ItemGroup>
<ItemGroup>
+4 -4
View File
@@ -2,8 +2,8 @@
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>
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="#26292E" />
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1" />
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5" />
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1" />
</ResourceDictionary>
+2 -2
View File
@@ -4,14 +4,14 @@ using System.Windows;
namespace NebulaAuth.Theme;
public partial class FontScaleWindow : Window
public 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;
private bool _loaded;
public FontScaleWindow()
{
+5 -11
View File
@@ -1,15 +1,9 @@
using System.Windows.Controls;
namespace LolzFucker.Theme;
namespace LolzFucker.Theme
public partial class Palette
{
/// <summary>
/// Логика взаимодействия для Palette.xaml
/// </summary>
public partial class Palette : UserControl
public Palette()
{
public Palette()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
@@ -1,9 +1,12 @@
using System;
using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
namespace NebulaAuth.Theme.WindowStyle;
public static class NativeMethods
// ReSharper disable once PartialTypeWithSinglePart //Required
public static partial class NativeMethods
{
public const int WM_NCCALCSIZE = 0x83;
public const int WM_NCPAINT = 0x85;
@@ -2,11 +2,14 @@
using System.Runtime.InteropServices;
using System.Windows;
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
namespace NebulaAuth.Theme.WindowStyle;
public static class WindowChromeHelper
{
public static Thickness LayoutOffsetThickness => new Thickness(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
public static Thickness LayoutOffsetThickness => new(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
/// <summary>
/// Gets the properly adjusted window resize border thickness from system parameters.
@@ -9,7 +9,8 @@ namespace NebulaAuth.Theme.WindowStyle;
public class WindowChromeRenderedBehavior : Behavior<Window>
{
private Window window;
private Window? _window;
protected override void OnAttached()
{
@@ -23,13 +24,13 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
AssociatedObject.ContentRendered -= OnContentRendered;
}
private void OnContentRendered(object sender, EventArgs e)
private void OnContentRendered(object? sender, EventArgs e)
{
window = Window.GetWindow(AssociatedObject);
_window = Window.GetWindow(AssociatedObject);
if (window == null) return;
if (_window == null) return;
var oldWindowChrome = WindowChrome.GetWindowChrome(window);
var oldWindowChrome = WindowChrome.GetWindowChrome(_window);
if (oldWindowChrome == null) return;
@@ -43,9 +44,9 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
UseAeroCaptionButtons = oldWindowChrome.UseAeroCaptionButtons
};
WindowChrome.SetWindowChrome(window, newWindowChrome);
WindowChrome.SetWindowChrome(_window, newWindowChrome);
var hWnd = new WindowInteropHelper(window).Handle;
var hWnd = new WindowInteropHelper(_window).Handle;
HwndSource.FromHwnd(hWnd)?.AddHook(WndProc);
}
@@ -86,7 +87,7 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
margins.rightWidth = 0;
margins.topHeight = 0;
var helper = new WindowInteropHelper(window);
var helper = new WindowInteropHelper(_window);
NativeMethods.DwmExtendFrameIntoClientArea(helper.Handle, ref margins);
}
@@ -1,36 +1,35 @@
using System.Windows;
namespace NebulaAuth.Theme.WindowStyle
namespace NebulaAuth.Theme.WindowStyle;
/// <summary>
/// Логика взаимодействия для WindowStyle.xaml
/// </summary>
partial class WindowStyle
{
/// <summary>
/// Логика взаимодействия для WindowStyle.xaml
/// </summary>
partial class WindowStyle
public 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;
}
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;
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ public static class ExceptionHandler
Shell.Logger.Error(ex);
switch (ex)
{
case SessionExpiredException:
case SessionPermanentlyExpiredException:
{
msg = "SessionExpiredException".GetCodeBehindLocalization();
break;
+28 -28
View File
@@ -5,7 +5,7 @@
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
>
xmlns:vm="clr-namespace:NebulaAuth.ViewModel">
<DataTemplate DataType="{x:Type confirmations:Confirmation}">
@@ -18,15 +18,15 @@
<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="1" Text="{Binding TypeName}" />
<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}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -43,21 +43,21 @@
<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>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0" />
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0" />
<TextBlock VerticalAlignment="Center" Grid.Column="2" TextWrapping="WrapWithOverflow" >
<Run Text="{Tr MainWindow.ConfirmationTemplates.TradeWith, IsDynamic=False}"/>
<Run Text="{Binding UserName}" FontWeight="Bold"></Run>
<Run Text="{Binding UserName}" FontWeight="Bold" />
</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}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -76,12 +76,12 @@
<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}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -98,14 +98,14 @@
<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>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0" />
<Border Grid.Column="1" Background="{DynamicResource MaterialDesignPaper}" MaxHeight="36" HorizontalAlignment="Center" BorderBrush="{DynamicResource PrimaryHueMidBrush}" BorderThickness="0.6" Margin="0,0,10,0">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" ></Image>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" />
</Border>
<Grid Grid.Column="2">
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True" TextWrapping="WrapWithOverflow" Text="{Binding ItemName}"/>
<TextBlock Grid.Row="1" Foreground="LightGray" Text="{Binding PriceString}">
<TextBlock Foreground="LightGray" Text="{Binding PriceString}">
<TextBlock.FontSize>
<Binding ElementName="ItemName" Path="FontSize" ConverterParameter="0.7">
<Binding.Converter>
@@ -118,12 +118,12 @@
<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}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -139,15 +139,15 @@
<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="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}" />
<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}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -162,7 +162,7 @@
<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>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0" />
<TextBlock VerticalAlignment="Center" Grid.Column="1">
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market, IsDynamic=False}"/>
<Run Text="{Binding Confirmations.Count, Mode=OneWay}"/>
@@ -171,12 +171,12 @@
<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}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -1,21 +1,18 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs;
namespace NebulaAuth.View.Dialogs
/// <summary>
/// Логика взаимодействия для ConfirmCancelDialog.xaml
/// </summary>
public partial class ConfirmCancelDialog
{
/// <summary>
/// Логика взаимодействия для ConfirmCancelDialog.xaml
/// </summary>
public partial class ConfirmCancelDialog : UserControl
public ConfirmCancelDialog()
{
public ConfirmCancelDialog()
{
InitializeComponent();
}
public ConfirmCancelDialog(string msg)
{
InitializeComponent();
ConfirmTextBlock.Text = msg;
}
InitializeComponent();
}
}
public ConfirmCancelDialog(string msg)
{
InitializeComponent();
ConfirmTextBlock.Text = msg;
}
}
@@ -7,7 +7,6 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:model="clr-namespace:NebulaAuth.Model"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
mc:Ignorable="d"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
Foreground="WhiteSmoke"
@@ -25,7 +24,7 @@
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
<CheckBox Grid.Row="2" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
<Grid Grid.Row="3" Margin="10,10,10,0">
<Grid.ColumnDefinitions>
@@ -1,15 +1,12 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs;
namespace NebulaAuth.View.Dialogs
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainDialog
{
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainDialog : UserControl
public LoginAgainDialog()
{
public LoginAgainDialog()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
@@ -27,7 +27,7 @@
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
<ComboBox ToolTip="{Tr LoginAgainDialog.ProxyToolTip}" Grid.Row="2" Margin="10,18,10,0" materialDesign:HintAssist.Hint="{Tr Common.Proxy}" ItemsSource="{Binding Proxies}" SelectedItem="{Binding SelectedProxy}" >
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
@@ -1,13 +1,12 @@
namespace NebulaAuth.View.Dialogs
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainOnImportDialog
{
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainOnImportDialog
public LoginAgainOnImportDialog()
{
public LoginAgainOnImportDialog()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
@@ -2,11 +2,9 @@
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:d="http://schemas.microsoft.com/expression/blend/2008"
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"
@@ -21,7 +19,7 @@
<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>
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}" />
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
@@ -1,15 +1,12 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs;
namespace NebulaAuth.View.Dialogs
/// <summary>
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
/// </summary>
public partial class SetCryptPasswordDialog
{
/// <summary>
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
/// </summary>
public partial class SetCryptPasswordDialog : UserControl
public SetCryptPasswordDialog()
{
public SetCryptPasswordDialog()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
+3 -3
View File
@@ -18,8 +18,8 @@
<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>
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" IsIndeterminate="True" />
<TextBlock Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}" VerticalAlignment="Center" />
</Grid>
<Grid x:Name="CaptchaGrid" Margin="0,25,0,0" Visibility="Collapsed" Grid.Row="1">
<Grid.RowDefinitions>
@@ -27,7 +27,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image x:Name="CaptchaImage" Stretch="Uniform"></Image>
<Image x:Name="CaptchaImage" Stretch="Uniform" />
<TextBox Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" x:Name="CaptchaTB"/>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
+59 -60
View File
@@ -7,66 +7,65 @@ using System.Windows.Media.Imaging;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
namespace NebulaAuth.View.Dialogs
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для WaitLoginDialog.xaml
/// </summary>
public partial class WaitLoginDialog : ICaptchaResolver
{
/// <summary>
/// Логика взаимодействия для WaitLoginDialog.xaml
/// </summary>
public partial class WaitLoginDialog : ICaptchaResolver
private TaskCompletionSource<string> _tcs = new();
public WaitLoginDialog()
{
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);
}
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);
}
}
+6 -7
View File
@@ -2,8 +2,7 @@
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: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"
@@ -66,15 +65,15 @@
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}"/>
<TextBlock Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" >
<Hyperlink Command="{Binding OpenTroubleshootingCommand}">
<Run Text="{Tr LinkerDialog.GotErrorHyperlinkText, IsDynamic=False}"></Run>
</Hyperlink>
<Run Text="{Tr LinkerDialog.GotErrorHyperlinkText, IsDynamic=False}" />
</Hyperlink>
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Background="DarkGray" Grid.Row="1"></Separator>
<Separator Background="DarkGray" Grid.Row="1" />
<Grid Grid.Row="2" Margin="10,20,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
@@ -122,7 +121,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Padding="7" Text="{Binding FieldText}" Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10"></TextBox>
<TextBox Padding="7" Text="{Binding FieldText}" Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10" />
<TextBox Grid.Row="1" Padding="7" Text="{Binding PassFieldText}" Visibility="{Binding IsPasswordFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16"/>
<GroupBox Grid.Row="2" MaxWidth="400" BorderBrush="{StaticResource PrimaryHueMidBrush}" VerticalAlignment="Bottom" BorderThickness="1" Margin="0,10,0,0" Padding="5">
<GroupBox.HeaderTemplate>
+8 -11
View File
@@ -1,15 +1,12 @@
using System.Windows.Controls;
namespace NebulaAuth.View;
namespace NebulaAuth.View
/// <summary>
/// Логика взаимодействия для LinkerView.xaml
/// </summary>
public partial class LinkerView
{
/// <summary>
/// Логика взаимодействия для LinkerView.xaml
/// </summary>
public partial class LinkerView : UserControl
public LinkerView()
{
public LinkerView()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
+9 -16
View File
@@ -2,12 +2,11 @@
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DesignHeight="500" d:DesignWidth="400"
Foreground="WhiteSmoke"
FontFamily="{md:MaterialDesignFont}"
MinHeight="500"
@@ -15,12 +14,6 @@
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>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -36,11 +29,11 @@
</Grid.ColumnDefinitions>
<TextBlock Margin="10" FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}"/>
<Button Margin="0,0,10,0" 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>
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1"></Separator>
<Separator Grid.Row="1" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -54,7 +47,7 @@
</TextBlock>
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
<md:PackIcon Kind="ClearBox" Width="20" Height="20"></md:PackIcon>
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
</Button>
</Grid>
<md:Card Grid.Row="3" Margin="10">
@@ -98,7 +91,7 @@
<Button Style="{StaticResource MaterialDesignIconButton}" Padding="0" Width="24" Height="24" md:RippleAssist.IsDisabled="True" Grid.Column="1" HorizontalAlignment="Right"
Command="{Binding DataContext.SetDefaultCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}">
<md:PackIcon Height="16" Width="16" Kind="Heart"></md:PackIcon>
<md:PackIcon Height="16" Width="16" Kind="Heart" />
</Button>
</Grid>
@@ -113,12 +106,12 @@
<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>
<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}" />
<Button IsDefault="True" Grid.Column="1" Command="{Binding AddProxyCommand}">
<md:PackIcon Kind="Add"></md:PackIcon>
<md:PackIcon Kind="Add" />
</Button>
<Button Grid.Column="2" Command="{Binding RemoveProxyCommand}">
<md:PackIcon Kind="Trash"></md:PackIcon>
<md:PackIcon Kind="Trash" />
</Button>
</Grid>
+8 -11
View File
@@ -1,15 +1,12 @@
using System.Windows.Controls;
namespace NebulaAuth.View;
namespace NebulaAuth.View
/// <summary>
/// Логика взаимодействия для ProxyManagerView.xaml
/// </summary>
public partial class ProxyManagerView
{
/// <summary>
/// Логика взаимодействия для ProxyManagerView.xaml
/// </summary>
public partial class ProxyManagerView : UserControl
public ProxyManagerView()
{
public ProxyManagerView()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
+7 -13
View File
@@ -6,20 +6,15 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
d:DesignHeight="700"
d:DesignHeight="650"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
MinHeight="300"
MinHeight="600"
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>
@@ -35,15 +30,14 @@
</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>
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1" Margin="0,10,0,0" />
<StackPanel Grid.Row="2" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" ></ComboBox>
<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}"/>
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" />
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" />
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}" ToolTip="{Tr SettingsDialog.UseIndicatorHint}"/>
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
@@ -58,11 +52,11 @@
<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>
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" />
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom" Grid.Column="1"> <materialDesign:PackIcon Kind="ContentSave"/></Button>
</Grid>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding LegacyMode}" Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseAccountNameAsMafileName}" >
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
</CheckBox>
+8 -19
View File
@@ -1,23 +1,12 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace NebulaAuth.View;
namespace NebulaAuth.View
/// <summary>
/// Логика взаимодействия для SettingsView.xaml
/// </summary>
public partial class SettingsView
{
/// <summary>
/// Логика взаимодействия для SettingsView.xaml
/// </summary>
public partial class SettingsView : UserControl
public SettingsView()
{
public SettingsView()
{
InitializeComponent();
}
private void ColorPicker_OnColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
{
Debug.WriteLine(e.NewValue);
}
InitializeComponent();
}
}
}
+2 -4
View File
@@ -2,11 +2,9 @@
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
@@ -29,7 +27,7 @@
</Grid.RowDefinitions>
<TextBlock FontSize="24" Text="Обновления"/>
<Separator Grid.Row="1"></Separator>
<Separator Grid.Row="1" />
<TextBlock FontSize="16" Margin="5,10,0,10" TextWrapping="WrapWithOverflow" Grid.Row="2" >
<Run Text="Доступна новая версия:"/>
+8 -11
View File
@@ -1,15 +1,12 @@
using System.Windows.Controls;
namespace NebulaAuth.View;
namespace NebulaAuth.View
/// <summary>
/// Логика взаимодействия для UpdaterView.xaml
/// </summary>
public partial class UpdaterView
{
/// <summary>
/// Логика взаимодействия для UpdaterView.xaml
/// </summary>
public partial class UpdaterView : UserControl
public UpdaterView()
{
public UpdaterView()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
+18 -16
View File
@@ -5,6 +5,7 @@ using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using NebulaAuth.View.Dialogs;
using SteamLib.Exceptions;
using SteamLib.SteamMobile;
using System;
@@ -13,7 +14,6 @@ using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NebulaAuth.View.Dialogs;
namespace NebulaAuth.ViewModel;
@@ -32,11 +32,12 @@ public partial class MainVM : ObservableObject
}
private Mafile? _selectedMafile;
public bool IsMafileSelected => SelectedMafile != null;
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;
@@ -47,24 +48,26 @@ public partial class MainVM : ObservableObject
if (Storage.DuplicateFound > 0)
{
SnackbarController.SendSnackbar(
GetLocalizationOrDefault("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
GetLocalization("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
TimeSpan.FromSeconds(4));
}
}
private void SetMafile(Mafile? mafile)
{
if (mafile != SelectedMafile)
{
_selectedMafile = mafile;
OnPropertyChanged(nameof(SelectedMafile));
OnPropertyChanged(nameof(TradeTimerEnabled));
OnPropertyChanged(nameof(MarketTimerEnabled));
MaClient.SetAccount(mafile);
OnPropertyChanged(nameof(ConfirmationsVisible));
if (Settings.DisableTimersOnChange) OffTimer(dispatcher: false);
SetCurrentProxy();
OnPropertyChanged(nameof(IsDefaultProxy));
if (mafile != null) Code = SteamGuardCodeGenerator.GenerateCode(mafile.SharedSecret);
OnPropertyChanged(nameof(IsMafileSelected));
}
}
@@ -81,14 +84,14 @@ public partial class MainVM : ObservableObject
{
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"));
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
}
catch (LoginException ex)
{
@@ -118,7 +121,7 @@ public partial class MainVM : ObservableObject
try
{
await MaClient.RefreshSession(SelectedMafile);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SessionRefreshed"));
SnackbarController.SendSnackbar(GetLocalization("SessionRefreshed"));
}
catch (Exception ex) when (ExceptionHandler.Handle(ex))
{
@@ -129,7 +132,6 @@ public partial class MainVM : ObservableObject
[RelayCommand]
public async Task LinkAccount()
{
OffTimer(false);
await DialogsController.ShowLinkerDialog();
}
@@ -140,16 +142,16 @@ public partial class MainVM : ObservableObject
if (selectedMafile == null) return;
if (string.IsNullOrWhiteSpace(selectedMafile.RevocationCode))
{
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalizationOrDefault("MissingRCode"));
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalization("MissingRCode"));
return;
}
try
{
if (await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmRemovingAuthenticator")))
if (await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmRemovingAuthenticator")))
{
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(selectedMafile), selectedMafile);
SnackbarController.SendSnackbar(
result.Success ? GetLocalizationOrDefault("AuthenticatorRemoved") : GetLocalizationOrDefault("AuthenticatorNotRemoved"));
result.Success ? GetLocalization("AuthenticatorRemoved") : GetLocalization("AuthenticatorNotRemoved"));
if (result.Success)
{
@@ -176,17 +178,17 @@ public partial class MainVM : ObservableObject
try
{
LoginConfirmationResult res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
var res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
if (res.Success)
{
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
SnackbarController.SendSnackbar($"{GetLocalization("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
}
else
{
string msg = res.Error switch
{
LoginConfirmationError.NoRequests => GetLocalizationOrDefault("ConfirmLoginFailedNoRequests"),
LoginConfirmationError.MoreThanOneRequest => GetLocalizationOrDefault("ConfirmLoginFailedMoreThanOneRequest"), //TODO
LoginConfirmationError.NoRequests => GetLocalization("ConfirmLoginFailedNoRequests"),
LoginConfirmationError.MoreThanOneRequest => GetLocalization("ConfirmLoginFailedMoreThanOneRequest"), //TODO
_ => throw new ArgumentOutOfRangeException()
};
SnackbarController.SendSnackbar(msg);
+31 -2
View File
@@ -1,7 +1,12 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using SteamLib.SteamMobile;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
@@ -11,7 +16,7 @@ public partial class MainVM
{
private Timer _codeTimer;
[ObservableProperty] private double _codeProgress;
[ObservableProperty] private string _code;
[ObservableProperty] private string _code = "Code";
[MemberNotNull(nameof(_codeTimer))]
@@ -20,7 +25,7 @@ public partial class MainVM
_codeTimer = new Timer(UpdateCode, null, 0, 1000);
}
private void UpdateCode(object? state = null)
{
@@ -43,4 +48,28 @@ public partial class MainVM
if (c != null) Code = c;
}, DispatcherPriority.Background, code);
}
[RelayCommand(AllowConcurrentExecutions = false)]
private async Task CopyCode()
{
var selectedMafile = SelectedMafile;
if (selectedMafile == null) return;
try
{
Clipboard.SetText(Code);
Code = LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied");
}
catch (Exception ex)
{
Shell.Logger.Error(ex);
}
finally
{
await Task.Delay(200);
selectedMafile = SelectedMafile;
if (selectedMafile != null)
Code = SteamGuardCodeGenerator.GenerateCode(selectedMafile.SharedSecret);
}
}
}
+21 -18
View File
@@ -40,17 +40,20 @@ public partial class MainVM //Confirmations
_confirmationsLoadedForMafile = maf;
OnPropertyChanged(nameof(ConfirmationsVisible));
Confirmations.Clear();
var marketConfirmations = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction).ToList();
var marketConfirmations = conf
.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction)
.Cast<MarketConfirmation>()
.ToList();
if (marketConfirmations.Count > 1)
{
var indexOfLast = conf.IndexOf(marketConfirmations.First());
var indexOfLast = conf.IndexOf(marketConfirmations.Last());
foreach (var mCon in marketConfirmations)
{
conf.Remove(mCon);
}
var mConf = new MarketMultiConfirmation(marketConfirmations.Cast<MarketConfirmation>());
var mConf = new MarketMultiConfirmation(marketConfirmations);
conf.Insert(indexOfLast, mConf);
}
@@ -60,12 +63,25 @@ public partial class MainVM //Confirmations
}
}
[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);
}
private async Task SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
{
bool result;
try
{
if (confirmation is MarketMultiConfirmation multi)
if (confirmation is MarketMultiConfirmation multi)
{
result = await MaClient.SendMultipleConfirmation(mafile, multi.Confirmations, confirm);
}
@@ -86,20 +102,7 @@ public partial class MainVM //Confirmations
}
else
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("ConfirmationError"));
SnackbarController.SendSnackbar(GetLocalization("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);
}
}
+14 -14
View File
@@ -38,7 +38,7 @@ public partial class MainVM //File //TODO: Refactor
}
if (mafilePath != null)
{
path = $"/select, \"{mafilePath}\""; ;
path = $"/select, \"{mafilePath}\"";
}
try
@@ -65,7 +65,7 @@ public partial class MainVM //File //TODO: Refactor
var fs = openFileDialog.ShowDialog();
if (fs != true) return Task.CompletedTask;
var path = openFileDialog.FileName;
return AddMafile(new[] { path });
return AddMafile([path]);
}
public async Task AddMafile(string[] path)
@@ -87,7 +87,7 @@ public partial class MainVM //File //TODO: Refactor
}
catch (IOException)
{
confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmMafileOverwrite"));
confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmMafileOverwrite"));
if (confirmOverwrite == true)
{
@@ -115,24 +115,24 @@ public partial class MainVM //File //TODO: Refactor
}
else
{
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("MafileImportError")} {Path.GetFileName(str)}{GetLocalizationOrDefault("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
SnackbarController.SendSnackbar($"{GetLocalization("MafileImportError")} {Path.GetFileName(str)}{GetLocalization("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
}
}
}
var msg = GetLocalizationOrDefault("Import");
var msg = GetLocalization("Import");
if (added > 0)
{
msg += $" {GetLocalizationOrDefault("ImportAdded")} {added}.";
msg += $" {GetLocalization("ImportAdded")} {added}.";
}
if (notAdded > 0)
{
msg += $" {GetLocalizationOrDefault("ImportSkipped")} {notAdded}.";
msg += $" {GetLocalization("ImportSkipped")} {notAdded}.";
}
if (errors > 0)
{
msg += $" {GetLocalizationOrDefault("ImportErrors")} {errors}.";
msg += $" {GetLocalization("ImportErrors")} {errors}.";
}
SnackbarController.SendSnackbar(msg, TimeSpan.FromSeconds(2));
}
@@ -155,7 +155,7 @@ public partial class MainVM //File //TODO: Refactor
try
{
await MaClient.LoginAgain(data, password, loginAgainVm.SavePassword, waitDialog);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
}
catch (LoginException ex)
{
@@ -188,7 +188,7 @@ public partial class MainVM //File //TODO: Refactor
{
if (SelectedMafile == null) return;
var confirm =
await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("RemoveMafileConfirmation"));
await DialogsController.ShowConfirmCancelDialog(GetLocalization("RemoveMafileConfirmation"));
if (!confirm) return;
try
@@ -197,12 +197,12 @@ public partial class MainVM //File //TODO: Refactor
}
catch (UnauthorizedAccessException)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRemoveAlreadyExist"));
SnackbarController.SendSnackbar(GetLocalization("CantRemoveAlreadyExist"));
}
catch (Exception ex)
{
SnackbarController.SendSnackbar(
$"{GetLocalizationOrDefault("CantRemoveMafile")} {ex.Message}");
$"{GetLocalization("CantRemoveMafile")} {ex.Message}");
}
}
@@ -218,7 +218,7 @@ public partial class MainVM //File //TODO: Refactor
}
[RelayCommand]
private async Task CopyMafileFromBuffer()
private async Task PasteMafilesFromClipboard()
{
StringCollection files;
try
@@ -249,7 +249,7 @@ public partial class MainVM //File //TODO: Refactor
try
{
Clipboard.SetText(maf.AccountName);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("LoginCopied"));
SnackbarController.SendSnackbar(GetLocalization("LoginCopied"));
return;
}
catch (Exception ex)
+2 -1
View File
@@ -11,7 +11,7 @@ namespace NebulaAuth.ViewModel;
public partial class MainVM //Groups
{
[ObservableProperty]
private ObservableCollection<string> _groups = new();
private ObservableCollection<string> _groups = [];
public string? SelectedGroup
@@ -109,6 +109,7 @@ public partial class MainVM //Groups
private void PerformQuery()
{
MaacDisplay = false;
if (string.IsNullOrWhiteSpace(SelectedGroup) && string.IsNullOrWhiteSpace(SearchText))
{
MaFiles = Storage.MaFiles;
@@ -1,17 +0,0 @@
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);
}
}
+118
View File
@@ -0,0 +1,118 @@
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Model.MAAC;
namespace NebulaAuth.ViewModel;
public partial class MainVM //MAAC
{
public bool MaacDisplay
{
get => _maacDisplay;
set
{
if (SetProperty(ref _maacDisplay, value))
{
SwitchMAACDispaly(value);
}
}
}
private bool _maacDisplay;
public bool MarketTimerEnabled
{
get => SelectedMafile?.LinkedClient?.AutoConfirmMarket ?? false;
set => SetMarketTimer(value);
}
public bool TradeTimerEnabled
{
get => SelectedMafile?.LinkedClient?.AutoConfirmTrades ?? false;
set => SetTradeTimer(value);
}
public int TimerCheckSeconds
{
get => Settings.Instance.TimerSeconds;
set => SetTimer(value);
}
private void SetMarketTimer(bool value)
{
var selectedMafile = SelectedMafile;
if (selectedMafile == null) return;
if (value && selectedMafile.LinkedClient == null)
{
MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile);
}
if (!value && selectedMafile is { LinkedClient.AutoConfirmTrades: false })
{
MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile);
}
if (selectedMafile.LinkedClient != null)
{
selectedMafile.LinkedClient.AutoConfirmMarket = value;
}
}
private void SetTradeTimer(bool value)
{
var selectedMafile = SelectedMafile;
if (selectedMafile == null) return;
if (value && selectedMafile.LinkedClient == null)
{
MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile);
}
if (!value && selectedMafile is { LinkedClient.AutoConfirmMarket: false })
{
MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile);
}
if (selectedMafile.LinkedClient != null)
{
selectedMafile.LinkedClient.AutoConfirmTrades = value;
}
}
private void SetTimer(int value)
{
var timerCheckSeconds = Settings.TimerSeconds;
if (timerCheckSeconds == value) return;
if (timerCheckSeconds < 10)
{
timerCheckSeconds = 10; //Guard
}
if (value < 10)
{
value = timerCheckSeconds;
SnackbarController.SendSnackbar(GetLocalization("TimerTooFast"));
}
Settings.TimerSeconds = value;
OnPropertyChanged(nameof(TimerCheckSeconds));
if (timerCheckSeconds != TimerCheckSeconds)
SnackbarController.SendSnackbar(GetLocalization("TimerChanged"));
}
[RelayCommand]
public void RemoveFromMAAC(object? maf)
{
if (maf is not Mafile mafile) return;
MultiAccountAutoConfirmer.RemoveFromConfirm(mafile);
}
private void SwitchMAACDispaly(bool newValue)
{
if (newValue)
{
MaFiles = MultiAccountAutoConfirmer.Clients;
}
else
{
PerformQuery();
}
}
}
+10
View File
@@ -2,11 +2,21 @@
using System;
using System.Windows;
using NebulaAuth.View.Dialogs;
using NebulaAuth.Core;
namespace NebulaAuth.ViewModel;
public partial class MainVM //Other
{
private const string LOC_PATH = "MainVM";
private static string GetLocalization(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
}
private void SessionHandlerOnLoginCompleted(object? sender, EventArgs e)
{
var currentSession = DialogHost.GetDialogSession(null);
+6 -5
View File
@@ -6,7 +6,6 @@ using NebulaAuth.Model.Entities;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using NebulaAuth.Model.Comparers;
namespace NebulaAuth.ViewModel;
@@ -23,16 +22,17 @@ public partial class MainVM
{
OnPropertyChanged(nameof(IsDefaultProxy));
OnProxyChanged();
};
}
}
}
private MaProxy? _selectedProxy;
[ObservableProperty] private bool _proxyExist = true;
public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null;
public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null && SelectedMafile != null;
private bool _handleProxyChange;
//TODO: Refactor this method
private void SetCurrentProxy()
{
if (ReferenceEquals(_selectedProxy, SelectedMafile?.Proxy) == false && _selectedProxy?.Equals(SelectedMafile?.Proxy) == false)
@@ -41,6 +41,7 @@ public partial class MainVM
if (SelectedMafile == null)
{
SelectedProxy = null;
ProxyExist = true;
return;
}
if (SelectedMafile.Proxy == null)
@@ -52,7 +53,7 @@ public partial class MainVM
var existed = Proxies.FirstOrDefault(p => p.Id == SelectedMafile.Proxy.Id);
if (existed == null || ProxyDataComparer.Equal(existed.Data, SelectedMafile.Proxy.Data) == false)
if (existed == null || existed.Data.Equals(SelectedMafile.Proxy.Data) == false)
{
SelectedProxy = SelectedMafile.Proxy;
}
@@ -127,7 +128,7 @@ public partial class MainVM
var canSave = Storage.ValidateCanSave(data);
if (!canSave)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRetrieveSteamIDToUpdate"));
SnackbarController.SendSnackbar(GetLocalization("CantRetrieveSteamIDToUpdate"));
}
return canSave;
}
-141
View File
@@ -1,141 +0,0 @@
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);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("TimerChanged"));
}
}
+4 -4
View File
@@ -61,7 +61,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
private TaskCompletionSource _emailConfTcs = new();
private TaskCompletionSource<string> _linkCodeTcs = new();
private bool isLinkStarted;
private bool _isLinkStarted;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(ProceedCommand))]
@@ -179,12 +179,12 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
#endregion
if (isLinkStarted)
if (_isLinkStarted)
goto linkStarted;
try
{
isLinkStarted = true;
_isLinkStarted = true;
var linkOptions = new LinkOptions(Client, LoginV2Executor.NullConsumer, this,
null, this, this, backupHandler: Backup, Logger2);
_linker = new SteamAuthenticatorLinker(linkOptions);
@@ -313,7 +313,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
IsLogin = false;
IsFieldVisible = true;
IsEmailCode = false;
isLinkStarted = false;
_isLinkStarted = false;
IsPhoneNumber = false;
IsEmailConfirmation = false;
CanProceed = true;
+2 -8
View File
@@ -11,11 +11,6 @@ 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;
@@ -119,10 +114,9 @@ public partial class SettingsVM : ObservableObject
[RelayCommand]
public void SetPassword()
private void SetPassword()
{
PHandler.SetPassword(Password);
Settings.IsPasswordSet = PHandler.IsPasswordSet;
Settings.IsPasswordSet = PHandler.SetPassword(Password);
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace NebulaAuth.ViewModel.Other;
public partial class UpdaterVM : ObservableObject
public class UpdaterVM : ObservableObject
{
public UpdateInfoEventArgs UpdateInfoEventArgs { get; }
+53 -46
View File
@@ -172,14 +172,19 @@
}
},
"TradeTimerHint": {
"en": "Trade",
"ru": "Трейд",
"ua": "Трейд"
"en": "Auto-confirm trades",
"ru": "Автоподтверждение трейдов",
"ua": "Автопідтвердження трейдів"
},
"MarketTimerHint": {
"en": "Market",
"ru": "Маркет",
"ua": "Маркет"
"en": "Auto-confirm market",
"ru": "Автоподтверждение маркета",
"ua": "Автопідтвердження маркету"
},
"ShowAutoConfirmAccountsHint": {
"en": "Show accounts with auto-confirmation enabled.",
"ru": "Показать аккаунты с включенным автоподтверждением.",
"ua": "Показати акаунти з включеним автопідтвердженням."
}
},
"LeftPart": {
@@ -256,6 +261,11 @@
"en": "Remove from group",
"ru": "Удалить из группы",
"ua": "Видалити з групи"
},
"RemoveFromMAAC": {
"en": "Turn off auto-confirm",
"ru": "Выкл. авто-подтверждение",
"ua": "Вимкнути авто-підтвердження"
}
},
"Proxy": {
@@ -300,11 +310,6 @@
"ua": "Без фону"
}
},
"DisableTimersOnSwitch": {
"en": "Disable timers on account switch",
"ru": "Отключать таймеры при смене аккаунта",
"ua": "Вимикати таймери при зміні акаунта"
},
"MinimizeToTray": {
"en": "Minimize to tray",
"ru": "Сворачивать в трей",
@@ -605,26 +610,6 @@
"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.",
@@ -897,9 +882,9 @@
"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. Спробуйте прив'язати з допомогою СМС. Якщо СМС прийде, але помилка повториться. Потрібно спробувати ще раз."
"ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.",
"en": "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
"ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна."
},
"GeneralFailure": {
"ru": "General Failure",
@@ -909,15 +894,15 @@
}
},
"ExceptionHandler": {
"SessionExpiredException": {
"ru": "Сессия истекла. Попробуйте обновить ее через меню",
"en": "Session expired. Try to refresh it through menu",
"ua": "Сесія закінчилася. Спробуйте оновити її через меню"
},
"SessionInvalidException": {
"ru": "Сессия невалидна. Нужно залогиниться заново",
"en": "Session invalid. Need to login again",
"ua": "Сесія невалідна. Потрібно залогінитися знову"
"ru": "Сессия истекла. Попробуйте обновить ее через меню или залогиниться заново",
"en": "Session expired. Try to refresh it through menu or login again",
"ua": "Сесія прострочена. Спробуйте оновити її через меню або залогінитися знову"
},
"SessionExpiredException": {
"ru": "Сессия полностью истекла. Нужно залогиниться заново",
"en": "Session fully expired. Need to login again",
"ua": "Сесія повніст'ю прострочена'. Потрібно залогінитися знову"
},
"TaskCanceledException": {
"ru": "Произошла отмена операции",
@@ -1027,11 +1012,33 @@
"ua": "На телефон {0} було відправлено СМС"
},
"EnterPhoneNumber": {
"ru": "Введите номер телефона (без знака '+' и др. символов)",
"en": "Enter phone number (without '+' and other symbols)",
"ua": "Введіть номер телефону (без знака '+' та ін. символів)"
"ru": "Введите номер телефона (без знака '+' и др. символов). По-желанию.",
"en": "Enter phone number (without '+' and other symbols). Optional",
"ua": "Введіть номер телефону (без знака '+' та ін. символів). За бажанням"
}
}
},
"MAAC": {
"TimerConfirmed": {
"ru": "Авто: подтверждено ",
"en": "Auto: confirmed ",
"ua": "Авто: підтверджено "
},
"TimerNotConfirmed": {
"ru": "Авто: подтверждение не сработало",
"en": "Auto: confirmation was unsuccessful",
"ua": "Авто: підтвердження не спрацювало"
},
"TimerSessionError": {
"ru": "необходимо обновить сессию или перелогиниться",
"en": "need to refresh session or relogin",
"ua": "необхідно оновити сесію або перелогінитися"
},
"TimerPrefix": {
"ru": "Авто ",
"en": "Auto ",
"ua": "Авто "
}
}
},
"CantAlignTimeError": {
"ru": "Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log",
@@ -54,7 +54,7 @@ public static class SteamAuthenticatorLinkerApi
};
var resp = await client.PostProtoMsg<AddAuthenticator_Response>(reqUri, req);
if (resp is {Result: EResult.InvalidState, ResponseMsg.Status: 2})
if (resp is { Result: EResult.InvalidState, ResponseMsg.Status: 2 })
{
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidStateWithStatus2);
}
@@ -73,8 +73,8 @@ public static class SteamAuthenticatorLinkerApi
{
var validateSmsReq = new FinalizeAddAuthenticator_Request
{
SteamId = data.SteamId.Steam64.ToUlong(),
AuthenticatorTime = (ulong) time,
SteamId = data.SteamId.Steam64.ToUlong(),
AuthenticatorTime = (ulong)time,
ConfirmationCode = confirmationCode,
ValidateConfirmationCode = true
};
@@ -182,7 +182,7 @@ public static class SteamAuthenticatorLinkerApi
while (i < 5)
{
i++;
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri, new EmptyMessage()); ;
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri, new EmptyMessage());
if (resp.IsWaiting == false) return true;
+21 -15
View File
@@ -1,5 +1,4 @@
using System.Net;
using JetBrains.Annotations;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using SteamLib.Core;
using SteamLib.Exceptions;
@@ -7,14 +6,13 @@ using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Enums;
using SteamLib.ProtoCore.Exceptions;
using SteamLib.ProtoCore.Services;
using System.Net;
namespace SteamLib.Api.Mobile;
[PublicAPI]
public static class SteamMobileApi
//TODO: cancellation token
//TODO: HealthMonitor
{
private const string GENERATE_ACCESS_TOKEN =
@@ -27,9 +25,10 @@ public static class SteamMobileApi
/// <param name="client"></param>
/// <param name="refreshToken"></param>
/// <param name="steamId"></param>
/// <param name="cancellationToken"></param>
/// <returns>Refreshed AccessToken</returns>
/// <exception cref="SessionInvalidException"></exception>
public static async Task<string> RefreshJwt(HttpClient client, string refreshToken, long steamId)
public static async Task<string> RefreshJwt(HttpClient client, string refreshToken, long steamId, CancellationToken cancellationToken = default)
{
var req = new GenerateAccessTokenForApp_Request
{
@@ -40,17 +39,17 @@ public static class SteamMobileApi
try
{
var resp = await client.PostProto<GenerateAccessTokenForApp_Response>(GENERATE_ACCESS_TOKEN, req);
var resp = await client.PostProto<GenerateAccessTokenForApp_Response>(GENERATE_ACCESS_TOKEN, req, cancellationToken: cancellationToken);
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");
throw new SessionPermanentlyExpiredException("RefreshToken is not accepted by Steam. You must login again and use new token");
}
}
public static async Task<bool> HasPhoneAttached(HttpClient client, string sessionId)
public static async Task<bool> HasPhoneAttached(HttpClient client, string sessionId, CancellationToken cancellationToken = default)
{
var data = new Dictionary<string, string>
{
@@ -60,14 +59,18 @@ public static class SteamMobileApi
};
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>();
var resp = await client.PostAsync(SteamConstants.STEAM_COMMUNITY + "steamguard/phoneajax", content, cancellationToken);
var respContent = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
return SteamLibErrorMonitor.HandleResponse(respContent, () =>
{
var json = JObject.Parse(respContent);
return json["has_phone"]!.Value<bool>();
});
}
public static async Task<RemoveAuthenticator_Response> RemoveAuthenticator(HttpClient client, string accessToken, string rCode)
public static async Task<RemoveAuthenticator_Response> RemoveAuthenticator(HttpClient client, string accessToken, string rCode, CancellationToken cancellationToken = default)
{
var req = SteamConstants.STEAM_API + "ITwoFactorService/RemoveAuthenticator/v1?access_token=" + accessToken;
var reqData = new RemoveAuthenticator_Request
@@ -78,12 +81,15 @@ public static class SteamMobileApi
};
try
{
return await client.PostProto<RemoveAuthenticator_Response>(req, reqData);
return await client.PostProto<RemoveAuthenticator_Response>(req, reqData, cancellationToken: cancellationToken);
}
catch (HttpRequestException ex)
when (ex.StatusCode is HttpStatusCode.Unauthorized)
{
throw new SessionExpiredException("Session expired");
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG);
}
}
}
@@ -0,0 +1,55 @@
using SteamLib.Core;
using SteamLib.Exceptions;
using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Services;
using System.Net;
namespace SteamLib.Api.Mobile;
public static class SteamMobileAuthenticatorApi
{
public static async Task<GetAuthSessionsForAccount_Response> GetAuthSessionsForAccount(HttpClient client, string accessToken, CancellationToken cancellationToken = default)
{
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" + accessToken;
try
{
return await client.GetProto<GetAuthSessionsForAccount_Response>(req, new EmptyMessage(), cancellationToken: cancellationToken);
}
catch (HttpRequestException ex)
when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
}
}
public static async Task<GetAuthSessionInfo_Response> GetAuthSessionInfo(HttpClient client, string accessToken, ulong clientId, CancellationToken cancellationToken = default)
{
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionInfo/v1?access_token=" + accessToken;
var reqData = new GetAuthSessionInfo_Request
{
ClientId = clientId
};
try
{
return await client.PostProto<GetAuthSessionInfo_Response>(req, reqData, cancellationToken: cancellationToken);
}
catch (HttpRequestException ex)
when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
}
}
public static async Task<bool> UpdateAuthSessionStatus(HttpClient client, string accessToken, string sharedSecret,
UpdateAuthSessionWithMobileConfirmation_Request request)
{
var req = SteamConstants.STEAM_API + "IAuthenticationService/UpdateAuthSessionWithMobileConfirmation/v1?access_token=" + accessToken;
request.ComputeSignature(sharedSecret);
await client.PostProtoEnsureSuccess(req, request);
return true;
}
}
@@ -1,42 +1,56 @@
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 AchiesUtilities.Web.Extensions;
using JetBrains.Annotations;
using SteamLib.Account;
using SteamLib.Core;
using SteamLib.Core.StatusCodes;
using SteamLib.Exceptions;
using SteamLib.Exceptions.General;
using SteamLib.SteamMobile;
using SteamLib.SteamMobile.Confirmations;
using SteamLib.Utility;
using SteamLib.Web.Scrappers.JSON;
using System.Net;
namespace SteamLib.Api.Mobile;
[PublicAPI]
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)
public static async Task<IEnumerable<Confirmation>> GetConfirmations(HttpClient client, MobileData data, SteamId steamId, CancellationToken cancellationToken = default)
{
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();
var resp = await client.SendAsync(reqMsg, cancellationToken);
var respStr = await resp.Content.ReadAsStringAsync(cancellationToken);
if (resp.StatusCode == HttpStatusCode.Redirect)
{
throw new SessionExpiredException("Mobile session expired");
throw new SessionPermanentlyExpiredException("Mobile session expired");
}
resp.EnsureSuccessStatusCode();
try
{
return MobileConfirmationScrapper.Scrap(respStr);
}
catch (Exception ex)
when (ex is not SessionPermanentlyExpiredException)
{
var e = new UnsupportedResponseException(respStr, ex);
SteamLibErrorMonitor.LogErrorResponse(respStr, e);
throw e;
return HealthMonitor.LogOnException(respStr, MobileConfirmationScrapper.Scrap, typeof(SessionExpiredException));
}
}
public static async Task<bool> SendConfirmation(HttpClient client, Confirmation confirmation, long steamId, MobileData data, bool confirm)
public static async Task<bool> SendConfirmation(HttpClient client, Confirmation confirmation, SteamId steamId, MobileData data, bool confirm, CancellationToken cancellationToken = default)
{
var op = confirm ? "allow" : "cancel";
@@ -50,8 +64,8 @@ public static class SteamMobileConfirmationsApi
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 _));
var resp = await client.GetStringAsync(req, cancellationToken);
var successCode = SteamLibErrorMonitor.HandleResponse(resp, () => SteamStatusCode.Translate<SteamStatusCode>(resp, out _));
return successCode.Equals(SteamStatusCode.Ok);
@@ -59,7 +73,7 @@ public static class SteamMobileConfirmationsApi
public static async Task<bool> SendMultipleConfirmations(HttpClient client, IEnumerable<Confirmation> confirmations,
long steamId, MobileData data, bool confirm)
SteamId steamId, MobileData data, bool confirm, CancellationToken cancellationToken = default)
{
var op = confirm ? "allow" : "cancel";
var query = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, op).ToList();
@@ -72,25 +86,25 @@ public static class SteamMobileConfirmationsApi
}
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 _));
var resp = await client.PostAsync(MULTI_CONF_OP, content, cancellationToken);
var respStr = await resp.Content.ReadAsStringAsync(cancellationToken);
var successCode = SteamLibErrorMonitor.HandleResponse(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")
internal static IEnumerable<KeyValuePair<string, string>> GetConfirmationKvp(SteamId 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)
};
return
[
KeyValuePair.Create("p", deviceId),
KeyValuePair.Create("a", steamId.Steam64.ToString()),
KeyValuePair.Create("k", hash),
KeyValuePair.Create("t", time.ToString()),
KeyValuePair.Create("m", "react"),
KeyValuePair.Create("tag", tag)
];
}
}
@@ -210,6 +210,14 @@ public static class AdmissionHelper
.Value;
}
public static void ClearAllCookies(this CookieContainer container)
{
foreach (Cookie allCookie in container.GetAllCookies())
{
allCookie.Expired = true;
}
}
#endregion
@@ -42,8 +42,8 @@ public class LoginV2Executor
/// <summary>
/// Do login on <see href="https://steamcommunity.com/">SteamCommunity</see>.<br/>
/// Some functions requires proper SessionId. But <see cref="SessionData"/> contains only SteamCommunity related SessionId and some functions on other services may not work
/// Do log in on <see href="https://steamcommunity.com/">SteamCommunity</see>.<br/>
/// Some functions require proper SessionId. But <see cref="SessionData"/> contains only SteamCommunity related SessionId and some functions on other services may not work
/// </summary>
/// <param name="options"></param>
/// <param name="username"></param>
@@ -163,7 +163,7 @@ public class LoginV2Executor
var finalizeContent = await finalize.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
var finalizeResp =
HealthMonitor.LogOnException(finalizeContent, () => JsonConvert.DeserializeObject<FinalizeLoginJson>(finalizeContent)!);
SteamLibErrorMonitor.HandleResponse(finalizeContent, () => JsonConvert.DeserializeObject<FinalizeLoginJson>(finalizeContent)!);
List<SteamAuthToken> tokens = new();
+118 -94
View File
@@ -1,149 +1,173 @@
using Microsoft.Extensions.Logging;
using SteamLib.Exceptions.General;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ExceptionServices;
namespace SteamLib.Core;
public static class HealthMonitor
/// <summary>
/// Provides unified error handling, logging, and mapping for responses from Steam.
/// This class is used to log and analyze errors that occur due to server changes
/// or code issues, ensuring consistency in logging and exception handling.
/// </summary>
public static class SteamLibErrorMonitor
{
public static ILogger? FatalLogger { get; set; }
internal static void LogUnexpected(string response, Exception ex)
/// <summary>
/// Logger for capturing and analyzing error-related issues.
/// </summary>
public static ILogger? MonitorLogger { get; set; }
/// <summary>
/// Logs the response and the associated exception using the <see cref="MonitorLogger"/>.
/// Differentiates between <see cref="UnsupportedResponseException"/> and other exceptions.
/// </summary>
/// <param name="response">The response string to log.</param>
/// <param name="ex">The exception that occurred.</param>
internal static void LogErrorResponse(string response, Exception ex)
{
FatalLogger?.LogCritical(ex, "Unexpected response detected:\n{response}", response);
if (ex is UnsupportedResponseException)
{
MonitorLogger?.LogError(ex, "Unsupported response detected");
}
else
{
MonitorLogger?.LogError(ex, "Unsupported response detected: {response}", response);
}
}
/// <summary>
/// Maps the error to an <see cref="UnsupportedResponseException"/>, logs it, and then throws the exception.
/// This method is used to handle unexpected responses and map them to a known exception type.
/// </summary>
/// <param name="response">The response that triggered the error.</param>
/// <param name="ex">The original exception.</param>
/// <exception cref="UnsupportedResponseException">Always throws after logging the response.</exception>
[DoesNotReturn]
internal static void MapAndThrowException(string response, Exception ex)
{
var e = new UnsupportedResponseException(response, ex);
ExceptionDispatchInfo.SetCurrentStackTrace(e);
LogErrorResponse(response, e);
throw e;
}
internal static T LogOnException<T>(string resp, Func<T> del)
[DoesNotReturn]
internal static T MapAndThrowException<T>(string response, Exception ex)
{
var e = new UnsupportedResponseException(response, ex);
ExceptionDispatchInfo.SetCurrentStackTrace(e);
LogErrorResponse(response, e);
throw e;
}
/// <summary>
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
/// </summary>
/// <typeparam name="T">The return type of the delegate.</typeparam>
/// <param name="resp">The response string for logging purposes.</param>
/// <param name="del">The delegate to execute.</param>
/// <returns>The result of the delegate execution.</returns>
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
internal static T HandleResponse<T>(string resp, Func<T> del)
{
try
{
return del();
}
catch (UnsupportedResponseException ex)
{
LogErrorResponse(resp, ex);
throw;
}
catch (Exception ex)
{
LogUnexpected(resp, ex);
throw;
MapAndThrowException(resp, ex);
return default; //Not reachable
}
}
internal static T LogOnException<T>(string resp, Func<string, T> del)
/// <summary>
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
/// </summary>
/// <typeparam name="T">The return type of the delegate.</typeparam>
/// <param name="resp">The response string for logging purposes.</param>
/// <param name="del">The delegate to execute.</param>
/// <returns>The result of the delegate execution.</returns>
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
internal static T HandleResponse<T>(string resp, Func<string, T> del)
{
try
{
return del(resp);
}
catch (Exception ex)
catch (UnsupportedResponseException ex)
{
LogUnexpected(resp, ex);
LogErrorResponse(resp, ex);
throw;
}
}
internal static T LogOnException<T>(string resp, Func<T> del, params Type[] exceptExceptions)
{
try
{
return del();
}
catch (Exception ex)
when (exceptExceptions.Any(t => t == ex.GetType()) == false)
{
LogUnexpected(resp, ex);
throw;
MapAndThrowException(resp, ex);
return default; //Not reachable
}
}
internal static T LogOnException<T>(string resp, Func<string, T> del, params Type[] exceptExceptions)
{
try
{
return del(resp);
}
catch (Exception ex)
when (exceptExceptions.Any(t => t == ex.GetType()) == false)
{
LogUnexpected(resp, ex);
throw;
}
}
internal static T LogOnException<T>(string resp, Func<T> del, Func<Exception, bool> logPredicate)
{
try
{
return del();
}
catch (Exception ex)
when (logPredicate(ex))
{
LogUnexpected(resp, ex);
throw;
}
}
internal static T LogOnException<T>(string resp, Func<string, T> del, Func<Exception, bool> logPredicate)
{
try
{
return del(resp);
}
catch (Exception ex)
when (logPredicate(ex))
{
LogUnexpected(resp, ex);
throw;
}
}
internal static void LogOnException(string resp, Action del)
/// <summary>
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
/// </summary>
/// <param name="resp">The response string for logging purposes.</param>
/// <param name="del">The delegate to execute.</param>
/// <returns>The result of the delegate execution.</returns>
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
internal static void HandleResponse(string resp, Action del)
{
try
{
del();
}
catch (Exception ex)
catch (UnsupportedResponseException ex)
{
LogUnexpected(resp, ex);
LogErrorResponse(resp, ex);
throw;
}
}
internal static void LogOnException(string resp, Action del, Func<Exception, bool> logPredicate)
{
try
{
del();
}
catch (Exception ex)
when (logPredicate(ex))
{
LogUnexpected(resp, ex);
throw;
MapAndThrowException(resp, ex);
}
}
internal static async Task<T> LogOnExceptionAsync<T>(string resp, Func<Task<T>> del)
/// <summary>
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
/// </summary>
/// <param name="resp">The response string for logging purposes.</param>
/// <param name="del">The delegate to execute.</param>
/// <returns>The result of the delegate execution.</returns>
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
internal static void HandleResponse(string resp, Action<string> del)
{
try
{
return await del();
del(resp);
}
catch (UnsupportedResponseException ex)
{
LogErrorResponse(resp, ex);
throw;
}
catch (Exception ex)
{
LogUnexpected(resp, ex);
throw;
MapAndThrowException(resp, ex);
}
}
internal static async Task<T> LogOnExceptionAsync<T>(string resp, Func<Task<T>> del, params Type[] exceptExceptions)
{
try
{
return await del();
}
catch (Exception ex)
when (exceptExceptions.Any(t => t == ex.GetType()) == false)
{
LogUnexpected(resp, ex);
throw;
}
}
}
@@ -1,6 +1,6 @@
using SteamLib.Exceptions;
using AchiesUtilities.Models;
using SteamLib.Exceptions;
using SteamLib.Utility;
using SteamLib.Utility.Models;
using System.Collections.ObjectModel;
namespace SteamLib.Core.StatusCodes;
@@ -11,7 +11,6 @@ public static class SteamConstants
public const string STEAM_TV = "https://steam.tv/";
public const string STEAM_CHECKOUT = "https://checkout.steampowered.com/";
public const string LOGIN_STEAM = "https://login.steampowered.com/";
#endregion
@@ -22,15 +21,9 @@ public static class SteamConstants
#endregion
#region Misc
public const string ENGLISH = "english";
#endregion
}
@@ -7,9 +7,6 @@ public class CantLoadConfirmationsException : Exception
public CantLoadConfirmationsException() { }
public CantLoadConfirmationsException(string message) : base(message) { }
public CantLoadConfirmationsException(string message, Exception inner) : base(message, inner) { }
protected CantLoadConfirmationsException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
@@ -1,6 +1,4 @@
using System.Runtime.Serialization;
namespace SteamLib.Exceptions.Mobile;
namespace SteamLib.Exceptions.Mobile;
[Serializable]
public class BadMobileCookiesException : Exception
@@ -12,7 +10,7 @@ public class BadMobileCookiesException : Exception
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
//
public BadMobileCookiesException() : base("You are using deafult HttpClient without mobile specific cookies. Login can't be proceeded with these cookies")
public BadMobileCookiesException() : base("You are using default HttpClient without mobile specific cookies. Login can't be proceeded with these cookies")
{
}
@@ -23,10 +21,4 @@ public class BadMobileCookiesException : Exception
public BadMobileCookiesException(string message, Exception inner) : base(message, inner)
{
}
protected BadMobileCookiesException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
@@ -1,11 +1,13 @@
namespace SteamLib.Exceptions;
public class SessionExpiredException : SessionInvalidException
/// <summary>
/// Unlike <see cref="SessionInvalidException"/>, this exception indicates a definite session expiration. Refreshing the JWT token will not help.
/// </summary>
public class SessionPermanentlyExpiredException : SessionInvalidException
{
public const string SESSION_EXPIRED_MSG = "Session expired and won't longer work. You must login to get new session";
public SessionExpiredException() { }
public SessionExpiredException(string message) : base(message) { }
public SessionExpiredException(string message, Exception inner) : base(message, inner) { }
protected SessionExpiredException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
public SessionPermanentlyExpiredException() { }
public SessionPermanentlyExpiredException(string message) : base(message) { }
public SessionPermanentlyExpiredException(string message, Exception inner) : base(message, inner) { }
}
@@ -2,11 +2,9 @@
public class SessionInvalidException : Exception
{
public const string SESSION_NULL_MSG = "Session was null. Before acting SteamClient must be logged in";
public const string SESSION_NULL_MSG = "Session is null. SteamClient must be logged in before proceeding.";
public const string GOT_401_MSG = "Steam indicates the session is invalid. Received a 401 Unauthorized response.";
public SessionInvalidException() { }
public SessionInvalidException(string message) : base(message) { }
public SessionInvalidException(string message, Exception? inner) : base(message, inner) { }
protected SessionInvalidException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
public SessionInvalidException(string message) : base(message) { }
public SessionInvalidException(string message, Exception? inner) : base(message, inner) { }
}
@@ -10,6 +10,7 @@ public class MobileData
[JsonRequired] public string SharedSecret { get; set; } = null!;
[JsonRequired] public string IdentitySecret { get; set; } = null!;
[JsonRequired] public string DeviceId { get; set; } = null!;
//TODO: This property used only for tracing purposes in Steam, so if it's not provided, we can generate it manually
}
+7 -1
View File
@@ -1,5 +1,11 @@
namespace SteamLib.ProtoCore.Enums;
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
using JetBrains.Annotations;
namespace SteamLib.ProtoCore.Enums;
[PublicAPI]
public enum EResult
{
Invalid = 0,
@@ -21,8 +21,4 @@ public class EResultException : Exception
{
Result = result;
}
protected EResultException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
@@ -1,22 +1,15 @@
using System.Runtime.Serialization;
namespace SteamLib.ProtoCore.Exceptions;
namespace SteamLib.ProtoCore.Exceptions;
[Serializable]
public class UnknownEResultException : Exception
{
public int EResult { get; }
public UnknownEResultException()
{}
{ }
public UnknownEResultException(int eResult) : base("Got unknown EResult: " + eResult)
{
EResult = eResult;
}
protected UnknownEResultException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
@@ -217,7 +217,7 @@ public class GenerateAccessTokenForApp_Request : IProtoMsg
[ProtoMember(2, DataFormat = DataFormat.FixedSize)]
public long SteamId { get; set; }
[ProtoMember(3)] public bool TokenRenewalType { get; set; } = true; //enum: ETokenRenewalType
[ProtoMember(3)] public bool TokenRenewalType { get; set; } = true; //FIXME: enum: ETokenRenewalType
}
[ProtoContract]
+6 -6
View File
@@ -7,15 +7,15 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AchiesUtilities.Newtonsoft.JSON" Version="1.2.1" />
<PackageReference Include="AchiesUtilities.Web" Version="1.0.11" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.58" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="AchiesUtilities.Newtonsoft.JSON" Version="1.2.11" />
<PackageReference Include="AchiesUtilities.Web" Version="1.0.14" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="protobuf-net.Core" Version="3.2.30" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />
</ItemGroup>
</Project>
@@ -40,7 +40,7 @@ public class SteamAuthenticatorLinker
if (data.RefreshToken.IsExpired)
{
Logger?.LogError("Session expired");
throw new SessionExpiredException(SessionExpiredException.SESSION_EXPIRED_MSG);
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG);
}
var accessToken = data.GetMobileToken();
@@ -11,6 +11,5 @@ public class TradeConfirmation : Confirmation
public TradeConfirmation(long id, ulong nonce, long creator, string typeName) : base(id, nonce, 1, creator, ConfirmationType.Trade, typeName)
{
}
}
+4 -7
View File
@@ -16,7 +16,7 @@ public static class EncryptionHelper
public static string ToBase64EncryptedPassword(string keyExp, string keyMod, string password)
{
// RSA Encryption.
RSACryptoServiceProvider rsa = new();
using RSACryptoServiceProvider rsa = new();
RSAParameters rsaParameters = new()
{
Exponent = HexStringToByteArray(keyExp),
@@ -34,8 +34,7 @@ public static class EncryptionHelper
{
var hashedBytes = GenerateConfirmationHashBytes(time, identitySecret, tag);
var encodedData = Convert.ToBase64String(hashedBytes, Base64FormattingOptions.None);
var hash = encodedData;
return hash;
return encodedData;
}
public static byte[] GenerateConfirmationHashBytes(long time, string identitySecret, string tag = "conf")
{
@@ -64,10 +63,8 @@ public static class EncryptionHelper
}
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, array, 8, n2 - 8);
var hmacGenerator = new HMACSHA1
{
Key = decode
};
using var hmacGenerator = new HMACSHA1();
hmacGenerator.Key = decode;
var hashedData = hmacGenerator.ComputeHash(array);
return hashedData;
}
@@ -16,7 +16,7 @@ public class DeserializedMafileData
public int? Version { get; init; }
public bool IsExtended { get; init; }
public DeserializedMafileSessionResult SessionResult { get; init; }
public Dictionary<string, JProperty>? UnusedProperties { get; init; } = null;
public Dictionary<string, JProperty>? UnusedProperties { get; init; }
public HashSet<string>? MissingProperties { get; init; } = new();
@@ -1,6 +1,9 @@
using Newtonsoft.Json;
using SteamLib.Web.Converters;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
//TODO: Fix pragma warning
namespace SteamLib.Utility.MaFiles;
internal class LegacyMafile //TODO: move
@@ -55,7 +55,7 @@ public static partial class MafileSerializer
var sharedSecretToken = GetTokenOrThrow(j, nameof(MobileData.SharedSecret), unusedProperties, "sharedsecret", "shared_secret", "shared");
var identitySecretToken = GetTokenOrThrow(j, nameof(MobileData.IdentitySecret), unusedProperties, "identitysecret", "identity_secret", "identity");
var deviceIdToken = GetTokenOrThrow(j, nameof(MobileData.DeviceId), unusedProperties, "deviceid", "device_id", "device");
//TODO: see MobileData.DeviceId ToDo
var sharedSecret = GetBase64(nameof(MobileData.SharedSecret), sharedSecretToken);
var identitySecret = GetBase64(nameof(MobileData.IdentitySecret), identitySecretToken);
@@ -1,44 +0,0 @@
using System.Reflection;
namespace SteamLib.Utility.Models;
public abstract class Enumeration(int id, string name) : IComparable
{
public string Name { get; } = name;
public int Id { get; } = id;
public override string ToString() => Name;
//public static IEnumerable<T> GetAll<T>() where T : Enumeration =>
// typeof(T).GetFields(BindingFlags.Public |
// BindingFlags.Static |
// BindingFlags.DeclaredOnly)
// .Select(f => f.GetValue(null))
// .Cast<T>();
public static IEnumerable<T> GetAll<T>() where T : Enumeration
{
var res = typeof(T).GetFields(BindingFlags.Public |
BindingFlags.Static |
BindingFlags.DeclaredOnly)
.Select(f => f.GetValue(null))
.Cast<T>();
return res;
}
public override bool Equals(object? obj)
{
if (obj is not Enumeration otherValue)
{
return false;
}
return GetType() == obj.GetType() && Id.Equals(otherValue.Id);
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Id, GetType());
}
public int CompareTo(object? other) => Id.CompareTo(((Enumeration?)other)?.Id);
}

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