1.5.3 progress. Added multi-confirmation and code refactorings

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

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

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