From 1e65cd4a066cd85f2e31723d05b4399a8b483a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=B2=D0=B8=D0=B4=20=D0=A7=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D0=BE=D0=BF=D1=8F=D1=82=D0=BE=D0=B2?= Date: Wed, 16 Oct 2024 16:31:53 +0300 Subject: [PATCH] 1.5.3 progress. Added multi-confirmation and code refactorings NebulaAuth: - Added MAAC and PortableMaClient for multi-confirmations - Code clean-ups, removed unused classes, refactoring of old files - SteamLib updated - Localization and UI updates - Dependcies updated to the last version LegacyConverter: - Added mode to decrypt mafiles --- .../EncryptedManifest.cs | 48 ++++ NebulaAuth.LegacyConverter/Program.cs | 195 +++++++++++++--- NebulaAuth.LegacyConverter/SDADecryptor.cs | 203 +++++++++++++++++ NebulaAuth.sln.DotSettings | 3 +- NebulaAuth/App.xaml | 5 +- NebulaAuth/App.xaml.cs | 2 +- .../AnyMafilesToVisibilityConverter.cs | 10 +- .../BackgroundImageVisibleConverter.cs | 6 +- .../Background/BackgroundSourceConverter.cs | 4 +- NebulaAuth/Converters/CoefficientConverter.cs | 9 +- .../Converters/OnOffBoolTextConverter.cs | 23 -- .../PortableMaClientStatusToColorConverter.cs | 24 ++ NebulaAuth/Converters/ProxyTextConverter.cs | 12 +- .../Converters/ReverseBooleanConverter.cs | 20 +- NebulaAuth/Converters/ValueConverterGroup.cs | 4 +- NebulaAuth/Core/SnackbarController.cs | 2 + NebulaAuth/Core/TrayManager.cs | 22 +- NebulaAuth/MainWindow.xaml | 100 ++++++--- NebulaAuth/MainWindow.xaml.cs | 40 +--- .../Model/Comparers/ProxyDataComparer.cs | 31 --- .../Model/Entities/LoginConfirmationResult.cs | 4 +- NebulaAuth/Model/Entities/Mafile.cs | 29 ++- NebulaAuth/Model/Exceptions/CantAlignTime.cs | 7 - .../Model/MAAC/MultiAccountAutoConfirmer.cs | 128 +++++++++++ NebulaAuth/Model/MAAC/PortableMaClient.cs | 172 ++++++++++++++ NebulaAuth/Model/MaClient.cs | 71 ++---- NebulaAuth/Model/PHandler.cs | 19 +- NebulaAuth/Model/ProxyStorage.cs | 9 +- NebulaAuth/Model/SessionHandler.cs | 8 +- NebulaAuth/Model/Settings.cs | 1 - NebulaAuth/Model/Shell.cs | 2 +- NebulaAuth/Model/Storage.cs | 6 +- NebulaAuth/NebulaAuth.csproj | 8 +- NebulaAuth/Theme/Brushes.xaml | 8 +- NebulaAuth/Theme/FontScaleWindow.cs | 4 +- NebulaAuth/Theme/Palette.xaml.cs | 16 +- NebulaAuth/Theme/WindowStyle/NativeMethods.cs | 5 +- .../Theme/WindowStyle/WindowChromeHelper.cs | 5 +- .../WindowChromeRenderedBehavior.cs | 17 +- .../Theme/WindowStyle/WindowStyle.xaml.cs | 61 +++-- NebulaAuth/Utility/ExceptionHandler.cs | 2 +- NebulaAuth/View/ConfirmationTemplates.xaml | 56 ++--- .../View/Dialogs/ConfirmCancelDialog.xaml.cs | 31 ++- NebulaAuth/View/Dialogs/LoginAgainDialog.xaml | 3 +- .../View/Dialogs/LoginAgainDialog.xaml.cs | 19 +- .../Dialogs/LoginAgainOnImportDialog.xaml | 2 +- .../Dialogs/LoginAgainOnImportDialog.xaml.cs | 19 +- .../View/Dialogs/SetCryptPasswordDialog.xaml | 6 +- .../Dialogs/SetCryptPasswordDialog.xaml.cs | 19 +- NebulaAuth/View/Dialogs/WaitLoginDialog.xaml | 6 +- .../View/Dialogs/WaitLoginDialog.xaml.cs | 119 +++++----- NebulaAuth/View/LinkerView.xaml | 13 +- NebulaAuth/View/LinkerView.xaml.cs | 19 +- NebulaAuth/View/ProxyManagerView.xaml | 25 +-- NebulaAuth/View/ProxyManagerView.xaml.cs | 19 +- NebulaAuth/View/SettingsView.xaml | 20 +- NebulaAuth/View/SettingsView.xaml.cs | 27 +-- NebulaAuth/View/UpdaterView.xaml | 6 +- NebulaAuth/View/UpdaterView.xaml.cs | 19 +- NebulaAuth/ViewModel/MainVM.cs | 34 +-- NebulaAuth/ViewModel/MainVM_Code.cs | 33 ++- NebulaAuth/ViewModel/MainVM_Confirmations.cs | 39 ++-- NebulaAuth/ViewModel/MainVM_File.cs | 28 +-- NebulaAuth/ViewModel/MainVM_Groups.cs | 3 +- NebulaAuth/ViewModel/MainVM_Localization.cs | 17 -- NebulaAuth/ViewModel/MainVM_MAAC.cs | 118 ++++++++++ NebulaAuth/ViewModel/MainVM_Other.cs | 10 + NebulaAuth/ViewModel/MainVM_Proxy.cs | 11 +- NebulaAuth/ViewModel/MainVM_Timer.cs | 141 ------------ NebulaAuth/ViewModel/Other/LinkAccountVM.cs | 8 +- NebulaAuth/ViewModel/Other/SettingsVM.cs | 10 +- NebulaAuth/ViewModel/Other/UpdaterVM.cs | 2 +- NebulaAuth/localization.loc.json | 99 ++++---- .../Api/Mobile/SteamAuthenticatorLinkerApi.cs | 8 +- SteamLibForked/Api/Mobile/SteamMobileApi.cs | 36 +-- .../Api/Mobile/SteamMobileAuthenticatorApi.cs | 55 +++++ .../Api/Mobile/SteamMobileConfirmationsApi.cs | 74 +++--- .../Authentication/AdmissionHelper.cs | 8 + .../Authentication/LoginV2/LoginV2Executor.cs | 6 +- SteamLibForked/Core/HealthMonitor.cs | 212 ++++++++++-------- ...firmationConsumer.cs => ILoginConsumer.cs} | 0 .../Core/StatusCodes/SteamStatusCode.cs | 4 +- .../Core/{Constants.cs => SteamConstants.cs} | 7 - .../CantLoadConfirmationsException.cs | 3 - .../Mobile/BadMobileCookiesException.cs | 12 +- .../Exceptions/SessionExpiredException.cs | 16 +- .../Exceptions/SessionInvalidException.cs | 10 +- .../MobileSteamClient/MobileData.cs | 1 + SteamLibForked/ProtoCore/Enums/EResult.cs | 8 +- .../ProtoCore/Exceptions/EResutException.cs | 4 - .../Exceptions/UnknownEResultException.cs | 13 +- .../Services/AuthenticationService.cs | 2 +- SteamLibForked/SteamLibForked.csproj | 12 +- .../SteamAuthenticatorLinker.cs | 2 +- .../Concrete[folder]/TradeConfirmation.cs | 1 - SteamLibForked/Utility/EncryptionHelper.cs | 11 +- .../Utility/MaFiles/DeserializedMafileData.cs | 2 +- .../Utility/MaFiles/LegacyMafile.cs | 3 + .../Utility/MaFiles/MafileSerializer.cs | 2 +- SteamLibForked/Utility/Models/Enumeration.cs | 44 ---- SteamLibForked/Utility/Utilities.cs | 17 +- .../GlobalMarketInfo/MarketWalletSchema.cs | 24 +- .../JSON/MobileConfirmationScrapper.cs | 28 ++- .../SteamWebApi[folder]/SteamApi_Market.cs | 2 +- changelog/1.5.3.html | 14 +- 105 files changed, 1827 insertions(+), 1140 deletions(-) create mode 100644 NebulaAuth.LegacyConverter/EncryptedManifest.cs create mode 100644 NebulaAuth.LegacyConverter/SDADecryptor.cs delete mode 100644 NebulaAuth/Converters/OnOffBoolTextConverter.cs create mode 100644 NebulaAuth/Converters/PortableMaClientStatusToColorConverter.cs delete mode 100644 NebulaAuth/Model/Comparers/ProxyDataComparer.cs create mode 100644 NebulaAuth/Model/MAAC/MultiAccountAutoConfirmer.cs create mode 100644 NebulaAuth/Model/MAAC/PortableMaClient.cs delete mode 100644 NebulaAuth/ViewModel/MainVM_Localization.cs create mode 100644 NebulaAuth/ViewModel/MainVM_MAAC.cs delete mode 100644 NebulaAuth/ViewModel/MainVM_Timer.cs create mode 100644 SteamLibForked/Api/Mobile/SteamMobileAuthenticatorApi.cs rename SteamLibForked/Core/Interfaces/{IConfirmationConsumer.cs => ILoginConsumer.cs} (100%) rename SteamLibForked/Core/{Constants.cs => SteamConstants.cs} (98%) delete mode 100644 SteamLibForked/Utility/Models/Enumeration.cs 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..5a5ee83 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 TO_STORE_FOLDER = "ConvertedMafiles"; - if (Path.Exists(path) == false) + if (Directory.Exists(TO_STORE_FOLDER) == false) { - Console.WriteLine($"NOT VALID PATH: '{path}'"); - continue; + Directory.CreateDirectory(TO_STORE_FOLDER); } - Console.WriteLine("Reading: " + path); - try + else { - var text = File.ReadAllText(path); - var maf = MafileSerializer.Deserialize(text, out _); - var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented); - var name = Path.GetFileNameWithoutExtension(path); - Write(legacy, name); + var isEmpty = Directory.GetFiles(TO_STORE_FOLDER).Length == 0; + Console.ForegroundColor = ConsoleColor.Yellow; + if (!isEmpty) + { + Console.WriteLine( + "WARNING! 'ConverterdMafiles' folder is not empty. Please backup data from it and then continue."); + Console.ResetColor(); + Console.WriteLine("Press Y to continue"); + while (Console.ReadKey(true).Key != ConsoleKey.Y) + { + } + } + } + + var decryptMode = false; + while (true) + { + Console.WriteLine("Press 'D' to select decrypt mode. Press 'C' to convert mode. Press ESC to exit"); + var key = Console.ReadKey(true); + switch (key.Key) + { + case ConsoleKey.D: + decryptMode = true; + break; + case ConsoleKey.C: + break; + case ConsoleKey.Escape: + return; + default: + continue; + } - Console.WriteLine("DONE: " + name); - } - catch (Exception ex) - { - Console.WriteLine($"ERROR: {ex.Message}"); - Console.WriteLine(ex); - } - finally - { - Console.WriteLine("-----------------------------------------"); + break; } + Manifest? manifest = null; + string? password = null; + if (decryptMode) + { + var files = Directory.GetFiles(currentPath, "manifest.json"); + var manifestPath = files.FirstOrDefault(); + if (manifestPath == null) + { + Console.WriteLine("No manifest.json found in current directory"); + return; + } + + var manifestText = File.ReadAllText(manifestPath); + try + { + manifest = JsonConvert.DeserializeObject(manifestText)!; + } + catch (Exception ex) + { + Console.WriteLine("Can't read manifest: " + ex); + return; + } + + if (manifest.Encrypted == false) + { + Console.WriteLine("Manifest is not encrypted"); + return; + } + + while (true) + { + Console.WriteLine("Please enter your encryption password: "); + password = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(password)) + continue; + + break; + } + } + + + Console.WriteLine(currentPath); + + + foreach (var path in args) + { + + if (Path.Exists(path) == false) + { + Console.WriteLine($"NOT VALID PATH: '{path}'"); + continue; + } + + Console.WriteLine("Reading: " + path); + try + { + var text = File.ReadAllText(path); + if (decryptMode) + { + var fileName = Path.GetFileName(path); + text = DecryptMafile(fileName, text); + if (text == null) + { + Console.WriteLine(path + " not found in manifest. Skipped"); + continue; + } + } + + var maf = MafileSerializer.Deserialize(text, out _); + var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented); + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + Write(legacy, fileNameWithoutExtension); + + Console.WriteLine("DONE: " + fileNameWithoutExtension); + } + catch (Exception ex) + { + Console.WriteLine($"ERROR: {ex.Message}"); + Console.WriteLine(ex); + } + finally + { + Console.WriteLine("-----------------------------------------"); + } + + + } + + //Local Functions + + void Write(string maf, string name) + { + + var path = Path.Combine(TO_STORE_FOLDER, name + "_legacy.mafile"); + File.WriteAllText(path, maf); + } + + string? DecryptMafile(string fileName, string cipherText) + { + if (password == null) return null; + var entry = manifest?.Entries.FirstOrDefault(x => x.Filename.EqualsIgnoreCase(fileName)); + if (entry == null) + { + return null; + } + + var iv = entry.EncryptionIv; + var salt = entry.EncryptionSalt; + return SDAEncryptor.DecryptData(password, salt, iv, cipherText); + + + } } - -Console.WriteLine("Press any key to exit..."); -Console.ReadKey(); - - -void Write(string maf, string name) +finally { - - var path = Path.Combine(name + "_legacy.mafile"); - File.WriteAllText(path, maf); + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); } \ 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.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..0b017b6 100644 --- a/NebulaAuth/App.xaml +++ b/NebulaAuth/App.xaml @@ -1,9 +1,7 @@ pack://application:,,,/Fonts/Roboto/#Roboto Symbols - + @@ -34,7 +32,6 @@ - diff --git a/NebulaAuth/App.xaml.cs b/NebulaAuth/App.xaml.cs index 55f0d69..b99aa64 100644 --- a/NebulaAuth/App.xaml.cs +++ b/NebulaAuth/App.xaml.cs @@ -8,7 +8,7 @@ using CodingSeb.Localization; namespace NebulaAuth; -public partial class App : Application +public partial class App { protected override void OnStartup(StartupEventArgs e) { diff --git a/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs b/NebulaAuth/Converters/AnyMafilesToVisibilityConverter.cs index 9d20158..c4ce2e3 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,11 +19,11 @@ public class AnyMafilesToVisibilityConverter : IValueConverter return Visibility.Visible; } - EverAnyMafiles = true; + _everAnyMafiles = true; return Visibility.Collapsed; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } 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..0f9f89d 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,7 +21,7 @@ public class BackgroundSourceConverter : IValueConverter return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg")); } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } 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/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..65b5a73 --- /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 class ProxyTextConverter : IValueConverter return $"{p.Id}: {p.Data.Address}:{p.Data.Port}"; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - throw new NotImplementedException(); + throw new NotSupportedException(); } } public class ProxyDataTextConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is not ProxyData p) { @@ -36,8 +36,8 @@ public class ProxyDataTextConverter : IValueConverter return $"{p.Address}:{p.Port}"; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - throw new NotImplementedException(); + throw new NotSupportedException(); } } \ 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/ValueConverterGroup.cs b/NebulaAuth/Converters/ValueConverterGroup.cs index 4d7b5fd..b7c2a31 100644 --- a/NebulaAuth/Converters/ValueConverterGroup.cs +++ b/NebulaAuth/Converters/ValueConverterGroup.cs @@ -9,12 +9,12 @@ 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(); } 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 class SnackbarController /// /// /// + /// /// 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 class TrayManager _notifyIcon.ContextMenuStrip = contextMenu; MainWindow.StateChanged += MainWindow_StateChanged; + Init = true; } private static void OnExitClick(object? sender, EventArgs e) @@ -57,6 +60,7 @@ public static class TrayManager private static void ShowMainWindow() { + if (!Init) return; MainWindow.Show(); MainWindow.WindowState = WindowState.Normal; _notifyIcon.Visible = false; @@ -64,13 +68,15 @@ public static class TrayManager private static void HideMainWindow() { - if(IsEnabled == false) return; + if (!Init) return; + if (IsEnabled == false) return; _notifyIcon.Visible = true; MainWindow.Hide(); } private static void ExitApplication() { + if (!Init) return; _notifyIcon.Dispose(); Application.Current.Shutdown(); } diff --git a/NebulaAuth/MainWindow.xaml b/NebulaAuth/MainWindow.xaml index 451a6ff..bd0e21f 100644 --- a/NebulaAuth/MainWindow.xaml +++ b/NebulaAuth/MainWindow.xaml @@ -25,7 +25,7 @@ - @@ -44,27 +44,27 @@ - + - - + + - + - + - - + + @@ -85,7 +85,15 @@ - + @@ -118,10 +126,17 @@ - - - - + + + + + + + + + + + @@ -134,10 +149,10 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + - + @@ -184,7 +226,11 @@ - + + + + + @@ -193,16 +239,10 @@ - - - - - - + @@ -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.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(); - } - - public ConfirmCancelDialog(string msg) - { - InitializeComponent(); - ConfirmTextBlock.Text = msg; - } + InitializeComponent(); } -} + + 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 a7a870a..802a56e 100644 --- a/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml +++ b/NebulaAuth/View/Dialogs/LoginAgainDialog.xaml @@ -7,7 +7,6 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other" xmlns:model="clr-namespace:NebulaAuth.Model" - xmlns:entities="clr-namespace:NebulaAuth.Model.Entities" mc:Ignorable="d" theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" Foreground="WhiteSmoke" @@ -25,7 +24,7 @@ - + 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 fba974b..670ae8d 100644 --- a/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml +++ b/NebulaAuth/View/Dialogs/LoginAgainOnImportDialog.xaml @@ -27,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 af40e3f..57ae647 100644 --- a/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml +++ b/NebulaAuth/View/Dialogs/SetCryptPasswordDialog.xaml @@ -2,11 +2,9 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:NebulaAuth.View.Dialogs" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other" xmlns:theme="clr-namespace:NebulaAuth.Theme" - xmlns:model="clr-namespace:NebulaAuth.Model" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" Foreground="WhiteSmoke" @@ -21,7 +19,7 @@ - + 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 System.Windows.Media.Imaging; 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(); - } - - 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 () => - { - var image = await LoadImage(stream); - CaptchaImage.Source = image; - try - { - 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; - - var image = new BitmapImage(); - - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = ms; - image.EndInit(); - await stream.DisposeAsync(); - return image; - } - - private void CancelButton_OnClick(object sender, RoutedEventArgs e) - { - _tcs.SetCanceled(); - } - - private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e) - { - if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return; - var oldTcs = _tcs; - _tcs = new TaskCompletionSource(); - oldTcs.SetResult(CaptchaTB.Text); - } + InitializeComponent(); } -} + + 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 () => + { + var image = await LoadImage(stream); + CaptchaImage.Source = image; + try + { + 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; + + var image = new BitmapImage(); + + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = ms; + image.EndInit(); + await stream.DisposeAsync(); + return image; + } + + private void CancelButton_OnClick(object sender, RoutedEventArgs e) + { + _tcs.SetCanceled(); + } + + private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e) + { + if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return; + var oldTcs = _tcs; + _tcs = new TaskCompletionSource(); + oldTcs.SetResult(CaptchaTB.Text); + } +} \ No newline at end of file diff --git a/NebulaAuth/View/LinkerView.xaml b/NebulaAuth/View/LinkerView.xaml index f07df2d..e0c3e84 100644 --- a/NebulaAuth/View/LinkerView.xaml +++ b/NebulaAuth/View/LinkerView.xaml @@ -2,8 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:NebulaAuth.View" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other" mc:Ignorable="d" @@ -66,15 +65,15 @@ - - + + - + @@ -122,7 +121,7 @@ - + diff --git a/NebulaAuth/View/LinkerView.xaml.cs b/NebulaAuth/View/LinkerView.xaml.cs index 5887bc3..d958b17 100644 --- a/NebulaAuth/View/LinkerView.xaml.cs +++ b/NebulaAuth/View/LinkerView.xaml.cs @@ -1,15 +1,12 @@ -using System.Windows.Controls; +namespace NebulaAuth.View; -namespace NebulaAuth.View +/// +/// Логика взаимодействия для LinkerView.xaml +/// +public partial class LinkerView { - /// - /// Логика взаимодействия для LinkerView.xaml - /// - public partial class LinkerView : UserControl + public LinkerView() { - public LinkerView() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/NebulaAuth/View/ProxyManagerView.xaml b/NebulaAuth/View/ProxyManagerView.xaml index 27b64ec..469611a 100644 --- a/NebulaAuth/View/ProxyManagerView.xaml +++ b/NebulaAuth/View/ProxyManagerView.xaml @@ -2,12 +2,11 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:NebulaAuth.View" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800" + d:DesignHeight="500" d:DesignWidth="400" Foreground="WhiteSmoke" FontFamily="{md:MaterialDesignFont}" MinHeight="500" @@ -15,12 +14,6 @@ MaxHeight="550" d:DataContext="{d:DesignInstance other:ProxyManagerVM}" Background="{DynamicResource WindowBackground}"> - - - - @@ -36,11 +29,11 @@ - + @@ -54,7 +47,7 @@ @@ -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..8fa8aea 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,11 +52,11 @@ + 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 1a28438..07b33c0 100644 --- a/NebulaAuth/View/UpdaterView.xaml +++ b/NebulaAuth/View/UpdaterView.xaml @@ -2,11 +2,9 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:NebulaAuth.View" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other" - xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" mc:Ignorable="d" Foreground="WhiteSmoke" FontFamily="{materialDesign:MaterialDesignFont}" @@ -29,7 +27,7 @@ - + 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..abe3917 100644 --- a/NebulaAuth/ViewModel/MainVM.cs +++ b/NebulaAuth/ViewModel/MainVM.cs @@ -5,6 +5,7 @@ using NebulaAuth.Core; using NebulaAuth.Model; using NebulaAuth.Model.Entities; using NebulaAuth.Utility; +using NebulaAuth.View.Dialogs; using SteamLib.Exceptions; using SteamLib.SteamMobile; using System; @@ -13,7 +14,6 @@ using System.Collections.Specialized; using System.Linq; using System.Threading; using System.Threading.Tasks; -using NebulaAuth.View.Dialogs; namespace NebulaAuth.ViewModel; @@ -32,11 +32,12 @@ public partial class MainVM : ObservableObject } private Mafile? _selectedMafile; + public bool IsMafileSelected => SelectedMafile != null; + public MainVM() { CreateCodeTimer(); - _confirmTimer = new Timer(ConfirmByTimer, null, TimeSpan.FromSeconds(_timerCheckSeconds), TimeSpan.FromSeconds(_timerCheckSeconds)); Proxies = new ObservableCollection(ProxyStorage.Proxies.Select(kvp => new MaProxy(kvp.Key, kvp.Value))); Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged; @@ -47,24 +48,26 @@ public partial class MainVM : ObservableObject if (Storage.DuplicateFound > 0) { SnackbarController.SendSnackbar( - GetLocalizationOrDefault("DuplicateMafilesFound") + " " + Storage.DuplicateFound, + GetLocalization("DuplicateMafilesFound") + " " + Storage.DuplicateFound, TimeSpan.FromSeconds(4)); } } - + private void SetMafile(Mafile? mafile) { if (mafile != SelectedMafile) { _selectedMafile = mafile; OnPropertyChanged(nameof(SelectedMafile)); + OnPropertyChanged(nameof(TradeTimerEnabled)); + OnPropertyChanged(nameof(MarketTimerEnabled)); MaClient.SetAccount(mafile); OnPropertyChanged(nameof(ConfirmationsVisible)); - if (Settings.DisableTimersOnChange) OffTimer(dispatcher: false); SetCurrentProxy(); OnPropertyChanged(nameof(IsDefaultProxy)); if (mafile != null) Code = SteamGuardCodeGenerator.GenerateCode(mafile.SharedSecret); + OnPropertyChanged(nameof(IsMafileSelected)); } } @@ -81,14 +84,14 @@ public partial class MainVM : ObservableObject { return; } - + var password = loginAgainVm.Password; var waitDialog = new WaitLoginDialog(); var wait = DialogHost.Show(waitDialog); try { await MaClient.LoginAgain(SelectedMafile, password, loginAgainVm.SavePassword, waitDialog); - SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin")); + SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin")); } catch (LoginException ex) { @@ -118,7 +121,7 @@ public partial class MainVM : ObservableObject try { await MaClient.RefreshSession(SelectedMafile); - SnackbarController.SendSnackbar(GetLocalizationOrDefault("SessionRefreshed")); + SnackbarController.SendSnackbar(GetLocalization("SessionRefreshed")); } catch (Exception ex) when (ExceptionHandler.Handle(ex)) { @@ -129,7 +132,6 @@ public partial class MainVM : ObservableObject [RelayCommand] public async Task LinkAccount() { - OffTimer(false); await DialogsController.ShowLinkerDialog(); } @@ -140,16 +142,16 @@ public partial class MainVM : ObservableObject if (selectedMafile == null) return; if (string.IsNullOrWhiteSpace(selectedMafile.RevocationCode)) { - SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalizationOrDefault("MissingRCode")); + SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalization("MissingRCode")); return; } try { - if (await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmRemovingAuthenticator"))) + if (await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmRemovingAuthenticator"))) { var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(selectedMafile), selectedMafile); SnackbarController.SendSnackbar( - result.Success ? GetLocalizationOrDefault("AuthenticatorRemoved") : GetLocalizationOrDefault("AuthenticatorNotRemoved")); + result.Success ? GetLocalization("AuthenticatorRemoved") : GetLocalization("AuthenticatorNotRemoved")); if (result.Success) { @@ -176,17 +178,17 @@ public partial class MainVM : ObservableObject try { - LoginConfirmationResult res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile); + var res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile); if (res.Success) { - SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("ConfirmLoginSuccess")} {res.IP} ({res.Country})"); + SnackbarController.SendSnackbar($"{GetLocalization("ConfirmLoginSuccess")} {res.IP} ({res.Country})"); } else { string msg = res.Error switch { - LoginConfirmationError.NoRequests => GetLocalizationOrDefault("ConfirmLoginFailedNoRequests"), - LoginConfirmationError.MoreThanOneRequest => GetLocalizationOrDefault("ConfirmLoginFailedMoreThanOneRequest"), //TODO + LoginConfirmationError.NoRequests => GetLocalization("ConfirmLoginFailedNoRequests"), + LoginConfirmationError.MoreThanOneRequest => GetLocalization("ConfirmLoginFailedMoreThanOneRequest"), //TODO _ => throw new ArgumentOutOfRangeException() }; SnackbarController.SendSnackbar(msg); diff --git a/NebulaAuth/ViewModel/MainVM_Code.cs b/NebulaAuth/ViewModel/MainVM_Code.cs index 1090104..2fe953d 100644 --- a/NebulaAuth/ViewModel/MainVM_Code.cs +++ b/NebulaAuth/ViewModel/MainVM_Code.cs @@ -1,7 +1,12 @@ using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using NebulaAuth.Core; +using NebulaAuth.Model; using SteamLib.SteamMobile; +using System; using System.Diagnostics.CodeAnalysis; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; @@ -11,7 +16,7 @@ public partial class MainVM { private Timer _codeTimer; [ObservableProperty] private double _codeProgress; - [ObservableProperty] private string _code; + [ObservableProperty] private string _code = "Code"; [MemberNotNull(nameof(_codeTimer))] @@ -20,7 +25,7 @@ public partial class MainVM _codeTimer = new Timer(UpdateCode, null, 0, 1000); } - + private void UpdateCode(object? state = null) { @@ -43,4 +48,28 @@ public partial class MainVM if (c != null) Code = c; }, DispatcherPriority.Background, code); } + + [RelayCommand(AllowConcurrentExecutions = false)] + private async Task CopyCode() + { + var selectedMafile = SelectedMafile; + if (selectedMafile == null) return; + try + { + Clipboard.SetText(Code); + Code = LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied"); + } + catch (Exception ex) + { + Shell.Logger.Error(ex); + } + finally + { + await Task.Delay(200); + selectedMafile = SelectedMafile; + if (selectedMafile != null) + Code = SteamGuardCodeGenerator.GenerateCode(selectedMafile.SharedSecret); + } + + } } \ 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 partial class MainVM //Confirmations _confirmationsLoadedForMafile = maf; OnPropertyChanged(nameof(ConfirmationsVisible)); Confirmations.Clear(); - var marketConfirmations = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction).ToList(); + var marketConfirmations = conf + .Where(c => c.ConfType == ConfirmationType.MarketSellTransaction) + .Cast() + .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 partial class MainVM //Confirmations } } + [RelayCommand] + private Task Confirm(Confirmation? confirmation) + { + if (SelectedMafile == null || confirmation == null) return Task.CompletedTask; + return SendConfirmation(SelectedMafile, confirmation, true); + } + [RelayCommand] + private Task Cancel(Confirmation? confirmation) + { + if (SelectedMafile == null || confirmation == null) return Task.CompletedTask; + return SendConfirmation(SelectedMafile, confirmation, false); + } + private async Task SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm) { bool result; try { - if (confirmation is MarketMultiConfirmation multi) + if (confirmation is MarketMultiConfirmation multi) { result = await MaClient.SendMultipleConfirmation(mafile, multi.Confirmations, confirm); } @@ -86,20 +102,7 @@ public partial class MainVM //Confirmations } else { - SnackbarController.SendSnackbar(GetLocalizationOrDefault("ConfirmationError")); + SnackbarController.SendSnackbar(GetLocalization("ConfirmationError")); } } - - [RelayCommand] - private Task Confirm(Confirmation? confirmation) - { - if (SelectedMafile == null || confirmation == null) return Task.CompletedTask; - return SendConfirmation(SelectedMafile, confirmation, true); - } - [RelayCommand] - private Task Cancel(Confirmation? confirmation) - { - if (SelectedMafile == null || confirmation == null) return Task.CompletedTask; - return SendConfirmation(SelectedMafile, confirmation, false); - } } \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_File.cs b/NebulaAuth/ViewModel/MainVM_File.cs index b0c9746..f4b7b7f 100644 --- a/NebulaAuth/ViewModel/MainVM_File.cs +++ b/NebulaAuth/ViewModel/MainVM_File.cs @@ -38,7 +38,7 @@ public partial class MainVM //File //TODO: Refactor } if (mafilePath != null) { - path = $"/select, \"{mafilePath}\""; ; + path = $"/select, \"{mafilePath}\""; } try @@ -65,7 +65,7 @@ public partial class MainVM //File //TODO: Refactor var fs = openFileDialog.ShowDialog(); if (fs != true) return Task.CompletedTask; var path = openFileDialog.FileName; - return AddMafile(new[] { path }); + return AddMafile([path]); } public async Task AddMafile(string[] path) @@ -87,7 +87,7 @@ public partial class MainVM //File //TODO: Refactor } catch (IOException) { - confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmMafileOverwrite")); + confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmMafileOverwrite")); if (confirmOverwrite == true) { @@ -115,24 +115,24 @@ public partial class MainVM //File //TODO: Refactor } else { - SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("MafileImportError")} {Path.GetFileName(str)}{GetLocalizationOrDefault("MissingSessionInMafile")}", TimeSpan.FromSeconds(4)); + SnackbarController.SendSnackbar($"{GetLocalization("MafileImportError")} {Path.GetFileName(str)}{GetLocalization("MissingSessionInMafile")}", TimeSpan.FromSeconds(4)); } } } - var msg = GetLocalizationOrDefault("Import"); + var msg = GetLocalization("Import"); if (added > 0) { - msg += $" {GetLocalizationOrDefault("ImportAdded")} {added}."; + msg += $" {GetLocalization("ImportAdded")} {added}."; } if (notAdded > 0) { - msg += $" {GetLocalizationOrDefault("ImportSkipped")} {notAdded}."; + msg += $" {GetLocalization("ImportSkipped")} {notAdded}."; } if (errors > 0) { - msg += $" {GetLocalizationOrDefault("ImportErrors")} {errors}."; + msg += $" {GetLocalization("ImportErrors")} {errors}."; } SnackbarController.SendSnackbar(msg, TimeSpan.FromSeconds(2)); } @@ -155,7 +155,7 @@ public partial class MainVM //File //TODO: Refactor try { await MaClient.LoginAgain(data, password, loginAgainVm.SavePassword, waitDialog); - SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin")); + SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin")); } catch (LoginException ex) { @@ -188,7 +188,7 @@ public partial class MainVM //File //TODO: Refactor { if (SelectedMafile == null) return; var confirm = - await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("RemoveMafileConfirmation")); + await DialogsController.ShowConfirmCancelDialog(GetLocalization("RemoveMafileConfirmation")); if (!confirm) return; try @@ -197,12 +197,12 @@ public partial class MainVM //File //TODO: Refactor } catch (UnauthorizedAccessException) { - SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRemoveAlreadyExist")); + SnackbarController.SendSnackbar(GetLocalization("CantRemoveAlreadyExist")); } catch (Exception ex) { SnackbarController.SendSnackbar( - $"{GetLocalizationOrDefault("CantRemoveMafile")} {ex.Message}"); + $"{GetLocalization("CantRemoveMafile")} {ex.Message}"); } } @@ -218,7 +218,7 @@ public partial class MainVM //File //TODO: Refactor } [RelayCommand] - private async Task CopyMafileFromBuffer() + private async Task PasteMafilesFromClipboard() { StringCollection files; try @@ -249,7 +249,7 @@ public partial class MainVM //File //TODO: Refactor try { Clipboard.SetText(maf.AccountName); - SnackbarController.SendSnackbar(GetLocalizationOrDefault("LoginCopied")); + SnackbarController.SendSnackbar(GetLocalization("LoginCopied")); return; } catch (Exception ex) 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 @@ public partial class MainVM //Groups 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..110fce1 --- /dev/null +++ b/NebulaAuth/ViewModel/MainVM_MAAC.cs @@ -0,0 +1,118 @@ +using CommunityToolkit.Mvvm.Input; +using NebulaAuth.Core; +using NebulaAuth.Model; +using NebulaAuth.Model.Entities; +using NebulaAuth.Model.MAAC; + +namespace NebulaAuth.ViewModel; + +public partial class MainVM //MAAC +{ + public bool MaacDisplay + { + get => _maacDisplay; + set + { + if (SetProperty(ref _maacDisplay, value)) + { + SwitchMAACDispaly(value); + } + } + } + private bool _maacDisplay; + + public bool MarketTimerEnabled + { + get => SelectedMafile?.LinkedClient?.AutoConfirmMarket ?? false; + set => SetMarketTimer(value); + } + public bool TradeTimerEnabled + { + get => SelectedMafile?.LinkedClient?.AutoConfirmTrades ?? false; + set => SetTradeTimer(value); + } + + public int TimerCheckSeconds + { + get => Settings.Instance.TimerSeconds; + set => SetTimer(value); + } + + private void SetMarketTimer(bool value) + { + var selectedMafile = SelectedMafile; + if (selectedMafile == null) return; + if (value && selectedMafile.LinkedClient == null) + { + MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile); + } + if (!value && selectedMafile is { LinkedClient.AutoConfirmTrades: false }) + { + MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile); + } + + if (selectedMafile.LinkedClient != null) + { + selectedMafile.LinkedClient.AutoConfirmMarket = value; + } + } + + private void SetTradeTimer(bool value) + { + var selectedMafile = SelectedMafile; + if (selectedMafile == null) return; + if (value && selectedMafile.LinkedClient == null) + { + MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile); + } + + if (!value && selectedMafile is { LinkedClient.AutoConfirmMarket: false }) + { + MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile); + } + + if (selectedMafile.LinkedClient != null) + { + selectedMafile.LinkedClient.AutoConfirmTrades = value; + } + } + + private void SetTimer(int value) + { + var timerCheckSeconds = Settings.TimerSeconds; + if (timerCheckSeconds == value) return; + if (timerCheckSeconds < 10) + { + timerCheckSeconds = 10; //Guard + } + if (value < 10) + { + value = timerCheckSeconds; + SnackbarController.SendSnackbar(GetLocalization("TimerTooFast")); + } + Settings.TimerSeconds = value; + OnPropertyChanged(nameof(TimerCheckSeconds)); + if (timerCheckSeconds != TimerCheckSeconds) + SnackbarController.SendSnackbar(GetLocalization("TimerChanged")); + } + + [RelayCommand] + public void RemoveFromMAAC(object? maf) + { + if (maf is not Mafile mafile) return; + MultiAccountAutoConfirmer.RemoveFromConfirm(mafile); + } + + + private void SwitchMAACDispaly(bool newValue) + { + if (newValue) + { + MaFiles = MultiAccountAutoConfirmer.Clients; + } + else + { + PerformQuery(); + } + } +} \ No newline at end of file diff --git a/NebulaAuth/ViewModel/MainVM_Other.cs b/NebulaAuth/ViewModel/MainVM_Other.cs index af830ba..8f3d860 100644 --- a/NebulaAuth/ViewModel/MainVM_Other.cs +++ b/NebulaAuth/ViewModel/MainVM_Other.cs @@ -2,11 +2,21 @@ using System; using System.Windows; using NebulaAuth.View.Dialogs; +using NebulaAuth.Core; namespace NebulaAuth.ViewModel; public partial class MainVM //Other { + + private const string LOC_PATH = "MainVM"; + + private static string GetLocalization(string key) + { + return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key); + } + + private void SessionHandlerOnLoginCompleted(object? sender, EventArgs e) { var currentSession = DialogHost.GetDialogSession(null); 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 NebulaAuth.Model.Entities; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; -using NebulaAuth.Model.Comparers; namespace NebulaAuth.ViewModel; @@ -23,16 +22,17 @@ public partial class MainVM { OnPropertyChanged(nameof(IsDefaultProxy)); OnProxyChanged(); - }; + } } } private MaProxy? _selectedProxy; [ObservableProperty] private bool _proxyExist = true; - public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null; + public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null && SelectedMafile != null; private bool _handleProxyChange; + //TODO: Refactor this method private void SetCurrentProxy() { if (ReferenceEquals(_selectedProxy, SelectedMafile?.Proxy) == false && _selectedProxy?.Equals(SelectedMafile?.Proxy) == false) @@ -41,6 +41,7 @@ public partial class MainVM if (SelectedMafile == null) { SelectedProxy = null; + ProxyExist = true; return; } if (SelectedMafile.Proxy == null) @@ -52,7 +53,7 @@ public partial class MainVM var existed = Proxies.FirstOrDefault(p => p.Id == SelectedMafile.Proxy.Id); - if (existed == null || ProxyDataComparer.Equal(existed.Data, SelectedMafile.Proxy.Data) == false) + if (existed == null || existed.Data.Equals(SelectedMafile.Proxy.Data) == false) { SelectedProxy = SelectedMafile.Proxy; } @@ -127,7 +128,7 @@ public partial class MainVM var canSave = Storage.ValidateCanSave(data); if (!canSave) { - SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRetrieveSteamIDToUpdate")); + SnackbarController.SendSnackbar(GetLocalization("CantRetrieveSteamIDToUpdate")); } return canSave; } 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 4c92a3b..db861d0 100644 --- a/NebulaAuth/ViewModel/Other/LinkAccountVM.cs +++ b/NebulaAuth/ViewModel/Other/LinkAccountVM.cs @@ -61,7 +61,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum private TaskCompletionSource _emailConfTcs = new(); private TaskCompletionSource _linkCodeTcs = new(); - private bool isLinkStarted; + private bool _isLinkStarted; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ProceedCommand))] @@ -179,12 +179,12 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum #endregion - if (isLinkStarted) + if (_isLinkStarted) goto linkStarted; try { - isLinkStarted = true; + _isLinkStarted = true; var linkOptions = new LinkOptions(Client, LoginV2Executor.NullConsumer, this, null, this, this, backupHandler: Backup, Logger2); _linker = new SteamAuthenticatorLinker(linkOptions); @@ -313,7 +313,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum IsLogin = false; IsFieldVisible = true; IsEmailCode = false; - isLinkStarted = false; + _isLinkStarted = false; IsPhoneNumber = false; IsEmailConfirmation = false; CanProceed = true; diff --git a/NebulaAuth/ViewModel/Other/SettingsVM.cs b/NebulaAuth/ViewModel/Other/SettingsVM.cs index 1408eb5..df315da 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; @@ -119,10 +114,9 @@ public partial class SettingsVM : ObservableObject [RelayCommand] - public void SetPassword() + private void SetPassword() { - PHandler.SetPassword(Password); - Settings.IsPasswordSet = PHandler.IsPasswordSet; + Settings.IsPasswordSet = PHandler.SetPassword(Password); } } \ 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 @@ using CommunityToolkit.Mvvm.ComponentModel; 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 9e398c1..023a1ba 100644 --- a/NebulaAuth/localization.loc.json +++ b/NebulaAuth/localization.loc.json @@ -172,14 +172,19 @@ } }, "TradeTimerHint": { - "en": "Trade", - "ru": "Трейд", - "ua": "Трейд" + "en": "Auto-confirm trades", + "ru": "Автоподтверждение трейдов", + "ua": "Автопідтвердження трейдів" }, "MarketTimerHint": { - "en": "Market", - "ru": "Маркет", - "ua": "Маркет" + "en": "Auto-confirm market", + "ru": "Автоподтверждение маркета", + "ua": "Автопідтвердження маркету" + }, + "ShowAutoConfirmAccountsHint": { + "en": "Show accounts with auto-confirmation enabled.", + "ru": "Показать аккаунты с включенным автоподтверждением.", + "ua": "Показати акаунти з включеним автопідтвердженням." } }, "LeftPart": { @@ -256,6 +261,11 @@ "en": "Remove from group", "ru": "Удалить из группы", "ua": "Видалити з групи" + }, + "RemoveFromMAAC": { + "en": "Turn off auto-confirm", + "ru": "Выкл. авто-подтверждение", + "ua": "Вимкнути авто-підтвердження" } }, "Proxy": { @@ -300,11 +310,6 @@ "ua": "Без фону" } }, - "DisableTimersOnSwitch": { - "en": "Disable timers on account switch", - "ru": "Отключать таймеры при смене аккаунта", - "ua": "Вимикати таймери при зміні акаунта" - }, "MinimizeToTray": { "en": "Minimize to tray", "ru": "Сворачивать в трей", @@ -605,26 +610,6 @@ "en": "Can't remove mafile:", "ua": "Не вдалося видалити (перемістити) мафайл:" }, - "TimerConfirmed": { - "ru": "Авто: подтверждено ", - "en": "Auto: confirmed ", - "ua": "Авто: підтверджено " - }, - "TimerNotConfirmed": { - "ru": "Авто: подтверждение не сработало", - "en": "Auto: confirmation was unsuccessful", - "ua": "Авто: підтвердження не спрацювало" - }, - "TimerSessionError": { - "ru": "Авто: необходимо обновить сессию или перелогиниться", - "en": "Auto: need to refresh session or relogin", - "ua": "Авто: необхідно оновити сесію або перелогінитися" - }, - "TimerPrefix": { - "ru": "Авто: ", - "en": "Auto: ", - "ua": "Авто: " - }, "TimerTooFast": { "ru": "Слишком быстрый таймер.", "en": "Too fast timer.", @@ -897,9 +882,9 @@ "ua": "Не вдається згенерувати коди" }, "InvalidStateWithStatus2": { - "ru": "Неверное состояние (InvalidState) со статусом 2. Попробуйте привязку с помощью СМС. Если СМС придет, но ошибка повторится. Нужно попробовать еще раз.", - "en": "Invalid state (InvalidState) with status 2. Try to attach with SMS. If SMS will come, but error will repeat. You need to try again.", - "ua": "Невірний стан (InvalidState) зі статусом 2. Спробуйте прив'язати з допомогою СМС. Якщо СМС прийде, але помилка повториться. Потрібно спробувати ще раз." + "ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.", + "en": "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.", + "ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна." }, "GeneralFailure": { "ru": "General Failure", @@ -909,15 +894,15 @@ } }, "ExceptionHandler": { - "SessionExpiredException": { - "ru": "Сессия истекла. Попробуйте обновить ее через меню", - "en": "Session expired. Try to refresh it through menu", - "ua": "Сесія закінчилася. Спробуйте оновити її через меню" - }, "SessionInvalidException": { - "ru": "Сессия невалидна. Нужно залогиниться заново", - "en": "Session invalid. Need to login again", - "ua": "Сесія невалідна. Потрібно залогінитися знову" + "ru": "Сессия истекла. Попробуйте обновить ее через меню или залогиниться заново", + "en": "Session expired. Try to refresh it through menu or login again", + "ua": "Сесія прострочена. Спробуйте оновити її через меню або залогінитися знову" + }, + "SessionExpiredException": { + "ru": "Сессия полностью истекла. Нужно залогиниться заново", + "en": "Session fully expired. Need to login again", + "ua": "Сесія повніст'ю прострочена'. Потрібно залогінитися знову" }, "TaskCanceledException": { "ru": "Произошла отмена операции", @@ -1027,11 +1012,33 @@ "ua": "На телефон {0} було відправлено СМС" }, "EnterPhoneNumber": { - "ru": "Введите номер телефона (без знака '+' и др. символов)", - "en": "Enter phone number (without '+' and other symbols)", - "ua": "Введіть номер телефону (без знака '+' та ін. символів)" + "ru": "Введите номер телефона (без знака '+' и др. символов). По-желанию.", + "en": "Enter phone number (without '+' and other symbols). Optional", + "ua": "Введіть номер телефону (без знака '+' та ін. символів). За бажанням" } - } + }, + "MAAC": { + "TimerConfirmed": { + "ru": "Авто: подтверждено ", + "en": "Auto: confirmed ", + "ua": "Авто: підтверджено " + }, + "TimerNotConfirmed": { + "ru": "Авто: подтверждение не сработало", + "en": "Auto: confirmation was unsuccessful", + "ua": "Авто: підтвердження не спрацювало" + }, + "TimerSessionError": { + "ru": "необходимо обновить сессию или перелогиниться", + "en": "need to refresh session or relogin", + "ua": "необхідно оновити сесію або перелогінитися" + }, + "TimerPrefix": { + "ru": "Авто ", + "en": "Auto ", + "ua": "Авто " + } + } }, "CantAlignTimeError": { "ru": "Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log", 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 class SteamAuthenticatorLinkerApi }; 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 class SteamAuthenticatorLinkerApi { var validateSmsReq = new FinalizeAddAuthenticator_Request { - SteamId = data.SteamId.Steam64.ToUlong(), - AuthenticatorTime = (ulong) time, + SteamId = data.SteamId.Steam64.ToUlong(), + AuthenticatorTime = (ulong)time, ConfirmationCode = confirmationCode, ValidateConfirmationCode = true }; @@ -182,7 +182,7 @@ public static class SteamAuthenticatorLinkerApi while (i < 5) { i++; - var resp = await client.PostProto(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..0a143fb 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,13 @@ using SteamLib.ProtoCore; using SteamLib.ProtoCore.Enums; using SteamLib.ProtoCore.Exceptions; using SteamLib.ProtoCore.Services; +using System.Net; namespace SteamLib.Api.Mobile; [PublicAPI] public static class SteamMobileApi -//TODO: cancellation token -//TODO: HealthMonitor { private const string GENERATE_ACCESS_TOKEN = @@ -27,9 +25,10 @@ public static class SteamMobileApi /// /// /// + /// /// Refreshed AccessToken /// - public static async Task RefreshJwt(HttpClient client, string refreshToken, long steamId) + public static async Task RefreshJwt(HttpClient client, string refreshToken, long steamId, CancellationToken cancellationToken = default) { var req = new GenerateAccessTokenForApp_Request { @@ -40,17 +39,17 @@ public static class SteamMobileApi try { - var resp = await client.PostProto(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 +59,18 @@ public static class SteamMobileApi }; var content = new FormUrlEncodedContent(data); - var resp = await client.PostAsync(SteamConstants.STEAM_COMMUNITY + "steamguard/phoneajax", content); - var respContent = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); - var json = JObject.Parse(respContent); - return json["has_phone"]!.Value(); + 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 +81,15 @@ public static class SteamMobileApi }; 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..3939e8c 100644 --- a/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs +++ b/SteamLibForked/Api/Mobile/SteamMobileConfirmationsApi.cs @@ -1,42 +1,56 @@ -using SteamLib.Core; -using SteamLib.Exceptions; -using SteamLib.SteamMobile.Confirmations; -using SteamLib.SteamMobile; -using System.Net; -using AchiesUtilities.Web.Extensions; -using SteamLib.Web.Scrappers.JSON; +using AchiesUtilities.Web.Extensions; +using JetBrains.Annotations; +using SteamLib.Account; +using SteamLib.Core; using SteamLib.Core.StatusCodes; +using SteamLib.Exceptions; +using SteamLib.Exceptions.General; +using SteamLib.SteamMobile; +using SteamLib.SteamMobile.Confirmations; using SteamLib.Utility; +using SteamLib.Web.Scrappers.JSON; +using System.Net; namespace SteamLib.Api.Mobile; +[PublicAPI] public static class SteamMobileConfirmationsApi { private const string CONF = SteamConstants.STEAM_COMMUNITY + "mobileconf"; private const string CONF_OP = SteamConstants.STEAM_COMMUNITY + "mobileconf/ajaxop"; private const string MULTI_CONF_OP = SteamConstants.STEAM_COMMUNITY + "mobileconf/multiajaxop"; - public static async Task> 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); + + var respStr = await resp.Content.ReadAsStringAsync(cancellationToken); if (resp.StatusCode == HttpStatusCode.Redirect) { - throw new SessionExpiredException("Mobile session expired"); + throw new SessionPermanentlyExpiredException("Mobile session expired"); } resp.EnsureSuccessStatusCode(); + try + { + return MobileConfirmationScrapper.Scrap(respStr); + } + catch (Exception ex) + when (ex is not SessionPermanentlyExpiredException) + { + var e = new UnsupportedResponseException(respStr, ex); + SteamLibErrorMonitor.LogErrorResponse(respStr, e); + throw e; - return HealthMonitor.LogOnException(respStr, MobileConfirmationScrapper.Scrap, typeof(SessionExpiredException)); + } } - public static async Task 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 +64,8 @@ public static class SteamMobileConfirmationsApi 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 +73,7 @@ public static class SteamMobileConfirmationsApi 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 +86,25 @@ public static class SteamMobileConfirmationsApi } var content = new FormUrlEncodedContent(query); - var resp = await client.PostAsync(MULTI_CONF_OP, content); - var respStr = await resp.Content.ReadAsStringAsync(); - var successCode = HealthMonitor.LogOnException(respStr, () => SteamStatusCode.Translate(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..36bde8e 100644 --- a/SteamLibForked/Authentication/AdmissionHelper.cs +++ b/SteamLibForked/Authentication/AdmissionHelper.cs @@ -210,6 +210,14 @@ public static class AdmissionHelper .Value; } + public static void ClearAllCookies(this CookieContainer container) + { + foreach (Cookie allCookie in container.GetAllCookies()) + { + allCookie.Expired = true; + } + } + #endregion 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 @@ public class LoginV2Executor /// - /// 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 @@ public class LoginV2Executor 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) + /// + /// Logger for capturing and analyzing error-related issues. + /// + public static ILogger? MonitorLogger { get; set; } + + + /// + /// 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) { - FatalLogger?.LogCritical(ex, "Unexpected response detected:\n{response}", response); + if (ex is UnsupportedResponseException) + { + MonitorLogger?.LogError(ex, "Unsupported response detected"); + } + else + { + MonitorLogger?.LogError(ex, "Unsupported response detected: {response}", response); + } + } + /// + /// 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) + { + var e = new UnsupportedResponseException(response, ex); + ExceptionDispatchInfo.SetCurrentStackTrace(e); + LogErrorResponse(response, e); + throw e; + } - internal static T LogOnException(string resp, Func del) + [DoesNotReturn] + internal static T MapAndThrowException(string response, Exception ex) + { + var e = new UnsupportedResponseException(response, ex); + ExceptionDispatchInfo.SetCurrentStackTrace(e); + LogErrorResponse(response, e); + throw e; + } + + /// + /// 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(); } + catch (UnsupportedResponseException ex) + { + LogErrorResponse(resp, ex); + throw; + } catch (Exception ex) { - LogUnexpected(resp, ex); - throw; + MapAndThrowException(resp, ex); + return default; //Not reachable } } - internal static T LogOnException(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 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) + catch (UnsupportedResponseException ex) { - LogUnexpected(resp, ex); + LogErrorResponse(resp, ex); throw; } - } - internal static T LogOnException(string resp, Func del, params Type[] exceptExceptions) - { - try - { - return del(); - } catch (Exception ex) - when (exceptExceptions.Any(t => t == ex.GetType()) == false) { - LogUnexpected(resp, ex); - throw; + MapAndThrowException(resp, ex); + return default; //Not reachable } } - internal static T LogOnException(string resp, Func del, params Type[] exceptExceptions) - { - try - { - return del(resp); - } - catch (Exception ex) - when (exceptExceptions.Any(t => t == ex.GetType()) == false) - { - LogUnexpected(resp, ex); - throw; - } - } - - internal static T LogOnException(string resp, Func del, Func logPredicate) - { - try - { - return del(); - } - catch (Exception ex) - when (logPredicate(ex)) - { - LogUnexpected(resp, ex); - throw; - } - } - - internal static T LogOnException(string resp, Func del, Func logPredicate) - { - try - { - return del(resp); - } - catch (Exception ex) - when (logPredicate(ex)) - { - LogUnexpected(resp, ex); - throw; - } - } - - 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 (UnsupportedResponseException ex) + { + LogErrorResponse(resp, ex); + throw; } catch (Exception ex) { - LogUnexpected(resp, ex); - throw; + MapAndThrowException(resp, ex); } } - 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; - } - } - } \ 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..4baa632 100644 --- a/SteamLibForked/Exceptions/CantLoadConfirmationsException.cs +++ b/SteamLibForked/Exceptions/CantLoadConfirmationsException.cs @@ -7,9 +7,6 @@ public class CantLoadConfirmationsException : Exception public CantLoadConfirmationsException() { } public CantLoadConfirmationsException(string message) : base(message) { } public CantLoadConfirmationsException(string message, Exception inner) : base(message, inner) { } - protected CantLoadConfirmationsException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } 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 class BadMobileCookiesException : Exception 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 class EResultException : Exception { 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/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..e48bb71 100644 --- a/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs +++ b/SteamLibForked/SteamMobile/AuthenticatorLinker/SteamAuthenticatorLinker.cs @@ -40,7 +40,7 @@ public class SteamAuthenticatorLinker if (data.RefreshToken.IsExpired) { Logger?.LogError("Session expired"); - throw new SessionExpiredException(SessionExpiredException.SESSION_EXPIRED_MSG); + throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG); } var accessToken = data.GetMobileToken(); 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 class EncryptionHelper public static string ToBase64EncryptedPassword(string keyExp, string keyMod, string password) { // RSA Encryption. - RSACryptoServiceProvider rsa = new(); + using RSACryptoServiceProvider rsa = new(); RSAParameters rsaParameters = new() { Exponent = HexStringToByteArray(keyExp), @@ -34,8 +34,7 @@ public static class EncryptionHelper { var hashedBytes = GenerateConfirmationHashBytes(time, identitySecret, tag); var encodedData = Convert.ToBase64String(hashedBytes, Base64FormattingOptions.None); - var hash = encodedData; - return hash; + return encodedData; } public static byte[] GenerateConfirmationHashBytes(long time, string identitySecret, string tag = "conf") { @@ -64,10 +63,8 @@ public static class EncryptionHelper } Array.Copy(Encoding.UTF8.GetBytes(tag), 0, array, 8, n2 - 8); - var hmacGenerator = new HMACSHA1 - { - Key = decode - }; + using var hmacGenerator = new HMACSHA1(); + hmacGenerator.Key = decode; var hashedData = hmacGenerator.ComputeHash(array); return hashedData; } 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..cfff982 100644 --- a/SteamLibForked/Utility/MaFiles/MafileSerializer.cs +++ b/SteamLibForked/Utility/MaFiles/MafileSerializer.cs @@ -55,7 +55,7 @@ public static partial class MafileSerializer var sharedSecretToken = GetTokenOrThrow(j, nameof(MobileData.SharedSecret), unusedProperties, "sharedsecret", "shared_secret", "shared"); var identitySecretToken = GetTokenOrThrow(j, nameof(MobileData.IdentitySecret), unusedProperties, "identitysecret", "identity_secret", "identity"); var deviceIdToken = GetTokenOrThrow(j, nameof(MobileData.DeviceId), unusedProperties, "deviceid", "device_id", "device"); - + //TODO: see MobileData.DeviceId ToDo var sharedSecret = GetBase64(nameof(MobileData.SharedSecret), sharedSecretToken); var identitySecret = GetBase64(nameof(MobileData.IdentitySecret), identitySecretToken); 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..a5f4a97 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..3ccef7d 100644 --- a/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs +++ b/SteamLibForked/Web/Scrappers/JSON/MobileConfirmationScrapper.cs @@ -28,7 +28,7 @@ public static class MobileConfirmationScrapper if (conf.NeedAuth) { - throw new SessionExpiredException(); + throw new SessionPermanentlyExpiredException(); } if (conf is {Success: false, Message: not null}) @@ -80,6 +80,32 @@ public static class MobileConfirmationScrapper }; } + + //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 @@ using SteamLib.Web.Scrappers.HTML; 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 index 3fe36d5..bbe178d 100644 --- a/changelog/1.5.3.html +++ b/changelog/1.5.3.html @@ -47,10 +47,10 @@ padding: 0 15px; } - .description ul { + .description ul { list-style: inside square; padding: 0; - } + } @media only screen and (max-width: 600px) { .changelog-container { @@ -69,8 +69,16 @@
Version 1.5.3
DATE
+ - NEWS: Official telegram group created. Join us at t.me/nebulaauth
+ - UPDATE: Auto-confirmation was reworked. Now every account has separate timer settings
- UPDATE: Added hyperlink to troubleshooting guide in "Linking" window
- - UI: Minor design and localization improvements + - UPDATE: Added "switch auto-confirm view" button that shows enabled aut-confirm accounts
+ - UI: Added "Copy RCode" button after linking account
+ - UI: Auto-confirm timers now use icons instead of text-chips
+ - UI: Minor design and localization improvements
+ - UI: Hyperlink "by achies" now leads to "achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies" instead of "achiez/"
+ - DEV: Big code clean-ups and refactoring
+ - DEV: Updated dependency and SteamLib libraries