diff --git a/NebulaAuth.LegacyConverter/EncryptedManifest.cs b/NebulaAuth.LegacyConverter/EncryptedManifest.cs new file mode 100644 index 0000000..49591b1 --- /dev/null +++ b/NebulaAuth.LegacyConverter/EncryptedManifest.cs @@ -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; } +} \ No newline at end of file diff --git a/NebulaAuth.LegacyConverter/Program.cs b/NebulaAuth.LegacyConverter/Program.cs index a1076ad..c45f9e0 100644 --- a/NebulaAuth.LegacyConverter/Program.cs +++ b/NebulaAuth.LegacyConverter/Program.cs @@ -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 toStoreFolder = "ConvertedMafiles"; - if (Path.Exists(path) == false) + if (Directory.Exists(toStoreFolder) == false) + { + Directory.CreateDirectory(toStoreFolder); + } + else { - Console.WriteLine($"NOT VALID PATH: '{path}'"); - continue; + var isEmpty = Directory.GetFiles(toStoreFolder).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) + { + } + } } - Console.WriteLine("Reading: " + path); - try + + var decryptMode = false; + while (true) { - var text = File.ReadAllText(path); - var maf = MafileSerializer.Deserialize(text, out _); - var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented); - var name = Path.GetFileNameWithoutExtension(path); - Write(legacy, name); + Console.WriteLine("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); + break; } - catch (Exception ex) + + + Manifest? manifest = null; + string? password = null; + if (decryptMode) { - Console.WriteLine($"ERROR: {ex.Message}"); - Console.WriteLine(ex); + 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(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; + } } - finally + + + Console.WriteLine(currentPath); + + + foreach (var path in args) { - Console.WriteLine("-----------------------------------------"); + + 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, true, 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) + { -Console.WriteLine("Press any key to exit..."); -Console.ReadKey(); + var path = Path.Combine(toStoreFolder, 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); -void Write(string maf, string name) -{ - var path = Path.Combine(name + "_legacy.mafile"); - File.WriteAllText(path, maf); + } +} +finally +{ + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); } \ No newline at end of file diff --git a/NebulaAuth.LegacyConverter/SDADecryptor.cs b/NebulaAuth.LegacyConverter/SDADecryptor.cs new file mode 100644 index 0000000..fd2f402 --- /dev/null +++ b/NebulaAuth.LegacyConverter/SDADecryptor.cs @@ -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 + + + +/// +/// 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. +/// +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; + + /// + /// Returns an 8-byte cryptographically random salt in base64 encoding + /// + /// + public static string GetRandomSalt() + { + byte[] salt = new byte[SALT_LENGTH]; + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(salt); + } + return Convert.ToBase64String(salt); + } + + /// + /// Returns a 16-byte cryptographically random initialization vector (IV) in base64 encoding + /// + /// + public static string GetInitializationVector() + { + byte[] IV = new byte[IV_LENGTH]; + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(IV); + } + return Convert.ToBase64String(IV); + } + + + /// + /// Generates an encryption key derived using a password, a random salt, and specified number of rounds of PBKDF2 + /// + /// TODO: pass in password via SecureString? + /// + /// + /// + /// + 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); + } + } + + /// + /// 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. + /// + /// + /// + /// Initialization Vector + /// + /// + 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; + } + + /// + /// 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 + /// + /// + /// + /// + /// + /// + 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); + } +} diff --git a/NebulaAuth.sln b/NebulaAuth.sln index bb80a76..3b17041 100644 --- a/NebulaAuth.sln +++ b/NebulaAuth.sln @@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{ changelog\1.5.0.html = changelog\1.5.0.html changelog\1.5.1.html = changelog\1.5.1.html changelog\1.5.2.html = changelog\1.5.2.html + changelog\1.5.3.html = changelog\1.5.3.html EndProjectSection EndProject Global diff --git a/NebulaAuth.sln.DotSettings b/NebulaAuth.sln.DotSettings index 5b88222..e2bba46 100644 --- a/NebulaAuth.sln.DotSettings +++ b/NebulaAuth.sln.DotSettings @@ -1,2 +1,3 @@  - DO_NOT_SHOW \ No newline at end of file + DO_NOT_SHOW + True \ No newline at end of file diff --git a/NebulaAuth/App.xaml b/NebulaAuth/App.xaml index bb7f207..cd88f9d 100644 --- a/NebulaAuth/App.xaml +++ b/NebulaAuth/App.xaml @@ -1,9 +1,7 @@ pack://application:,,,/Fonts/Roboto/#Roboto Symbols - - + @@ -34,7 +31,6 @@ - diff --git a/NebulaAuth/App.xaml.cs b/NebulaAuth/App.xaml.cs index 55f0d69..f80f81e 100644 --- a/NebulaAuth/App.xaml.cs +++ b/NebulaAuth/App.xaml.cs @@ -3,12 +3,11 @@ using NebulaAuth.Model.Exceptions; using System; using System.Windows; -using CodingSeb.Localization; namespace NebulaAuth; -public partial class App : Application +public partial class App { protected override void OnStartup(StartupEventArgs e) { @@ -25,11 +24,12 @@ protected override void OnStartup(StartupEventArgs e) var msg = ex.ToString(); if (ex is CantAlignTimeException) { - msg = Loc.Tr(LocManager.GetCodeBehind("CantAlignTimeError")); + msg = LocManager.Get("CantAlignTimeError"); } - MessageBox.Show(msg); + MessageBox.Show(msg, "Error", MessageBoxButton.OK, MessageBoxImage.Stop, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly); throw; + } } } diff --git a/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs b/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs index 9d20158..04dcf5c 100644 --- a/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs +++ b/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs @@ -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,12 +19,12 @@ public object Convert(object value, Type targetType, object parameter, CultureIn 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(); + throw new NotSupportedException(); } } \ No newline at end of file diff --git a/NebulaAuth/Converters/Background/BackgroundImageVisibleConverter.cs b/NebulaAuth/Converters/Background/BackgroundImageVisibleConverter.cs index d886d19..3a92e03 100644 --- a/NebulaAuth/Converters/Background/BackgroundImageVisibleConverter.cs +++ b/NebulaAuth/Converters/Background/BackgroundImageVisibleConverter.cs @@ -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(); } } \ No newline at end of file diff --git a/NebulaAuth/Converters/Background/BackgroundSourceConverter.cs b/NebulaAuth/Converters/Background/BackgroundSourceConverter.cs index b66349a..d4399a2 100644 --- a/NebulaAuth/Converters/Background/BackgroundSourceConverter.cs +++ b/NebulaAuth/Converters/Background/BackgroundSourceConverter.cs @@ -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,8 +21,8 @@ public object Convert(object value, Type targetType, object parameter, CultureIn 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(); + throw new NotSupportedException(); } } \ No newline at end of file diff --git a/NebulaAuth/Converters/CoefficientConverter.cs b/NebulaAuth/Converters/CoefficientConverter.cs index e79ddc8..77c2d12 100644 --- a/NebulaAuth/Converters/CoefficientConverter.cs +++ b/NebulaAuth/Converters/CoefficientConverter.cs @@ -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; } } \ No newline at end of file diff --git a/NebulaAuth/Converters/MultiCommandParamaterConverter.cs b/NebulaAuth/Converters/MultiCommandParamaterConverter.cs index d228e06..673ec43 100644 --- a/NebulaAuth/Converters/MultiCommandParamaterConverter.cs +++ b/NebulaAuth/Converters/MultiCommandParamaterConverter.cs @@ -13,6 +13,6 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { - throw new NotImplementedException(); + throw new NotSupportedException(); } } \ No newline at end of file diff --git a/NebulaAuth/Converters/OnOffBoolTextConverter.cs b/NebulaAuth/Converters/OnOffBoolTextConverter.cs deleted file mode 100644 index 73e4b47..0000000 --- a/NebulaAuth/Converters/OnOffBoolTextConverter.cs +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs b/NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs new file mode 100644 index 0000000..fdd0516 --- /dev/null +++ b/NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs @@ -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(); + } +} \ No newline at end of file diff --git a/NebulaAuth/Converters/ProxyTextConverter.cs b/NebulaAuth/Converters/ProxyTextConverter.cs index a3806fb..2dab8f2 100644 --- a/NebulaAuth/Converters/ProxyTextConverter.cs +++ b/NebulaAuth/Converters/ProxyTextConverter.cs @@ -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 object Convert(object value, Type targetType, object parameter, CultureIn 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 object Convert(object value, Type targetType, object parameter, CultureIn 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(); } } \ No newline at end of file diff --git a/NebulaAuth/Converters/ReverseBooleanConverter.cs b/NebulaAuth/Converters/ReverseBooleanConverter.cs index 020a17b..0f40c2c 100644 --- a/NebulaAuth/Converters/ReverseBooleanConverter.cs +++ b/NebulaAuth/Converters/ReverseBooleanConverter.cs @@ -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)); } -} \ No newline at end of file +} diff --git a/NebulaAuth/Converters/SelectedProxyTextConverter.cs b/NebulaAuth/Converters/SelectedProxyTextConverter.cs deleted file mode 100644 index 5885ce8..0000000 --- a/NebulaAuth/Converters/SelectedProxyTextConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; -using NebulaAuth.Model.Entities; - -namespace NebulaAuth.Converters; - -public class SelectedProxyTextConverter : IMultiValueConverter -{ - - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) - { - if (values[0] is not MaProxy proxy) - { - return string.Empty; - } - - var proxyExist = (bool)values[1]; - return proxyExist ? $"{proxy.Id}: {proxy.Data.Address}" : ""; - } - - public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/NebulaAuth/Converters/ValueConverterGroup.cs b/NebulaAuth/Converters/ValueConverterGroup.cs index 4d7b5fd..ebd749f 100644 --- a/NebulaAuth/Converters/ValueConverterGroup.cs +++ b/NebulaAuth/Converters/ValueConverterGroup.cs @@ -9,14 +9,14 @@ public class ValueConverterGroup : List, 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(); + throw new NotSupportedException(); } #endregion diff --git a/NebulaAuth/Core/LocalizationManager.cs b/NebulaAuth/Core/LocalizationManager.cs index 7b30221..a111a09 100644 --- a/NebulaAuth/Core/LocalizationManager.cs +++ b/NebulaAuth/Core/LocalizationManager.cs @@ -18,6 +18,10 @@ public static void SetApplicationLocalization(LocalizationLanguage language) //Thread.CurrentThread.CurrentCulture= CultureInfo.GetCultureInfo(GetLanguageCode(language)); } + public static string GetCurrentLanguageCode() + { + return Loc.Instance.CurrentLanguage; + } public static string GetLanguageCode(LocalizationLanguage language) { diff --git a/NebulaAuth/Core/SnackbarController.cs b/NebulaAuth/Core/SnackbarController.cs index fb3ac05..2d6b142 100644 --- a/NebulaAuth/Core/SnackbarController.cs +++ b/NebulaAuth/Core/SnackbarController.cs @@ -24,7 +24,9 @@ public static void SendSnackbar(string text, TimeSpan? duration = null) /// /// /// + /// /// Default duration is 1 second + /// Default: 'OK' public static void SendSnackbarWithButton(string text, string actionText = "OK", Action? action = null, TimeSpan? duration = null) { duration ??= GetSnackbarTime(text); diff --git a/NebulaAuth/Core/TrayManager.cs b/NebulaAuth/Core/TrayManager.cs index d7ba53b..aa57e35 100644 --- a/NebulaAuth/Core/TrayManager.cs +++ b/NebulaAuth/Core/TrayManager.cs @@ -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 void InitializeTray() _notifyIcon.ContextMenuStrip = contextMenu; MainWindow.StateChanged += MainWindow_StateChanged; + Init = true; } private static void OnExitClick(object? sender, EventArgs e) @@ -57,6 +60,7 @@ private static void MainWindow_StateChanged(object? sender, EventArgs e) private static void ShowMainWindow() { + if (!Init) return; MainWindow.Show(); MainWindow.WindowState = WindowState.Normal; _notifyIcon.Visible = false; @@ -64,13 +68,15 @@ private static void ShowMainWindow() 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(); } diff --git a/NebulaAuth/MainWindow.xaml b/NebulaAuth/MainWindow.xaml index 451a6ff..fac5855 100644 --- a/NebulaAuth/MainWindow.xaml +++ b/NebulaAuth/MainWindow.xaml @@ -25,7 +25,7 @@ - @@ -44,27 +44,27 @@ - + - - + + - + - + - - + + @@ -85,7 +85,15 @@ - + @@ -118,10 +126,29 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -134,15 +161,52 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - + @@ -43,21 +43,21 @@ - - + + - + @@ -76,12 +76,12 @@ @@ -98,14 +98,14 @@ - + - + - + @@ -118,12 +118,12 @@ @@ -139,15 +139,15 @@ - + @@ -162,7 +162,7 @@ - + @@ -171,12 +171,12 @@ diff --git a/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml b/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml index 85b364b..560da51 100644 --- a/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml +++ b/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml @@ -6,7 +6,6 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:theme="clr-namespace:NebulaAuth.Theme" mc:Ignorable="d" - d:DesignHeight="100" d:DesignWidth="400" Height="Auto" Width="Auto" MaxWidth="500" Background="{DynamicResource WindowBackground}"> diff --git a/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml.cs b/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml.cs index 9a8999b..631da23 100644 --- a/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml.cs +++ b/NebulaAuth/View/Dialogs/ConfirmCancelDialog.xaml.cs @@ -1,21 +1,18 @@ -using System.Windows.Controls; +namespace NebulaAuth.View.Dialogs; -namespace NebulaAuth.View.Dialogs +/// +/// Логика взаимодействия для ConfirmCancelDialog.xaml +/// +public partial class ConfirmCancelDialog { - /// - /// Логика взаимодействия для ConfirmCancelDialog.xaml - /// - public partial class ConfirmCancelDialog : UserControl + public ConfirmCancelDialog() { - public ConfirmCancelDialog() - { - InitializeComponent(); - } + InitializeComponent(); + } - public ConfirmCancelDialog(string msg) - { - InitializeComponent(); - ConfirmTextBlock.Text = msg; - } + public ConfirmCancelDialog(string msg) + { + InitializeComponent(); + ConfirmTextBlock.Text = msg; } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml b/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml index f404fe5..802a56e 100644 --- a/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml +++ b/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml @@ -7,11 +7,9 @@ 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" - d:DesignHeight="250" d:DesignWidth="800" theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" - Foreground="WhiteSmoke" Cursor="Hand" + Foreground="WhiteSmoke" d:DataContext="{d:DesignInstance other:LoginAgainVM}" Background="{DynamicResource WindowBackground}"> @@ -26,7 +24,7 @@ - + diff --git a/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml.cs b/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml.cs index cf62437..2c8cb00 100644 --- a/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml.cs +++ b/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml.cs @@ -1,15 +1,12 @@ -using System.Windows.Controls; +namespace NebulaAuth.View.Dialogs; -namespace NebulaAuth.View.Dialogs +/// +/// Логика взаимодействия для LoginAgainDialog.xaml +/// +public partial class LoginAgainDialog { - /// - /// Логика взаимодействия для LoginAgainDialog.xaml - /// - public partial class LoginAgainDialog : UserControl + public LoginAgainDialog() { - public LoginAgainDialog() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml b/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml index 0ce08a0..670ae8d 100644 --- a/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml +++ b/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml @@ -9,9 +9,8 @@ xmlns:model="clr-namespace:NebulaAuth.Model" xmlns:entities="clr-namespace:NebulaAuth.Model.Entities" mc:Ignorable="d" - d:DesignHeight="250" d:DesignWidth="800" theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" - Foreground="WhiteSmoke" Cursor="Hand" + Foreground="WhiteSmoke" d:DataContext="{d:DesignInstance other:LoginAgainOnImportVM}" Background="{DynamicResource WindowBackground}"> @@ -28,7 +27,7 @@ - + diff --git a/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml.cs b/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml.cs index ae1bdce..4e189d4 100644 --- a/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml.cs +++ b/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml.cs @@ -1,13 +1,12 @@ -namespace NebulaAuth.View.Dialogs +namespace NebulaAuth.View.Dialogs; + +/// +/// Логика взаимодействия для LoginAgainDialog.xaml +/// +public partial class LoginAgainOnImportDialog { - /// - /// Логика взаимодействия для LoginAgainDialog.xaml - /// - public partial class LoginAgainOnImportDialog + public LoginAgainOnImportDialog() { - public LoginAgainOnImportDialog() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml b/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml index e5c8ab9..57ae647 100644 --- a/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml +++ b/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml @@ -2,14 +2,12 @@ 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" Cursor="Hand" + Foreground="WhiteSmoke" d:DataContext="{d:DesignInstance other:LoginAgainVM}" Background="{DynamicResource WindowBackground}"> @@ -21,7 +19,7 @@ - + diff --git a/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml.cs b/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml.cs index 0ebd27a..02dd5aa 100644 --- a/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml.cs +++ b/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml.cs @@ -1,15 +1,12 @@ -using System.Windows.Controls; +namespace NebulaAuth.View.Dialogs; -namespace NebulaAuth.View.Dialogs +/// +/// Логика взаимодействия для SetCryptPasswordDialog.xaml +/// +public partial class SetCryptPasswordDialog { - /// - /// Логика взаимодействия для SetCryptPasswordDialog.xaml - /// - public partial class SetCryptPasswordDialog : UserControl + public SetCryptPasswordDialog() { - public SetCryptPasswordDialog() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml b/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml index 41f263b..864df13 100644 --- a/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml +++ b/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml @@ -18,8 +18,8 @@ - - + + @@ -27,7 +27,7 @@ - + diff --git a/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml.cs b/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml.cs index 24483c8..156f5f7 100644 --- a/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml.cs +++ b/NebulaAuth/View/Dialogs/WaitLoginDialog.xaml.cs @@ -7,66 +7,65 @@ using SteamLib.Core.Interfaces; using SteamLib.Exceptions; -namespace NebulaAuth.View.Dialogs +namespace NebulaAuth.View.Dialogs; + +/// +/// Логика взаимодействия для WaitLoginDialog.xaml +/// +public partial class WaitLoginDialog : ICaptchaResolver { - /// - /// Логика взаимодействия для WaitLoginDialog.xaml - /// - public partial class WaitLoginDialog : ICaptchaResolver + private TaskCompletionSource _tcs = new(); + public WaitLoginDialog() { - private TaskCompletionSource _tcs = new(); - public WaitLoginDialog() - { - InitializeComponent(); - } + InitializeComponent(); + } - public async Task Resolve(Uri imageUrl, HttpClient client) - { + public async Task Resolve(Uri imageUrl, HttpClient client) + { - CaptchaGrid.Visibility = Visibility.Visible; - var stream = await client.GetStreamAsync(imageUrl); - return await Application.Current.Dispatcher.Invoke(async () => + 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 { - var image = await LoadImage(stream); - CaptchaImage.Source = image; - try - { - return await _tcs.Task; - } - catch (TaskCanceledException) - { - throw new LoginException(LoginError.CaptchaRequired); - } - }); - } + return await _tcs.Task; + } + catch (TaskCanceledException) + { + throw new LoginException(LoginError.CaptchaRequired); + } + }); + } - private async Task LoadImage(Stream stream) - { - using var ms = new MemoryStream(); - await stream.CopyToAsync(ms); - ms.Position = 0; + private async Task LoadImage(Stream stream) + { + using var ms = new MemoryStream(); + await stream.CopyToAsync(ms); + ms.Position = 0; - var image = new BitmapImage(); + var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = ms; - image.EndInit(); - await stream.DisposeAsync(); - return image; - } + 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 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(); - oldTcs.SetResult(CaptchaTB.Text); - } + private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e) + { + if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return; + var oldTcs = _tcs; + _tcs = new TaskCompletionSource(); + oldTcs.SetResult(CaptchaTB.Text); } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/LinkerView.xaml b/NebulaAuth/View/LinkerView.xaml index c6954ba..736df5a 100644 --- a/NebulaAuth/View/LinkerView.xaml +++ b/NebulaAuth/View/LinkerView.xaml @@ -2,12 +2,10 @@ 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" - d:DesignHeight="450" d:DesignWidth="800" Foreground="WhiteSmoke" FontFamily="{materialDesign:MaterialDesignFont}" MinHeight="640" @@ -63,12 +61,19 @@ - + + + + + + + + - + @@ -86,48 +91,58 @@ + + - - + @@ -53,8 +46,8 @@ - @@ -98,7 +91,7 @@ @@ -113,12 +106,12 @@ - + diff --git a/NebulaAuth/View/ProxyManagerView.xaml.cs b/NebulaAuth/View/ProxyManagerView.xaml.cs index 9bbe3c5..955fda2 100644 --- a/NebulaAuth/View/ProxyManagerView.xaml.cs +++ b/NebulaAuth/View/ProxyManagerView.xaml.cs @@ -1,15 +1,12 @@ -using System.Windows.Controls; +namespace NebulaAuth.View; -namespace NebulaAuth.View +/// +/// Логика взаимодействия для ProxyManagerView.xaml +/// +public partial class ProxyManagerView { - /// - /// Логика взаимодействия для ProxyManagerView.xaml - /// - public partial class ProxyManagerView : UserControl + public ProxyManagerView() { - public ProxyManagerView() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/SettingsView.xaml b/NebulaAuth/View/SettingsView.xaml index daa6d2f..e873812 100644 --- a/NebulaAuth/View/SettingsView.xaml +++ b/NebulaAuth/View/SettingsView.xaml @@ -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}"> - - - @@ -35,15 +30,14 @@ - - - + + @@ -58,16 +52,17 @@ + materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" /> - + - - + + + diff --git a/NebulaAuth/View/SettingsView.xaml.cs b/NebulaAuth/View/SettingsView.xaml.cs index 6e12243..baa65f3 100644 --- a/NebulaAuth/View/SettingsView.xaml.cs +++ b/NebulaAuth/View/SettingsView.xaml.cs @@ -1,23 +1,12 @@ -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; +namespace NebulaAuth.View; -namespace NebulaAuth.View +/// +/// Логика взаимодействия для SettingsView.xaml +/// +public partial class SettingsView { - /// - /// Логика взаимодействия для SettingsView.xaml - /// - public partial class SettingsView : UserControl + public SettingsView() { - public SettingsView() - { - InitializeComponent(); - } - - private void ColorPicker_OnColorChanged(object sender, RoutedPropertyChangedEventArgs e) - { - Debug.WriteLine(e.NewValue); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/UpdaterView.xaml b/NebulaAuth/View/UpdaterView.xaml index be17e6b..07b33c0 100644 --- a/NebulaAuth/View/UpdaterView.xaml +++ b/NebulaAuth/View/UpdaterView.xaml @@ -2,14 +2,10 @@ 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" - d:DesignHeight="700" - d:DesignWidth="500" Foreground="WhiteSmoke" FontFamily="{materialDesign:MaterialDesignFont}" d:DataContext="{d:DesignInstance other:UpdaterVM}" @@ -31,7 +27,7 @@ - + diff --git a/NebulaAuth/View/UpdaterView.xaml.cs b/NebulaAuth/View/UpdaterView.xaml.cs index c77a531..5afc6d9 100644 --- a/NebulaAuth/View/UpdaterView.xaml.cs +++ b/NebulaAuth/View/UpdaterView.xaml.cs @@ -1,15 +1,12 @@ -using System.Windows.Controls; +namespace NebulaAuth.View; -namespace NebulaAuth.View +/// +/// Логика взаимодействия для UpdaterView.xaml +/// +public partial class UpdaterView { - /// - /// Логика взаимодействия для UpdaterView.xaml - /// - public partial class UpdaterView : UserControl + public UpdaterView() { - public UpdaterView() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM.cs b/NebulaAuth/ViewModel/MainVM.cs index 1b9efcf..c253df0 100644 --- a/NebulaAuth/ViewModel/MainVM.cs +++ b/NebulaAuth/ViewModel/MainVM.cs @@ -1,19 +1,19 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using JetBrains.Annotations; using MaterialDesignThemes.Wpf; 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; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using NebulaAuth.View.Dialogs; namespace NebulaAuth.ViewModel; @@ -21,8 +21,9 @@ public partial class MainVM : ObservableObject { [ObservableProperty] private ObservableCollection _maFiles = Storage.MaFiles; - public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue; + [UsedImplicitly] + public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue; public Mafile? SelectedMafile @@ -32,39 +33,40 @@ public Mafile? SelectedMafile } 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(ProxyStorage.Proxies.Select(kvp => new MaProxy(kvp.Key, kvp.Value))); Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged; QueryGroups(); - SessionHandler.LoginStarted += SessionHandlerOnLoginStarted; - SessionHandler.LoginCompleted += SessionHandlerOnLoginCompleted; UpdateManager.CheckForUpdates(); 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 +83,14 @@ public async Task LoginAgain() { 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 +120,7 @@ private async Task RefreshSession() try { await MaClient.RefreshSession(SelectedMafile); - SnackbarController.SendSnackbar(GetLocalizationOrDefault("SessionRefreshed")); + SnackbarController.SendSnackbar(GetLocalization("SessionRefreshed")); } catch (Exception ex) when (ExceptionHandler.Handle(ex)) { @@ -129,7 +131,6 @@ private async Task RefreshSession() [RelayCommand] public async Task LinkAccount() { - OffTimer(false); await DialogsController.ShowLinkerDialog(); } @@ -140,16 +141,16 @@ private async Task RemoveAuthenticator() 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 +177,17 @@ private async Task ConfirmLogin() 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); @@ -198,4 +199,11 @@ private async Task ConfirmLogin() Shell.Logger.Error(ex); } } + + + private static string GetLocalization(string key) + { + const string locPath = "MainVM"; + return LocManager.GetCodeBehindOrDefault(key, locPath, key); + } } \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_Code.cs b/NebulaAuth/ViewModel/MainVM_Code.cs index 2e688db..17b964b 100644 --- a/NebulaAuth/ViewModel/MainVM_Code.cs +++ b/NebulaAuth/ViewModel/MainVM_Code.cs @@ -1,8 +1,12 @@ -using System; -using CommunityToolkit.Mvvm.ComponentModel; +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; @@ -12,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))] @@ -21,12 +25,12 @@ private void CreateCodeTimer() _codeTimer = new Timer(UpdateCode, null, 0, 1000); } - + private void UpdateCode(object? state = null) { var currentTime = TimeAligner.GetSteamTime(); - var untilChange = currentTime - currentTime / 30L * 30L; + var untilChange = currentTime % 30; var codeProgress = untilChange / 30D * 100; string? code = null; @@ -37,11 +41,35 @@ private void UpdateCode(object? state = null) if (Application.Current == null) return; - Application.Current.Dispatcher.Invoke((string? c) => + Application.Current.Dispatcher.BeginInvoke((string? c) => { if (Application.Current.MainWindow?.WindowState == WindowState.Minimized) return; CodeProgress = codeProgress; if (c != null) Code = c; - }, DispatcherPriority.Background, code); + }, DispatcherPriority.DataBind, 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); + } + } } \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_Confirmations.cs b/NebulaAuth/ViewModel/MainVM_Confirmations.cs index 041cd0e..56b892a 100644 --- a/NebulaAuth/ViewModel/MainVM_Confirmations.cs +++ b/NebulaAuth/ViewModel/MainVM_Confirmations.cs @@ -40,17 +40,20 @@ public async Task GetConfirmations() _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() + .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()); + var mConf = new MarketMultiConfirmation(marketConfirmations); conf.Insert(indexOfLast, mConf); } @@ -60,12 +63,25 @@ public async Task GetConfirmations() } } + [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 @@ private async Task SendConfirmation(Mafile mafile, Confirmation confirmation, bo } 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); - } } \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_File.cs b/NebulaAuth/ViewModel/MainVM_File.cs index 2d7a71c..9f8a75b 100644 --- a/NebulaAuth/ViewModel/MainVM_File.cs +++ b/NebulaAuth/ViewModel/MainVM_File.cs @@ -17,7 +17,6 @@ using SteamLib.Exceptions; using NebulaAuth.Utility; using NebulaAuth.View.Dialogs; -using AutoUpdaterDotNET; namespace NebulaAuth.ViewModel; @@ -39,7 +38,7 @@ private void OpenMafileFolder() } if (mafilePath != null) { - path = $"/select, \"{mafilePath}\""; ; + path = $"/select, \"{mafilePath}\""; } try @@ -66,7 +65,7 @@ private Task AddMafile() 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) @@ -88,7 +87,7 @@ public async Task AddMafile(string[] path) } catch (IOException) { - confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmMafileOverwrite")); + confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmMafileOverwrite")); if (confirmOverwrite == true) { @@ -116,24 +115,24 @@ public async Task AddMafile(string[] path) } 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)); } @@ -156,7 +155,7 @@ private async Task HandleAddMafileWithoutSession(Mafile data) try { await MaClient.LoginAgain(data, password, loginAgainVm.SavePassword, waitDialog); - SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin")); + SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin")); } catch (LoginException ex) { @@ -189,7 +188,7 @@ private async Task RemoveMafile() { if (SelectedMafile == null) return; var confirm = - await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("RemoveMafileConfirmation")); + await DialogsController.ShowConfirmCancelDialog(GetLocalization("RemoveMafileConfirmation")); if (!confirm) return; try @@ -198,12 +197,12 @@ private async Task RemoveMafile() } catch (UnauthorizedAccessException) { - SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRemoveAlreadyExist")); + SnackbarController.SendSnackbar(GetLocalization("CantRemoveAlreadyExist")); } catch (Exception ex) { SnackbarController.SendSnackbar( - $"{GetLocalizationOrDefault("CantRemoveMafile")} {ex.Message}"); + $"{GetLocalization("CantRemoveMafile")} {ex.Message}"); } } @@ -219,7 +218,7 @@ private async Task OpenSettingsDialog() } [RelayCommand] - private async Task CopyMafileFromBuffer() + private async Task PasteMafilesFromClipboard() { StringCollection files; try @@ -250,7 +249,40 @@ private void CopyLogin(object? mafile) try { Clipboard.SetText(maf.AccountName); - SnackbarController.SendSnackbar(GetLocalizationOrDefault("LoginCopied")); + SnackbarController.SendSnackbar(GetLocalization("LoginCopied")); + return; + } + catch (Exception ex) + { + if (i == 19) + { + Shell.Logger.Error(ex); + SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error")); + } + + } + i++; + } + } + + [RelayCommand] + private void CopyMafile(object? mafile) + { + if (mafile is not Mafile maf) return; + var i = 0; + var path = Storage.TryFindMafilePath(maf); + if (path == null) + { + SnackbarController.SendSnackbar(GetLocalization("MafileNotCopied")); + return; + } + + while (i < 20) + { + try + { + Clipboard.SetFileDropList([path]); + SnackbarController.SendSnackbar(GetLocalization("MafileCopied")); return; } catch (Exception ex) diff --git a/NebulaAuth/ViewModel/MainVM_Groups.cs b/NebulaAuth/ViewModel/MainVM_Groups.cs index b5df780..7e7f08e 100644 --- a/NebulaAuth/ViewModel/MainVM_Groups.cs +++ b/NebulaAuth/ViewModel/MainVM_Groups.cs @@ -11,7 +11,7 @@ namespace NebulaAuth.ViewModel; public partial class MainVM //Groups { [ObservableProperty] - private ObservableCollection _groups = new(); + private ObservableCollection _groups = []; public string? SelectedGroup @@ -109,6 +109,7 @@ private void QueryGroups() private void PerformQuery() { + MaacDisplay = false; if (string.IsNullOrWhiteSpace(SelectedGroup) && string.IsNullOrWhiteSpace(SearchText)) { MaFiles = Storage.MaFiles; diff --git a/NebulaAuth/ViewModel/MainVM_Localization.cs b/NebulaAuth/ViewModel/MainVM_Localization.cs deleted file mode 100644 index b89b8c9..0000000 --- a/NebulaAuth/ViewModel/MainVM_Localization.cs +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_MAAC.cs b/NebulaAuth/ViewModel/MainVM_MAAC.cs new file mode 100644 index 0000000..3c15083 --- /dev/null +++ b/NebulaAuth/ViewModel/MainVM_MAAC.cs @@ -0,0 +1,177 @@ +using System.Collections.Generic; +using System.Linq; +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)) + { + SwitchMAACDisplay(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] + private void SwitchMAAC(bool market) + { + var maf = SelectedMafile; + if (maf == null) return; + SwitchMAACOn([maf], market); + } + + [RelayCommand] + private void SwitchMAACOnGroup(bool market) + { + var group = SelectedGroup; + if(group == null) return; + var mafilesInGroup = MaFiles.Where(m => group.Equals(m.Group)); + SwitchMAACOn(mafilesInGroup, market); + } + + [RelayCommand] + private void SwitchMAACOnAll(bool market) + { + SwitchMAACOn(MaFiles, market); + } + + private void SwitchMAACOn(IEnumerable mafiles, bool market) + { + mafiles = mafiles.ToArray(); + + var turnOn = mafiles.All(m => m.LinkedClient == null || GetCurrentMode(m.LinkedClient) == false); + if (turnOn) + { + foreach (var mafile in mafiles) + { + MultiAccountAutoConfirmer.TryAddToConfirm(mafile); + SetCurrentMode(mafile.LinkedClient, turnOn); + } + + } + else + { + foreach (var mafile in mafiles) + { + SetCurrentMode(mafile.LinkedClient, turnOn); + if(PretendsToRemove(mafile)) + MultiAccountAutoConfirmer.RemoveFromConfirm(mafile); + } + } + + return; + + bool PretendsToRemove(Mafile mafile) => mafile.LinkedClient is {AutoConfirmMarket: false, AutoConfirmTrades: false}; + bool GetCurrentMode(PortableMaClient linkedClient) => market ? linkedClient.AutoConfirmMarket : linkedClient.AutoConfirmTrades; + void SetCurrentMode(PortableMaClient? linkedClient, bool value) + { + if (linkedClient == null) return; + if (market) + { + linkedClient.AutoConfirmMarket = value; + } + else + { + linkedClient.AutoConfirmTrades = value; + } + } + } + + private void SwitchMAACDisplay(bool newValue) + { + if (newValue) + { + MaFiles = MultiAccountAutoConfirmer.Clients; + } + else + { + PerformQuery(); + } + } +} \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_Other.cs b/NebulaAuth/ViewModel/MainVM_Other.cs deleted file mode 100644 index af830ba..0000000 --- a/NebulaAuth/ViewModel/MainVM_Other.cs +++ /dev/null @@ -1,37 +0,0 @@ -using MaterialDesignThemes.Wpf; -using System; -using System.Windows; -using NebulaAuth.View.Dialogs; - -namespace NebulaAuth.ViewModel; - -public partial class MainVM //Other -{ - private void SessionHandlerOnLoginCompleted(object? sender, EventArgs e) - { - var currentSession = DialogHost.GetDialogSession(null); - Application.Current.Dispatcher.BeginInvoke(() => - { - if (currentSession is { Content: WaitLoginDialog, IsEnded: false }) - { - try - { - currentSession.Close(); - } - catch - { - //Ignored - } - } - }); - } - - private async void SessionHandlerOnLoginStarted(object? sender, EventArgs e) - { - if (DialogHost.IsDialogOpen(null)) return; - await Application.Current.Dispatcher.BeginInvoke(async () => - { - await DialogHost.Show(new WaitLoginDialog()); - }); - } -} \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_Proxy.cs b/NebulaAuth/ViewModel/MainVM_Proxy.cs index 148d8d4..349c4f9 100644 --- a/NebulaAuth/ViewModel/MainVM_Proxy.cs +++ b/NebulaAuth/ViewModel/MainVM_Proxy.cs @@ -6,7 +6,6 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; -using NebulaAuth.Model.Comparers; namespace NebulaAuth.ViewModel; @@ -23,16 +22,17 @@ public MaProxy? SelectedProxy { 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 @@ private void SetCurrentProxy() if (SelectedMafile == null) { SelectedProxy = null; + ProxyExist = true; return; } if (SelectedMafile.Proxy == null) @@ -52,7 +53,7 @@ private void SetCurrentProxy() 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 @@ private bool ValidateCanSaveAndWarn(Mafile data) var canSave = Storage.ValidateCanSave(data); if (!canSave) { - SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRetrieveSteamIDToUpdate")); + SnackbarController.SendSnackbar(GetLocalization("CantRetrieveSteamIDToUpdate")); } return canSave; } diff --git a/NebulaAuth/ViewModel/MainVM_Timer.cs b/NebulaAuth/ViewModel/MainVM_Timer.cs deleted file mode 100644 index 2f8c7c4..0000000 --- a/NebulaAuth/ViewModel/MainVM_Timer.cs +++ /dev/null @@ -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 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(); - 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? 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 HandleTimerRequest(Func> 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")); - } -} \ No newline at end of file diff --git a/NebulaAuth/ViewModel/Other/LinkAccountVM.cs b/NebulaAuth/ViewModel/Other/LinkAccountVM.cs index 706e0b2..f75ba35 100644 --- a/NebulaAuth/ViewModel/Other/LinkAccountVM.cs +++ b/NebulaAuth/ViewModel/Other/LinkAccountVM.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using MaterialDesignThemes.Wpf; +using NebulaAuth.Core; using NebulaAuth.Model; using NebulaAuth.Model.Entities; using NebulaAuth.Utility; @@ -19,12 +20,12 @@ using SteamLib.Web; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Windows; -using NebulaAuth.Core; namespace NebulaAuth.ViewModel.Other; @@ -60,8 +61,8 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum private TaskCompletionSource _emailConfTcs = new(); private TaskCompletionSource _linkCodeTcs = new(); - private bool isLinkStarted; - + private bool _isLinkStarted; + private string _rCode = string.Empty; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ProceedCommand))] private bool _canProceed = true; @@ -178,12 +179,12 @@ public async Task Proceed() #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); @@ -194,10 +195,11 @@ public async Task Proceed() Storage.SaveMafile(mafile); File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile")); HintText = - string.Format(GetLocalizationOrDefault("MafileLinked"), + string.Format(GetLocalizationOrDefault("MafileLinked"), mafile.RevocationCode, mafile.SessionData?.SteamId.Steam64); + _rCode = mafile.RevocationCode ?? string.Empty; CanProceed = true; return; } @@ -312,11 +314,12 @@ private void ResetState() IsLogin = false; IsFieldVisible = true; IsEmailCode = false; - isLinkStarted = false; + _isLinkStarted = false; IsPhoneNumber = false; IsEmailConfirmation = false; CanProceed = true; _emailCodeTcs = new TaskCompletionSource(); + _rCode = string.Empty; } private void Backup(MobileDataExtended data) @@ -401,6 +404,33 @@ private void SetProxy() #endregion + [RelayCommand] + private void OpenTroubleshooting() + { + const string troubleshootingURI = + "https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/docs/{0}/LinkingTroubleshooting"; + + var localized = string.Format(troubleshootingURI, LocManager.GetCurrentLanguageCode()); + Process.Start(new ProcessStartInfo(new Uri(localized).ToString()) + { + UseShellExecute = true + }); + } + + [RelayCommand] + private void CopyCode() + { + try + { + Clipboard.SetText(_rCode); + } + catch (Exception ex) + { + Shell.Logger.Error(ex, "Error whily copying RCode"); + return; + } + } + private static string GetLocalizationOrDefault(string key) { diff --git a/NebulaAuth/ViewModel/Other/LoginAgainVM.cs b/NebulaAuth/ViewModel/Other/LoginAgainVM.cs index 69fde2d..8bdfd4d 100644 --- a/NebulaAuth/ViewModel/Other/LoginAgainVM.cs +++ b/NebulaAuth/ViewModel/Other/LoginAgainVM.cs @@ -16,7 +16,4 @@ public partial class LoginAgainVM : ObservableObject public bool IsFormValid => !string.IsNullOrWhiteSpace(Password); - - public LoginAgainVM() - { } } \ No newline at end of file diff --git a/NebulaAuth/ViewModel/Other/SettingsVM.cs b/NebulaAuth/ViewModel/Other/SettingsVM.cs index 1408eb5..9c6c8d1 100644 --- a/NebulaAuth/ViewModel/Other/SettingsVM.cs +++ b/NebulaAuth/ViewModel/Other/SettingsVM.cs @@ -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; @@ -115,14 +110,19 @@ public bool UseAccountNameAsMafileName set => Settings.UseAccountNameAsMafileName = value; } - [ObservableProperty] private string _password; + public bool IgnorePatchTuesdayErrors + { + get => Settings.IgnorePatchTuesdayErrors; + set => Settings.IgnorePatchTuesdayErrors = value; + } + + [ObservableProperty] private string? _password; [RelayCommand] - public void SetPassword() + private void SetPassword() { - PHandler.SetPassword(Password); - Settings.IsPasswordSet = PHandler.IsPasswordSet; + Settings.IsPasswordSet = PHandler.SetPassword(Password); } } \ No newline at end of file diff --git a/NebulaAuth/ViewModel/Other/UpdaterVM.cs b/NebulaAuth/ViewModel/Other/UpdaterVM.cs index 715ce35..7a46666 100644 --- a/NebulaAuth/ViewModel/Other/UpdaterVM.cs +++ b/NebulaAuth/ViewModel/Other/UpdaterVM.cs @@ -3,7 +3,7 @@ namespace NebulaAuth.ViewModel.Other; -public partial class UpdaterVM : ObservableObject +public class UpdaterVM : ObservableObject { public UpdateInfoEventArgs UpdateInfoEventArgs { get; } diff --git a/NebulaAuth/localization.loc.json b/NebulaAuth/localization.loc.json index f1105e5..bca075a 100644 --- a/NebulaAuth/localization.loc.json +++ b/NebulaAuth/localization.loc.json @@ -73,7 +73,7 @@ "ru": "Чтобы начать пользоваться программой вы можете привязать аккаунт через меню \"Аккаунт\", либо импортировать существующие мафайлы одним из способов:\n1. Скопировать их в папку mafiles и перезапустить приложение\n2. Перетянуть файлы прямо в окно программы\n3. Скопировать файлы и нажать CTRL+V в окне программы\n4. Через меню \"Файл\" - \"Импорт\"", "en": "To start using the program, you can link an account through the \"Account\" menu, or import existing mafiles in one of the following ways:\n1. Copy them to the mafiles folder and restart the application\n2. Drag files directly into the program window\n3. Copy files and press CTRL+V in the program window\n4. Through the \"File\" - \"Import\" menu", "ua": "Щоб почати користуватися програмою, ви можете прив'язати акаунт через меню \"Акаунт\", або імпортувати існуючі мафайли одним із способів:\n1. Скопіювати їх у папку mafiles та перезапустити програму\n2. Перетягнути файли безпосередньо в вікно програми\n3. Скопіювати файли та натиснути CTRL+V в вікні програми\n4. Через меню \"Файл\" - \"Імпорт\"" - } + } }, "Menu": { "File": { @@ -172,14 +172,31 @@ } }, "TradeTimerHint": { - "en": "Trade", - "ru": "Трейд", - "ua": "Трейд" + "en": "Auto-confirm trades. RMC for mass control", + "ru": "Автоподтверждение трейдов. ПКМ для массового управления", + "ua": "Автопідтвердження трейдів. ПКМ для масового управління" }, "MarketTimerHint": { - "en": "Market", - "ru": "Маркет", - "ua": "Маркет" + "en": "Auto-confirm market listings. RMC for mass control", + "ru": "Автоподтверждение лотов на маркете. ПКМ для массового управления", + "ua": "Автопідтвердження лотів на маркеті. ПКМ для масового управління" + }, + "TimersContextMenu": { + "SwitchMAACGroup": { + "en": "Switch in group", + "ru": "Переключить в группе", + "ua": "Перемкнути в групі" + }, + "SwitchMAACAll": { + "en": "Switch all", + "ru": "Переключить все", + "ua": "Перемкнути всі" + } + }, + "ShowAutoConfirmAccountsHint": { + "en": "Show accounts with auto-confirmation enabled.", + "ru": "Показать аккаунты с включенным автоподтверждением.", + "ua": "Показати акаунти з включеним автопідтвердженням." } }, "LeftPart": { @@ -247,6 +264,11 @@ "ru": "Скопировать логин", "ua": "Скопіювати логін" }, + "CopyMafile": { + "en": "Copy mafile", + "ru": "Скопировать мафайл", + "ua": "Скопіювати мафайл" + }, "AddToGroup": { "en": "Add to group", "ru": "Добавить в группу", @@ -300,11 +322,6 @@ "ua": "Без фону" } }, - "DisableTimersOnSwitch": { - "en": "Disable timers on account switch", - "ru": "Отключать таймеры при смене аккаунта", - "ua": "Вимикати таймери при зміні акаунта" - }, "MinimizeToTray": { "en": "Minimize to tray", "ru": "Сворачивать в трей", @@ -356,7 +373,19 @@ "en": "Use account name on mafiles", "ru": "Использовать имя аккаунта на мафайлах", "ua": "Використовувати ім'я акаунта на мафайлах" + }, + "IgnorePatchTuesdayErrors": { + "en": "Ignore Patch Tuesday errors in timer (exp.)", + "ru": "Игнорировать ошибки Patch Tuesday в таймере (эксп.)", + "ua": "Ігнорувати помилки Patch Tuesday у таймері (експ.)" + }, + "IgnorePatchTuesdayErrorsHint": { + "en": "Ignore errors that occur every Tuesday (Wednesday night in GMT) when Steam doing technical works and auto-confirm timers turns off unnecessarily", + "ru": "Игнорировать ошибки, которые возникают каждый вторник (ночь среды по GMT) когда Steam проводит технические работы и автоподтверждения отключаются без надобности", + "ua": "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби" } + + }, "LoginAgainDialog": { "Title": { @@ -398,7 +427,7 @@ "en": "Press 'DEL' to remove", "ru": "Нажмите 'DEL' для удаления", "ua": "Натисніть 'DEL' для видалення" - } + } }, "ProxyManagerDialog": { "Title": { @@ -430,6 +459,11 @@ "ru": "Привязка", "ua": "Прив'язка" }, + "GotErrorHyperlinkText": { + "en": "(getting error?)", + "ru": "(возникает ошибка?)", + "ua": "(виникає помилка?)" + }, "Proxy": { "en": "Proxy", "ru": "Прокси", @@ -451,14 +485,14 @@ "ua": "Номер телефону" }, "EmailLink": { - "en": "Email link", - "ru": "Ссылка из письма", - "ua": "Посилання з листа" + "en": "EMail link", + "ru": "Ссылка из email", + "ua": "Посилання з email" }, "SmsOrCode": { - "en": "Sms or code", - "ru": "Смс или код", - "ua": "Смс або код" + "en": "SMS or code", + "ru": "СМС или код", + "ua": "СМС або код" }, "Completed": { "en": "Completed", @@ -600,26 +634,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.", @@ -644,7 +658,17 @@ "en": "Login copied", "ru": "Логин скопирован", "ua": "Логін скопійовано" - } + }, + "MafileCopied": { + "en": "Mafile copied", + "ru": "Мафайл скопирован", + "ua": "Мафайл скопійовано" + }, + "MafileNotCopied": { + "en": "Mafile not copied", + "ru": "Мафайл не скопирован", + "ua": "Мафайл не скопійовано" + } }, "ErrorTranslator": { "Login": { @@ -892,9 +916,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", @@ -904,15 +928,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": "Произошла отмена операции", @@ -938,6 +962,23 @@ "ru": "Неизвестная ошибка: ", "en": "Unknown error: ", "ua": "Невідома помилка: " + }, + "CantLoadConfirmationsException": { + "Common": { + "ru": "Не удалось загрузить потверждения из-за ошибки Steam: ", + "en": "Can't load confirmations due to Steam error: ", + "ua": "Не вдалося завантажити підтвердження через помилку Steam: " + }, + "TryAgainLater": { + "ru": "Попробуйте позже", + "en": "Try again later", + "ua": "Спробуйте пізніше" + }, + "NotSetupToReceiveConfirmations": { + "ru": "Аккаунт не настроен на получение подтверждений", + "en": "Account is not set up to receive confirmations", + "ua": "Акаунт не налаштований на отримання підтверджень" + } } }, @@ -1022,9 +1063,31 @@ "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": "Авто " } } }, diff --git a/NebulaAuth/update.xml b/NebulaAuth/update.xml index de7990a..cb9162b 100644 --- a/NebulaAuth/update.xml +++ b/NebulaAuth/update.xml @@ -1,8 +1,7 @@  - 1.5.2.0 - https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.2/NebulaAuth.1.5.2.zip - https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.2.html + 1.5.3.0 + https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.3/NebulaAuth.1.5.3.zip + https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.3.html false - \ No newline at end of file diff --git a/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs b/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs index 3f933e7..6925baf 100644 --- a/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs +++ b/SteamLibForked/Api/Mobile/SteamAuthenticatorLinkerApi.cs @@ -54,7 +54,7 @@ public static async Task LinkRequest(HttpClient clien }; var resp = await client.PostProtoMsg(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 async Task FinalizeLink(HttpClient client, string conf { 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 async Task CheckEmailConfirmation(HttpClient client, string while (i < 5) { i++; - var resp = await client.PostProto(reqUri, new EmptyMessage()); ; + var resp = await client.PostProto(reqUri, new EmptyMessage()); if (resp.IsWaiting == false) return true; diff --git a/SteamLibForked/Api/Mobile/SteamMobileApi.cs b/SteamLibForked/Api/Mobile/SteamMobileApi.cs index d1cee0d..16e8076 100644 --- a/SteamLibForked/Api/Mobile/SteamMobileApi.cs +++ b/SteamLibForked/Api/Mobile/SteamMobileApi.cs @@ -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,14 @@ using SteamLib.ProtoCore.Enums; using SteamLib.ProtoCore.Exceptions; using SteamLib.ProtoCore.Services; +using System.Net; +using SteamLib.Account; namespace SteamLib.Api.Mobile; [PublicAPI] public static class SteamMobileApi -//TODO: cancellation token -//TODO: HealthMonitor { private const string GENERATE_ACCESS_TOKEN = @@ -27,30 +26,31 @@ public static class SteamMobileApi /// /// /// + /// /// Refreshed AccessToken /// - public static async Task RefreshJwt(HttpClient client, string refreshToken, long steamId) + public static async Task RefreshJwt(HttpClient client, string refreshToken, SteamId steamId, CancellationToken cancellationToken = default) { var req = new GenerateAccessTokenForApp_Request { RefreshToken = refreshToken, - SteamId = steamId, + SteamId = steamId.Steam64, TokenRenewalType = true }; try { - var resp = await client.PostProto(GENERATE_ACCESS_TOKEN, req); + var resp = await client.PostProto(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 HasPhoneAttached(HttpClient client, string sessionId) + public static async Task HasPhoneAttached(HttpClient client, string sessionId, CancellationToken cancellationToken = default) { var data = new Dictionary { @@ -60,14 +60,18 @@ public static async Task HasPhoneAttached(HttpClient client, string sessio }; 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(); + 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(); + }); } - public static async Task RemoveAuthenticator(HttpClient client, string accessToken, string rCode) + public static async Task 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 +82,15 @@ public static async Task RemoveAuthenticator(HttpC }; try { - return await client.PostProto(req, reqData); + return await client.PostProto(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); } } + + + } \ No newline at end of file diff --git a/SteamLibForked/Api/Mobile/SteamMobileAuthenticatorApi.cs b/SteamLibForked/Api/Mobile/SteamMobileAuthenticatorApi.cs new file mode 100644 index 0000000..3f1bd2b --- /dev/null +++ b/SteamLibForked/Api/Mobile/SteamMobileAuthenticatorApi.cs @@ -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(HttpClient client, string accessToken, CancellationToken cancellationToken = default) + { + var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" + accessToken; + + try + { + return await client.GetProto(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(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(req, reqData, cancellationToken: cancellationToken); + } + catch (HttpRequestException ex) + when (ex.StatusCode == HttpStatusCode.Unauthorized) + { + throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex); + } + } + + + public static async Task 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; + } + +} \ No newline at end of file diff --git a/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs b/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs index 41b5ec8..d5bf9d2 100644 --- a/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs +++ b/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs @@ -1,42 +1,54 @@ -using SteamLib.Core; +using AchiesUtilities.Web.Extensions; +using JetBrains.Annotations; +using SteamLib.Account; +using SteamLib.Core; +using SteamLib.Core.StatusCodes; using SteamLib.Exceptions; -using SteamLib.SteamMobile.Confirmations; using SteamLib.SteamMobile; -using System.Net; -using AchiesUtilities.Web.Extensions; -using SteamLib.Web.Scrappers.JSON; -using SteamLib.Core.StatusCodes; +using SteamLib.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> GetConfirmations(HttpClient client, MobileData data, long steamId) + public static async Task> 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); + + if (resp.StatusCode == HttpStatusCode.Redirect) { - throw new SessionExpiredException("Mobile session expired"); + throw new SessionInvalidException("Mobile session expired"); } - + var respStr = await resp.Content.ReadAsStringAsync(cancellationToken); resp.EnsureSuccessStatusCode(); + try + { + return MobileConfirmationScrapper.Scrap(respStr); + } + catch (Exception ex) + when (ex is not (SessionInvalidException or CantLoadConfirmationsException)) + { + SteamLibErrorMonitor.LogErrorResponse(respStr, ex); + throw; - return HealthMonitor.LogOnException(respStr, MobileConfirmationScrapper.Scrap, typeof(SessionExpiredException)); + } } - public static async Task SendConfirmation(HttpClient client, Confirmation confirmation, long steamId, MobileData data, bool confirm) + public static async Task SendConfirmation(HttpClient client, Confirmation confirmation, SteamId steamId, MobileData data, bool confirm, CancellationToken cancellationToken = default) { var op = confirm ? "allow" : "cancel"; @@ -50,8 +62,8 @@ public static async Task SendConfirmation(HttpClient client, Confirmation query.Add(new KeyValuePair("ck", key)); var req = CONF_OP + query.ToQueryString(); - var resp = await client.GetStringAsync(req); - var successCode = HealthMonitor.LogOnException(resp, () => SteamStatusCode.Translate(resp, out _)); + var resp = await client.GetStringAsync(req, cancellationToken); + var successCode = SteamLibErrorMonitor.HandleResponse(resp, () => SteamStatusCode.Translate(resp, out _)); return successCode.Equals(SteamStatusCode.Ok); @@ -59,7 +71,7 @@ public static async Task SendConfirmation(HttpClient client, Confirmation public static async Task SendMultipleConfirmations(HttpClient client, IEnumerable 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 +84,25 @@ public static async Task SendMultipleConfirmations(HttpClient client, IEnu } 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(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(respStr, out _)); return successCode.Equals(SteamStatusCode.Ok); } - internal static IEnumerable> GetConfirmationKvp(long steamId, string deviceId, string identitySecret, string tag = "conf") + internal static IEnumerable> GetConfirmationKvp(SteamId steamId, string deviceId, string identitySecret, string tag = "conf") { var time = TimeAligner.GetSteamTime(); var hash = EncryptionHelper.GenerateConfirmationHash(time, identitySecret, tag); - return new KeyValuePair[] - { - 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) + ]; } } \ No newline at end of file diff --git a/SteamLibForked/Authentication/AdmissionHelper.cs b/SteamLibForked/Authentication/AdmissionHelper.cs index 353d37e..fae2743 100644 --- a/SteamLibForked/Authentication/AdmissionHelper.cs +++ b/SteamLibForked/Authentication/AdmissionHelper.cs @@ -79,7 +79,7 @@ public static void SetSteamMobileCookies(this CookieContainer container, IMobile } /// - /// Clear and set new session. Not recommended. Uses for domain instead of its own cookie. It's okay to use it only for confirmations. But Market, Trading and other pages won't be authenticated + /// Clear and set new session. Not recommended. Uses for domain instead of its own cookie. It's okay to use it only for confirmations. But Market, Trading and other pages won't be authorized /// public static void SetSteamMobileCookiesWithMobileToken(this CookieContainer container, IMobileSessionData mobileSession, string setLanguage = "english") @@ -210,6 +210,14 @@ public static bool IsSteamCookie(Cookie cookie) => .Value; } + public static void ClearAllCookies(this CookieContainer container) + { + foreach (Cookie allCookie in container.GetAllCookies()) + { + allCookie.Expired = true; + } + } + #endregion diff --git a/SteamLibForked/Authentication/LoginV2/FinalizeLoginJson.cs b/SteamLibForked/Authentication/LoginV2/FinalizeLoginJson.cs index bfe479c..8dbd2da 100644 --- a/SteamLibForked/Authentication/LoginV2/FinalizeLoginJson.cs +++ b/SteamLibForked/Authentication/LoginV2/FinalizeLoginJson.cs @@ -2,30 +2,29 @@ namespace SteamLib.Authentication.LoginV2; -class FinalizeLoginJson +public class FinalizeLoginJson { [JsonProperty("steamID")] public ulong SteamId { get; set; } - [JsonProperty("transfer_info")] - public List TransferInfo { get; set; } + [JsonProperty("transfer_info")] + public List TransferInfo { get; set; } = []; } -class TransferInfo +public class TransferInfo { - [JsonProperty("url")] - public string Url { get; set; } + [JsonProperty("url")] + public string Url { get; set; } = null!; [JsonProperty("params")] - public TransferInfoParams TransferInfoParams { get; set; } + public TransferInfoParams TransferInfoParams { get; set; } = null!; } - -class TransferInfoParams +public class TransferInfoParams { [JsonProperty("nonce")] - public string Nonce { get; set; } + public string Nonce { get; set; } = null!; - [JsonProperty("auth")] - public string Auth { get; set; } + [JsonProperty("auth")] + public string Auth { get; set; } = null!; } \ No newline at end of file diff --git a/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs b/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs index ee5c35f..db4783c 100644 --- a/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs +++ b/SteamLibForked/Authentication/LoginV2/LoginV2Executor.cs @@ -42,8 +42,8 @@ private LoginV2Executor(LoginV2ExecutorOptions options) /// - /// Do login on SteamCommunity.
- /// Some functions requires proper SessionId. But contains only SteamCommunity related SessionId and some functions on other services may not work + /// Do log in on SteamCommunity.
+ /// Some functions require proper SessionId. But contains only SteamCommunity related SessionId and some functions on other services may not work ///
/// /// @@ -163,7 +163,7 @@ await client.PostProto( var finalizeContent = await finalize.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken); var finalizeResp = - HealthMonitor.LogOnException(finalizeContent, () => JsonConvert.DeserializeObject(finalizeContent)!); + SteamLibErrorMonitor.HandleResponse(finalizeContent, () => JsonConvert.DeserializeObject(finalizeContent)!); List tokens = new(); diff --git a/SteamLibForked/Core/HealthMonitor.cs b/SteamLibForked/Core/HealthMonitor.cs index 8dd8d3f..e10b803 100644 --- a/SteamLibForked/Core/HealthMonitor.cs +++ b/SteamLibForked/Core/HealthMonitor.cs @@ -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 +/// +/// 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. +/// +public static class SteamLibErrorMonitor { - public static ILogger? FatalLogger { get; set; } - internal static void LogUnexpected(string response, Exception ex) - { - FatalLogger?.LogCritical(ex, "Unexpected response detected:\n{response}", response); - } + /// + /// Logger for capturing and analyzing error-related issues. + /// + public static ILogger? MonitorLogger { get; set; } - internal static T LogOnException(string resp, Func del) + /// + /// Logs the response and the associated exception using the . + /// Differentiates between and other exceptions. + /// + /// The response string to log. + /// The exception that occurred. + internal static void LogErrorResponse(string response, Exception ex) { - try + if (ex is UnsupportedResponseException) { - return del(); + MonitorLogger?.LogError(ex, "Unsupported response detected"); } - catch (Exception ex) + else { - LogUnexpected(resp, ex); - throw; + MonitorLogger?.LogError(ex, "Unsupported response detected: {response}", response); } + } - internal static T LogOnException(string resp, Func del) + /// + /// Maps the error to an , logs it, and then throws the exception. + /// This method is used to handle unexpected responses and map them to a known exception type. + /// + /// The response that triggered the error. + /// The original exception. + /// Always throws after logging the response. + [DoesNotReturn] + internal static void MapAndThrowException(string response, Exception ex) { - try - { - return del(resp); - } - catch (Exception ex) - { - LogUnexpected(resp, ex); - throw; - } + var e = new UnsupportedResponseException(response, ex); + ExceptionDispatchInfo.SetCurrentStackTrace(e); + LogErrorResponse(response, e); + throw e; } - internal static T LogOnException(string resp, Func del, params Type[] exceptExceptions) + + [DoesNotReturn] + internal static T MapAndThrowException(string response, Exception ex) { - try - { - return del(); - } - catch (Exception ex) - when (exceptExceptions.Any(t => t == ex.GetType()) == false) - { - LogUnexpected(resp, ex); - throw; - } + var e = new UnsupportedResponseException(response, ex); + ExceptionDispatchInfo.SetCurrentStackTrace(e); + LogErrorResponse(response, e); + throw e; } - internal static T LogOnException(string resp, Func del, params Type[] exceptExceptions) + /// + /// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type. + /// If an is caught, it is logged and rethrown. + /// Otherwise, any other exceptions are remapped to . + /// + /// The return type of the delegate. + /// The response string for logging purposes. + /// The delegate to execute. + /// The result of the delegate execution. + /// Thrown when an unsupported response is encountered. + internal static T HandleResponse(string resp, Func del) { try { - return del(resp); + return del(); } - catch (Exception ex) - when (exceptExceptions.Any(t => t == ex.GetType()) == false) + catch (UnsupportedResponseException ex) { - LogUnexpected(resp, ex); + LogErrorResponse(resp, ex); throw; } - } - - internal static T LogOnException(string resp, Func del, Func logPredicate) - { - try - { - return del(); - } catch (Exception ex) - when (logPredicate(ex)) { - LogUnexpected(resp, ex); - throw; + MapAndThrowException(resp, ex); + return default; //Not reachable } } - internal static T LogOnException(string resp, Func del, Func logPredicate) + /// + /// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type. + /// If an is caught, it is logged and rethrown. + /// Otherwise, any other exceptions are remapped to . + /// + /// The return type of the delegate. + /// The response string for logging purposes. + /// The delegate to execute. + /// The result of the delegate execution. + /// Thrown when an unsupported response is encountered. + internal static T HandleResponse(string resp, Func del) { try { return del(resp); } - catch (Exception ex) - when (logPredicate(ex)) + catch (UnsupportedResponseException ex) { - LogUnexpected(resp, ex); + LogErrorResponse(resp, ex); throw; } + catch (Exception ex) + { + MapAndThrowException(resp, ex); + return default; //Not reachable + } } - internal static void LogOnException(string resp, Action del) + /// + /// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type. + /// If an is caught, it is logged and rethrown. + /// Otherwise, any other exceptions are remapped to . + /// + /// The response string for logging purposes. + /// The delegate to execute. + /// The result of the delegate execution. + /// Thrown when an unsupported response is encountered. + 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 logPredicate) - { - try - { - del(); - } catch (Exception ex) - when (logPredicate(ex)) { - LogUnexpected(resp, ex); - throw; + MapAndThrowException(resp, ex); } } - - internal static async Task LogOnExceptionAsync(string resp, Func> del) + /// + /// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type. + /// If an is caught, it is logged and rethrown. + /// Otherwise, any other exceptions are remapped to . + /// + /// The response string for logging purposes. + /// The delegate to execute. + /// The result of the delegate execution. + /// Thrown when an unsupported response is encountered. + internal static void HandleResponse(string resp, Action del) { try { - return await del(); + del(resp); } - catch (Exception ex) + catch (UnsupportedResponseException ex) { - LogUnexpected(resp, ex); + LogErrorResponse(resp, ex); throw; } - } - internal static async Task LogOnExceptionAsync(string resp, Func> del, params Type[] exceptExceptions) - { - try - { - return await del(); - } catch (Exception ex) - when (exceptExceptions.Any(t => t == ex.GetType()) == false) { - LogUnexpected(resp, ex); - throw; + MapAndThrowException(resp, ex); } } - } \ No newline at end of file diff --git a/SteamLibForked/Core/Interfaces/IConfirmationConsumer.cs b/SteamLibForked/Core/Interfaces/ILoginConsumer.cs similarity index 100% rename from SteamLibForked/Core/Interfaces/IConfirmationConsumer.cs rename to SteamLibForked/Core/Interfaces/ILoginConsumer.cs diff --git a/SteamLibForked/Core/StatusCodes/SteamStatusCode.cs b/SteamLibForked/Core/StatusCodes/SteamStatusCode.cs index 73fa04d..d55c547 100644 --- a/SteamLibForked/Core/StatusCodes/SteamStatusCode.cs +++ b/SteamLibForked/Core/StatusCodes/SteamStatusCode.cs @@ -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; diff --git a/SteamLibForked/Core/Constants.cs b/SteamLibForked/Core/SteamConstants.cs similarity index 98% rename from SteamLibForked/Core/Constants.cs rename to SteamLibForked/Core/SteamConstants.cs index 0c6e8dd..72717cf 100644 --- a/SteamLibForked/Core/Constants.cs +++ b/SteamLibForked/Core/SteamConstants.cs @@ -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 - - - - - } \ No newline at end of file diff --git a/SteamLibForked/Exceptions/CantLoadConfirmationsException.cs b/SteamLibForked/Exceptions/CantLoadConfirmationsException.cs index 3b94a9c..558bd92 100644 --- a/SteamLibForked/Exceptions/CantLoadConfirmationsException.cs +++ b/SteamLibForked/Exceptions/CantLoadConfirmationsException.cs @@ -4,12 +4,11 @@ public class CantLoadConfirmationsException : Exception { public LoadConfirmationsError Error { get; init; } + public string? ErrorMessage { get; init; } + public string? ErrorDetails { get; init; } 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) { } } diff --git a/SteamLibForked/Exceptions/Mobile/BadMobileCookiesException.cs b/SteamLibForked/Exceptions/Mobile/BadMobileCookiesException.cs index 0683e35..0f6ed03 100644 --- a/SteamLibForked/Exceptions/Mobile/BadMobileCookiesException.cs +++ b/SteamLibForked/Exceptions/Mobile/BadMobileCookiesException.cs @@ -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 BadMobileCookiesException(string message) : base(message) public BadMobileCookiesException(string message, Exception inner) : base(message, inner) { } - - protected BadMobileCookiesException( - SerializationInfo info, - StreamingContext context) : base(info, context) - { - } } \ No newline at end of file diff --git a/SteamLibForked/Exceptions/SessionExpiredException.cs b/SteamLibForked/Exceptions/SessionExpiredException.cs index 5a763c1..d6928d0 100644 --- a/SteamLibForked/Exceptions/SessionExpiredException.cs +++ b/SteamLibForked/Exceptions/SessionExpiredException.cs @@ -1,11 +1,13 @@ namespace SteamLib.Exceptions; -public class SessionExpiredException : SessionInvalidException + + +/// +/// Unlike , this exception indicates a definite session expiration. Refreshing the JWT token will not help. +/// +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) { } } diff --git a/SteamLibForked/Exceptions/SessionInvalidException.cs b/SteamLibForked/Exceptions/SessionInvalidException.cs index 3cad890..ad6a4a9 100644 --- a/SteamLibForked/Exceptions/SessionInvalidException.cs +++ b/SteamLibForked/Exceptions/SessionInvalidException.cs @@ -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) { } } diff --git a/SteamLibForked/MobileSteamClient/MobileData.cs b/SteamLibForked/MobileSteamClient/MobileData.cs index e0e6789..32529ac 100644 --- a/SteamLibForked/MobileSteamClient/MobileData.cs +++ b/SteamLibForked/MobileSteamClient/MobileData.cs @@ -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 } diff --git a/SteamLibForked/ProtoCore/Enums/EResult.cs b/SteamLibForked/ProtoCore/Enums/EResult.cs index 30d78a2..3d99d78 100644 --- a/SteamLibForked/ProtoCore/Enums/EResult.cs +++ b/SteamLibForked/ProtoCore/Enums/EResult.cs @@ -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, diff --git a/SteamLibForked/ProtoCore/Exceptions/EResutException.cs b/SteamLibForked/ProtoCore/Exceptions/EResutException.cs index 0e2276e..19b47cd 100644 --- a/SteamLibForked/ProtoCore/Exceptions/EResutException.cs +++ b/SteamLibForked/ProtoCore/Exceptions/EResutException.cs @@ -21,8 +21,4 @@ public EResultException(EResult result, Exception inner) : base("EResult error: { Result = result; } - - protected EResultException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } diff --git a/SteamLibForked/ProtoCore/Exceptions/UnknownEResultException.cs b/SteamLibForked/ProtoCore/Exceptions/UnknownEResultException.cs index a340a89..933e8b7 100644 --- a/SteamLibForked/ProtoCore/Exceptions/UnknownEResultException.cs +++ b/SteamLibForked/ProtoCore/Exceptions/UnknownEResultException.cs @@ -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) - { - } + } \ No newline at end of file diff --git a/SteamLibForked/ProtoCore/Services/AuthenticationService.cs b/SteamLibForked/ProtoCore/Services/AuthenticationService.cs index ca5cc35..cf9a636 100644 --- a/SteamLibForked/ProtoCore/Services/AuthenticationService.cs +++ b/SteamLibForked/ProtoCore/Services/AuthenticationService.cs @@ -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] diff --git a/SteamLibForked/ProtoCore/Services/AuthenticatorLinkerService.cs b/SteamLibForked/ProtoCore/Services/AuthenticatorLinkerService.cs index d26121d..9b2464d 100644 --- a/SteamLibForked/ProtoCore/Services/AuthenticatorLinkerService.cs +++ b/SteamLibForked/ProtoCore/Services/AuthenticatorLinkerService.cs @@ -95,7 +95,7 @@ public class IsAccountWaitingForEmailConfirmation_Response : IProtoMsg [ProtoContract] public class SendPhoneVerificationCode_Request : IProtoMsg { - [ProtoMember(1, IsRequired = true)] public int Language { get; set; } = 0; + [ProtoMember(1, IsRequired = true)] public int Language { get; set; } } diff --git a/SteamLibForked/SteamLibForked.csproj b/SteamLibForked/SteamLibForked.csproj index dac5e9a..7d959fe 100644 --- a/SteamLibForked/SteamLibForked.csproj +++ b/SteamLibForked/SteamLibForked.csproj @@ -7,15 +7,15 @@ - - - - - + + + + + - + diff --git a/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs b/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs index fcb8282..540ee7e 100644 --- a/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs +++ b/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs @@ -40,7 +40,7 @@ public async Task LinkAccount(MobileSessionData data) 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(); @@ -52,7 +52,7 @@ public async Task LinkAccount(MobileSessionData data) if (accessToken.Value.Type != SteamAccessTokenType.Mobile) Logger?.LogWarning("Provided access token is not of type Mobile. Actual type: {actualType}", accessToken.Value.Type); - var refreshed = await SteamMobileApi.RefreshJwt(Options.HttpClient, data.RefreshToken.Token, data.RefreshToken.SteamId.Steam64); + var refreshed = await SteamMobileApi.RefreshJwt(Options.HttpClient, data.RefreshToken.Token, data.RefreshToken.SteamId); accessToken = SteamTokenHelper.Parse(refreshed); data.SetMobileToken(accessToken.Value); } diff --git a/SteamLibForked/SteamMobile/Confirmations/Concrete[folder]/TradeConfirmation.cs b/SteamLibForked/SteamMobile/Confirmations/Concrete[folder]/TradeConfirmation.cs index 7cce95a..4a584d4 100644 --- a/SteamLibForked/SteamMobile/Confirmations/Concrete[folder]/TradeConfirmation.cs +++ b/SteamLibForked/SteamMobile/Confirmations/Concrete[folder]/TradeConfirmation.cs @@ -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) { - } } \ No newline at end of file diff --git a/SteamLibForked/Utility/EncryptionHelper.cs b/SteamLibForked/Utility/EncryptionHelper.cs index 8b0fdec..2bfc2c1 100644 --- a/SteamLibForked/Utility/EncryptionHelper.cs +++ b/SteamLibForked/Utility/EncryptionHelper.cs @@ -16,7 +16,7 @@ public static byte[] HexStringToByteArray(string hex) 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 string GenerateConfirmationHash(long time, string identitySecret, { 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 byte[] GenerateConfirmationHashBytes(long time, string identitySec } 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; } diff --git a/SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs b/SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs index 0bbca7a..e1d3224 100644 --- a/SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs +++ b/SteamLibForked/Utility/MaFiles/DeserializedMafileData.cs @@ -16,7 +16,7 @@ public class DeserializedMafileData public int? Version { get; init; } public bool IsExtended { get; init; } public DeserializedMafileSessionResult SessionResult { get; init; } - public Dictionary? UnusedProperties { get; init; } = null; + public Dictionary? UnusedProperties { get; init; } public HashSet? MissingProperties { get; init; } = new(); diff --git a/SteamLibForked/Utility/MaFiles/LegacyMafile.cs b/SteamLibForked/Utility/MaFiles/LegacyMafile.cs index b540b33..1e14bda 100644 --- a/SteamLibForked/Utility/MaFiles/LegacyMafile.cs +++ b/SteamLibForked/Utility/MaFiles/LegacyMafile.cs @@ -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 diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer.cs b/SteamLibForked/Utility/MaFiles/MafileSerializer.cs index 9f34bb8..2a13171 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer.cs +++ b/SteamLibForked/Utility/MaFiles/MafileSerializer.cs @@ -12,7 +12,12 @@ public static partial class MafileSerializer private static readonly HashSet ActualProperties = typeof(MobileDataExtended).GetProperties().Select(x => x.Name).ToHashSet(); - public static MobileData Deserialize(string json, out DeserializedMafileData mafileData) + //TODO: Options with: + //allowSessionIdGeneration + //allowDeviceIdGeneration + //allowInvalidTokensGeneration + //etc… + public static MobileData Deserialize(string json, bool allowSessionIdGeneration, out DeserializedMafileData mafileData) { var j = JObject.Parse(json); @@ -55,7 +60,7 @@ public static MobileData Deserialize(string json, out DeserializedMafileData maf 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); @@ -108,7 +113,7 @@ var minified var sResult = DeserializedMafileSessionResult.Missing; if (sessionDataToken is { Type: JTokenType.Object }) { - sessionData = DeserializeMobileSessionData((JObject) sessionDataToken, out sResult); + sessionData = DeserializeMobileSessionData((JObject) sessionDataToken, allowSessionIdGeneration, out sResult); } @@ -129,8 +134,4 @@ var minified }; } - - - - } \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs b/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs index 8ec1607..975750f 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs +++ b/SteamLibForked/Utility/MaFiles/MafileSerializer_SessionData.cs @@ -3,29 +3,31 @@ using SteamLib.Account; using SteamLib.Authentication; using SteamLib.Core.Enums; +using System.Security.Cryptography; +using System.Text; namespace SteamLib.Utility.MaFiles; public partial class MafileSerializer //SessionData { - private static MobileSessionData? DeserializeMobileSessionData(JObject j, out DeserializedMafileSessionResult result) + private static MobileSessionData? DeserializeMobileSessionData(JObject j, bool allowSessionIdGeneration, out DeserializedMafileSessionResult result) { result = DeserializedMafileSessionResult.Invalid; - var refreshTokenToken = GetToken(j, nameof(MobileSessionData.RefreshToken), "refreshtoken", "refresh_token", + var jRefreshToken = GetToken(j, nameof(MobileSessionData.RefreshToken), "refreshtoken", "refresh_token", "refresh", "OAuthToken"); SteamAuthToken? refreshToken = null; - if (refreshTokenToken == null || refreshTokenToken.Type == JTokenType.Null) return null; - if (refreshTokenToken.Type == JTokenType.String && SteamTokenHelper.TryParse(refreshTokenToken.Value()!, out var parsed)) + if (jRefreshToken == null || jRefreshToken.Type == JTokenType.Null) return null; + if (jRefreshToken.Type == JTokenType.String && SteamTokenHelper.TryParse(jRefreshToken.Value()!, out var parsed)) { refreshToken = parsed; } - else if (refreshTokenToken.Type == JTokenType.Object) + else if (jRefreshToken.Type == JTokenType.Object) { try { - refreshToken = refreshTokenToken.ToObject(); + refreshToken = jRefreshToken.ToObject(); } catch { @@ -33,31 +35,44 @@ public partial class MafileSerializer //SessionData } } + //if (refreshToken == null) + //{ + // result = DeserializedMafileSessionResult.Invalid; + // return null; + //} + var sessionId = GetString(j, "sessionid", "session_id", "session"); - var accessTokenToken = GetToken(j, "accesstoken", "access_token", "access"); - accessTokenToken ??= GetToken(j, "steamLoginSecure"); + var jAccessToken = GetToken(j, "accesstoken", "access_token", "access"); + jAccessToken ??= GetToken(j, "steamLoginSecure"); - if (string.IsNullOrWhiteSpace(sessionId)) return null; + if (sessionId == null && allowSessionIdGeneration) + { + sessionId = GenerateRandomHex(12); + }else if (sessionId == null) + { + return null; + } SteamAuthToken? accessToken = null; - if (accessTokenToken == null || accessTokenToken.Type == JTokenType.Null) + + if (jAccessToken == null || jAccessToken.Type == JTokenType.Null) { accessToken = null; } - if (accessTokenToken is { Type: JTokenType.Object }) + else if (jAccessToken is { Type: JTokenType.Object }) { try { - accessToken = refreshTokenToken.ToObject(); + accessToken = jRefreshToken.ToObject(); } catch { // ignored } } - else if (accessTokenToken is { Type: JTokenType.String } && - SteamTokenHelper.TryParse(accessTokenToken.Value()!, out var token) && token.Type == SteamAccessTokenType.Mobile) + else if (jAccessToken is { Type: JTokenType.String } && + SteamTokenHelper.TryParse(jAccessToken.Value()!, out var token) && token.Type == SteamAccessTokenType.Mobile) { accessToken = token; } @@ -115,6 +130,21 @@ public partial class MafileSerializer //SessionData //TODO: after fixing the issue, reflect changes in the original library private static SteamAuthToken CreateInvalid(SteamId steamId) { - return new SteamAuthToken("invalid", steamId, UnixTimeStamp.FromDateTime(DateTime.Now - TimeSpan.FromSeconds(1)), SteamDomain.Community, SteamAccessTokenType.MobileRefresh); + return new SteamAuthToken("invalid", steamId, UnixTimeStamp.Zero, SteamDomain.Community, SteamAccessTokenType.MobileRefresh); + } + + private static string GenerateRandomHex(int byteLength = 12) + { + byte[] randomBytes = new byte[byteLength]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomBytes); + } + + var hex = new StringBuilder(byteLength * 2); + foreach (var b in randomBytes) + hex.Append($"{b:x2}"); + + return hex.ToString(); } } \ No newline at end of file diff --git a/SteamLibForked/Utility/MaFiles/MafileSerializer_Write.cs b/SteamLibForked/Utility/MaFiles/MafileSerializer_Write.cs index 29c65ad..dace869 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer_Write.cs +++ b/SteamLibForked/Utility/MaFiles/MafileSerializer_Write.cs @@ -63,7 +63,7 @@ public static string SerializeLegacy(MobileData mobileData, Formatting formattin ? null : new { - AccesToken = ext.SessionData?.MobileToken?.Token, + AccessToken = ext.SessionData?.MobileToken?.Token, steamLoginSecure = ext.SessionData?.MobileToken?.SignedToken, RefreshToken = ext.SessionData?.RefreshToken.Token, SteamID = ext.SessionData?.SteamId.Steam64.Id, diff --git a/SteamLibForked/Utility/Models/Enumeration.cs b/SteamLibForked/Utility/Models/Enumeration.cs deleted file mode 100644 index a5dbc9d..0000000 --- a/SteamLibForked/Utility/Models/Enumeration.cs +++ /dev/null @@ -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 GetAll() where T : Enumeration => - // typeof(T).GetFields(BindingFlags.Public | - // BindingFlags.Static | - // BindingFlags.DeclaredOnly) - // .Select(f => f.GetValue(null)) - // .Cast(); - - public static IEnumerable GetAll() where T : Enumeration - { - var res = typeof(T).GetFields(BindingFlags.Public | - BindingFlags.Static | - BindingFlags.DeclaredOnly) - .Select(f => f.GetValue(null)) - .Cast(); - 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); -} \ No newline at end of file diff --git a/SteamLibForked/Utility/Utilities.cs b/SteamLibForked/Utility/Utilities.cs index 76c7046..85ba7dd 100644 --- a/SteamLibForked/Utility/Utilities.cs +++ b/SteamLibForked/Utility/Utilities.cs @@ -4,25 +4,20 @@ namespace SteamLib.Utility; public static class Utilities //TODO: refactor { + private static readonly Regex RegexInt = new("\"success\":\\s?(\\d*)", RegexOptions.Compiled); + private static readonly Regex RegexBool = new("\"success\":\\s?(\\w*)", RegexOptions.Compiled); public static int GetSuccessCode(string response) { - Regex regexInt = new("\"success\":(\\d*)"); - var matchInt = regexInt.Match(response); - + var length = Math.Min(response.Length, 100); + var matchInt = RegexInt.Match(response, 0, length); if (!string.IsNullOrEmpty(matchInt.Groups[1].Value)) return Convert.ToInt32(matchInt.Groups[1].Value); - Regex regexBool = new("\"success\":(\\w*)"); - var matchBool = regexBool.Match(response); + + var matchBool = RegexBool.Match(response, 0, length); if (!string.IsNullOrEmpty(matchBool.Groups[1].Value)) return bool.Parse(matchBool.Groups[1].Value) ? 1 : 0; return 0; } - - public static FormUrlEncodedContent ToFormContent(this IDictionary dic) => new(dic); //TODO: убрать и зарефакторить - - public static int DivideByDecimal(this int i, decimal value) => (int) (i / value); - public static int DivideByDouble(this int i, double value) => (int)(i / value); - } \ No newline at end of file diff --git a/SteamLibForked/Web/Models/GlobalMarketInfo/MarketWalletSchema.cs b/SteamLibForked/Web/Models/GlobalMarketInfo/MarketWalletSchema.cs index f11a5f9..c0384a1 100644 --- a/SteamLibForked/Web/Models/GlobalMarketInfo/MarketWalletSchema.cs +++ b/SteamLibForked/Web/Models/GlobalMarketInfo/MarketWalletSchema.cs @@ -26,32 +26,32 @@ public class MarketWalletSchema public int WalletFeeMinimum { get; set; } [JsonProperty("wallet_fee_percent")] - [JsonConverter(typeof(StringToDoubleConverter))] - public double WalletFeePercent { get; set; } + [JsonConverter(typeof(DecimalToStringConverter))] + public decimal WalletFeePercent { get; set; } [JsonProperty("wallet_publisher_fee_percent_default")] - [JsonConverter(typeof(StringToDoubleConverter))] - public double WalletPublisherFeePercentDefault { get; set; } + [JsonConverter(typeof(DecimalToStringConverter))] + public decimal WalletPublisherFeePercentDefault { get; set; } [JsonProperty("wallet_fee_base")] [JsonConverter(typeof(IntToStringConverter))] public int WalletFeeBase { get; set; } [JsonProperty("wallet_balance")] - [JsonConverter(typeof(IntToStringConverter))] - public int WalletBalance { get; set; } + [JsonConverter(typeof(LongToStringConverter))] + public long WalletBalance { get; set; } [JsonProperty("wallet_delayed_balance")] - [JsonConverter(typeof(IntToStringConverter))] - public int WalletDelayedBalance { get; set; } + [JsonConverter(typeof(LongToStringConverter))] + public long WalletDelayedBalance { get; set; } [JsonProperty("wallet_max_balance")] - [JsonConverter(typeof(IntToStringConverter))] - public int WalletMaxBalance { get; set; } + [JsonConverter(typeof(LongToStringConverter))] + public long WalletMaxBalance { get; set; } [JsonProperty("wallet_trade_max_balance")] - [JsonConverter(typeof(IntToStringConverter))] - public int WalletTradeMaxBalance { get; set; } + [JsonConverter(typeof(LongToStringConverter))] + public long WalletTradeMaxBalance { get; set; } [JsonProperty("success")] public int Success { get; set; } diff --git a/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs b/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs index 2b4f2fd..630dca8 100644 --- a/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs +++ b/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs @@ -14,6 +14,14 @@ public static class MobileConfirmationScrapper {"You are not set up to receive mobile confirmations", LoadConfirmationsError.NotSetupToReceiveConfirmations} }; + /// + /// + /// + /// + /// + /// + /// + /// public static List Scrap(string response) { ConfirmationsJson conf; @@ -28,27 +36,23 @@ public static List Scrap(string response) if (conf.NeedAuth) { - throw new SessionExpiredException(); + throw new SessionInvalidException(); } - if (conf is {Success: false, Message: not null}) + if (conf.Success == false) { var error = LoadConfirmationsError.Unknown; - if (ErrorMessages.TryGetValue(conf.Message, out var e)) + if (conf.Message != null && ErrorMessages.TryGetValue(conf.Message, out var e)) { error = e; } - throw new CantLoadConfirmationsException(conf.Message) + throw new CantLoadConfirmationsException("Error while loading confirmations") { - Error = error + Error = error, + ErrorMessage = conf.Message, + ErrorDetails = conf.Detail }; - - } - - if (conf.Success == false) - { - throw new UnsupportedResponseException(response); } @@ -80,6 +84,32 @@ private static Confirmation GetConcrete(ConfirmationJson json) }; } + + //BUG: ArgumentOutOfRangeException + //{ + // "success": true, + // "conf": + // [ + // { + // "type": 2, + // "type_name": "Trade Offer", + // "id": "16072406079", + // "creator_id": "7458859849", + // "nonce": "6768661787786520856", + // "creation_time": 1728760256, + // "cancel": "Revoke Offer", + // "accept": "Confirm Offer", + // "icon": null, + // "multi": false, + // "headline": "Error loading trade details", + // "summary": + // [ + // "" + // ], + // "warn": null + // } + // ] + // } private static TradeConfirmation GetTradeConfirmation(ConfirmationJson json) { diff --git a/SteamLibForked/Web/SteamWebApi[folder]/SteamApi_Market.cs b/SteamLibForked/Web/SteamWebApi[folder]/SteamApi_Market.cs index 6f0a404..894bf31 100644 --- a/SteamLibForked/Web/SteamWebApi[folder]/SteamApi_Market.cs +++ b/SteamLibForked/Web/SteamWebApi[folder]/SteamApi_Market.cs @@ -5,7 +5,7 @@ namespace SteamLib.Web; -public static partial class SteamWebApi +public static class SteamWebApi { /// /// Login is not required diff --git a/changelog/1.5.3.html b/changelog/1.5.3.html new file mode 100644 index 0000000..870f052 --- /dev/null +++ b/changelog/1.5.3.html @@ -0,0 +1,96 @@ + + + + + + Changelog + + + + +
+ +
+
Version 1.5.3
+
28.10.2024
+
+ - NEWS: Official Telegram group now available! Join us at t.me/nebulaauth
+ - UPDATE: Auto-confirmation system reworked. Each account now has separate timer settings.
+ - UPDATE: Added a hyperlink to the troubleshooting guide in the "Linking" window.
+ - UPDATE: Added support for SOCKS4 and SOCKS5 proxies (untested).
+ - UPDATE: Added an experimental option to ignore timer errors during "Patch Tuesday" (Steam maintenance period).
+ - UI: Added "Copy maFile" button in the maFile context menu.
+ - UI: Introduced additional hotkeys in the maFile context menu.
+ - UI: Added "Copy RCode" button after account linking.
+ - UI: Auto-confirmation timers now use icons instead of text chips.
+ - UI: Temporarily removed the "Allow auto-update" (without confirmation) setting due to functionality issues in the AU library.
+ - UI: Minor design and localization enhancements.
+ - UI-FIX: Updated "by achies" hyperlink to direct to the repository instead of the user page "achiez/".
+ - UI-FIX: Resolved lag issue in the code progress bar.
+ - FIX: Mafiles missing a "SessionID" (e.g., from Steam Desktop Authenticator) can now be imported without re-login. If absent, SessionID is generated on the client side.
+ - FIX: Errors during confirmation retrieval are now properly localized and displayed.
+ - FIX: Corrected typo in "AccesToken" when serializing in legacy mode.
+ - FIX: Resolved issue where the startup error message was not displayed correctly, leaving it blank when the app couldn’t synchronize time.
+ - DEV: Major code clean-up and refactoring.
+ - DEV: Updated dependencies and SteamLib libraries.
+
+
+
+ + + \ No newline at end of file