mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 06:14:31 +00:00
Merge pull request #5 from achiez/pre-release
Pre release 1.5.3 merge to master
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
@@ -1,51 +1,172 @@
|
||||
using Newtonsoft.Json;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.LegacyConverter;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Utility.MaFiles;
|
||||
|
||||
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
if (currentPath != null)
|
||||
Environment.CurrentDirectory = currentPath;
|
||||
|
||||
|
||||
Console.WriteLine(currentPath);
|
||||
foreach (var path in args)
|
||||
try
|
||||
{
|
||||
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
if (currentPath != null)
|
||||
Environment.CurrentDirectory = currentPath;
|
||||
const string toStoreFolder = "ConvertedMafiles";
|
||||
|
||||
if (Path.Exists(path) == false)
|
||||
if (Directory.Exists(toStoreFolder) == false)
|
||||
{
|
||||
Console.WriteLine($"NOT VALID PATH: '{path}'");
|
||||
continue;
|
||||
Directory.CreateDirectory(toStoreFolder);
|
||||
}
|
||||
Console.WriteLine("Reading: " + path);
|
||||
try
|
||||
else
|
||||
{
|
||||
var text = File.ReadAllText(path);
|
||||
var maf = MafileSerializer.Deserialize(text, out _);
|
||||
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
Write(legacy, name);
|
||||
var isEmpty = Directory.GetFiles(toStoreFolder).Length == 0;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
if (!isEmpty)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"WARNING! 'ConverterdMafiles' folder is not empty. Please backup data from it and then continue.");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine("Press Y to continue");
|
||||
while (Console.ReadKey(true).Key != ConsoleKey.Y)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var decryptMode = false;
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("Press 'D' to select decrypt mode. Press 'C' to convert mode. Press ESC to exit");
|
||||
var key = Console.ReadKey(true);
|
||||
switch (key.Key)
|
||||
{
|
||||
case ConsoleKey.D:
|
||||
decryptMode = true;
|
||||
break;
|
||||
case ConsoleKey.C:
|
||||
break;
|
||||
case ConsoleKey.Escape:
|
||||
return;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine("DONE: " + name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {ex.Message}");
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.WriteLine("-----------------------------------------");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Manifest? manifest = null;
|
||||
string? password = null;
|
||||
if (decryptMode)
|
||||
{
|
||||
var files = Directory.GetFiles(currentPath, "manifest.json");
|
||||
var manifestPath = files.FirstOrDefault();
|
||||
if (manifestPath == null)
|
||||
{
|
||||
Console.WriteLine("No manifest.json found in current directory");
|
||||
return;
|
||||
}
|
||||
|
||||
var manifestText = File.ReadAllText(manifestPath);
|
||||
try
|
||||
{
|
||||
manifest = JsonConvert.DeserializeObject<Manifest>(manifestText)!;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Can't read manifest: " + ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (manifest.Encrypted == false)
|
||||
{
|
||||
Console.WriteLine("Manifest is not encrypted");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("Please enter your encryption password: ");
|
||||
password = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine(currentPath);
|
||||
|
||||
|
||||
foreach (var path in args)
|
||||
{
|
||||
|
||||
if (Path.Exists(path) == false)
|
||||
{
|
||||
Console.WriteLine($"NOT VALID PATH: '{path}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine("Reading: " + path);
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(path);
|
||||
if (decryptMode)
|
||||
{
|
||||
var fileName = Path.GetFileName(path);
|
||||
text = DecryptMafile(fileName, text);
|
||||
if (text == null)
|
||||
{
|
||||
Console.WriteLine(path + " not found in manifest. Skipped");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var maf = MafileSerializer.Deserialize(text, true, out _);
|
||||
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
|
||||
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
|
||||
Write(legacy, fileNameWithoutExtension);
|
||||
|
||||
Console.WriteLine("DONE: " + fileNameWithoutExtension);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {ex.Message}");
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.WriteLine("-----------------------------------------");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//Local Functions
|
||||
|
||||
void Write(string maf, string name)
|
||||
{
|
||||
|
||||
var path = Path.Combine(toStoreFolder, name + "_legacy.mafile");
|
||||
File.WriteAllText(path, maf);
|
||||
}
|
||||
|
||||
string? DecryptMafile(string fileName, string cipherText)
|
||||
{
|
||||
if (password == null) return null;
|
||||
var entry = manifest?.Entries.FirstOrDefault(x => x.Filename.EqualsIgnoreCase(fileName));
|
||||
if (entry == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var iv = entry.EncryptionIv;
|
||||
var salt = entry.EncryptionSalt;
|
||||
return SDAEncryptor.DecryptData(password, salt, iv, cipherText);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
|
||||
|
||||
void Write(string maf, string name)
|
||||
finally
|
||||
{
|
||||
|
||||
var path = Path.Combine(name + "_legacy.mafile");
|
||||
File.WriteAllText(path, maf);
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
|
||||
changelog\1.5.0.html = changelog\1.5.0.html
|
||||
changelog\1.5.1.html = changelog\1.5.1.html
|
||||
changelog\1.5.2.html = changelog\1.5.2.html
|
||||
changelog\1.5.3.html = changelog\1.5.3.html
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=MAAC/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
+1
-5
@@ -1,9 +1,7 @@
|
||||
<Application x:Class="NebulaAuth.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:NebulaAuth"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
xmlns:converters="clr-namespace:NebulaAuth.Converters"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:background="clr-namespace:NebulaAuth.Converters.Background"
|
||||
@@ -15,13 +13,12 @@
|
||||
<FontFamily x:Key="RobotoSymbols">pack://application:,,,/Fonts/Roboto/#Roboto Symbols</FontFamily>
|
||||
<converters:CoefficientConverter x:Key="CoefficientConverter"/>
|
||||
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter"/>
|
||||
<converters:OnOffBoolTextConverter x:Key="OnOffBoolTextConverter"/>
|
||||
<converters:SelectedProxyTextConverter x:Key="SelectedProxyTextConverter"/>
|
||||
<converters:ProxyTextConverter x:Key="ProxyTextConverter"/>
|
||||
<converters:ProxyDataTextConverter x:Key="ProxyDataTextConverter"/>
|
||||
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter"/>
|
||||
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
|
||||
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter"/>
|
||||
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter"/>
|
||||
<!-- Background converters-->
|
||||
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter"/>
|
||||
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter"/>
|
||||
@@ -34,7 +31,6 @@
|
||||
|
||||
|
||||
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="LightGreen" SecondaryColor="Purple" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignExtensions;component/Themes/Generic.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.snackbar.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.Dark.xaml" />
|
||||
|
||||
@@ -3,12 +3,11 @@ using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using CodingSeb.Localization;
|
||||
|
||||
namespace NebulaAuth;
|
||||
|
||||
|
||||
public partial class App : Application
|
||||
public partial class App
|
||||
{
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
@@ -25,11 +24,12 @@ public partial class App : Application
|
||||
var msg = ex.ToString();
|
||||
if (ex is CantAlignTimeException)
|
||||
{
|
||||
msg = Loc.Tr(LocManager.GetCodeBehind("CantAlignTimeError"));
|
||||
msg = LocManager.Get("CantAlignTimeError");
|
||||
}
|
||||
|
||||
MessageBox.Show(msg);
|
||||
MessageBox.Show(msg, "Error", MessageBoxButton.OK, MessageBoxImage.Stop, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly);
|
||||
throw;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace NebulaAuth.Converters;
|
||||
|
||||
public class AnyMafilesToVisibilityConverter : IValueConverter
|
||||
{
|
||||
private static bool EverAnyMafiles;
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
private static bool _everAnyMafiles;
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (EverAnyMafiles)
|
||||
if (_everAnyMafiles)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
@@ -19,12 +19,12 @@ public class AnyMafilesToVisibilityConverter : IValueConverter
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
EverAnyMafiles = true;
|
||||
_everAnyMafiles = true;
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,13 @@ namespace NebulaAuth.Converters.Background;
|
||||
|
||||
public class BackgroundImageVisibleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is not BackgroundMode.Color ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace NebulaAuth.Converters.Background;
|
||||
|
||||
public class BackgroundSourceConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is BackgroundMode.Custom)
|
||||
{
|
||||
@@ -21,8 +21,8 @@ public class BackgroundSourceConverter : IValueConverter
|
||||
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg"));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,16 @@ namespace NebulaAuth.Converters;
|
||||
[ValueConversion(typeof(double), typeof(double))]
|
||||
public class CoefficientConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return System.Convert.ToDouble(value) * System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return (double)value / (double)parameter;
|
||||
var first = value as double? ?? 0;
|
||||
var second = parameter as double? ?? 0;
|
||||
if (second == 0) second = 1;
|
||||
return first / second;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,6 @@ public class MultiCommandParameterConverter : IMultiValueConverter
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace NebulaAuth.Converters;
|
||||
|
||||
public class ProxyTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not MaProxy p)
|
||||
{
|
||||
@@ -18,15 +18,15 @@ public class ProxyTextConverter : IValueConverter
|
||||
return $"{p.Id}: {p.Data.Address}:{p.Data.Port}";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class ProxyDataTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not ProxyData p)
|
||||
{
|
||||
@@ -36,8 +36,8 @@ public class ProxyDataTextConverter : IValueConverter
|
||||
return $"{p.Address}:{p.Port}";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,23 @@ namespace NebulaAuth.Converters;
|
||||
|
||||
public class ReverseBooleanConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return !(bool)value;
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return !boolValue;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Value must be of type bool", nameof(value));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return !(bool)value;
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return !boolValue;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Value must be of type bool", nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using NebulaAuth.Model.Entities;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
public class SelectedProxyTextConverter : IMultiValueConverter
|
||||
{
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values[0] is not MaProxy proxy)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var proxyExist = (bool)values[1];
|
||||
return proxyExist ? $"{proxy.Id}: {proxy.Data.Address}" : "";
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@ public class ValueConverterGroup : List<IValueConverter>, IValueConverter
|
||||
{
|
||||
#region IValueConverter Members
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -18,6 +18,10 @@ public static class LocManager
|
||||
//Thread.CurrentThread.CurrentCulture= CultureInfo.GetCultureInfo(GetLanguageCode(language));
|
||||
}
|
||||
|
||||
public static string GetCurrentLanguageCode()
|
||||
{
|
||||
return Loc.Instance.CurrentLanguage;
|
||||
}
|
||||
|
||||
public static string GetLanguageCode(LocalizationLanguage language)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,9 @@ public class SnackbarController
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="duration">Default duration is 1 second</param>
|
||||
/// <param name="actionText">Default: 'OK'</param>
|
||||
public static void SendSnackbarWithButton(string text, string actionText = "OK", Action? action = null, TimeSpan? duration = null)
|
||||
{
|
||||
duration ??= GetSnackbarTime(text);
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
using System;
|
||||
using NebulaAuth.Model;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using NebulaAuth.Model;
|
||||
using Application = System.Windows.Application;
|
||||
|
||||
namespace NebulaAuth.Core;
|
||||
|
||||
public static class TrayManager
|
||||
{
|
||||
private static NotifyIcon _notifyIcon;
|
||||
private static NotifyIcon? _notifyIcon;
|
||||
private static readonly Window MainWindow = Application.Current.MainWindow!;
|
||||
public static bool IsEnabled => Settings.Instance.HideToTray;
|
||||
private static bool IsEnabled => Settings.Instance.HideToTray;
|
||||
|
||||
[MemberNotNullWhen(true, nameof(_notifyIcon))]
|
||||
private static bool Init { get; set; }
|
||||
|
||||
public static void InitializeTray()
|
||||
{
|
||||
_notifyIcon = new NotifyIcon();
|
||||
_notifyIcon.Text = "NebulaAuth";
|
||||
|
||||
Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
|
||||
var iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
|
||||
_notifyIcon.Icon = new Icon(iconStream);
|
||||
|
||||
|
||||
_notifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick;
|
||||
|
||||
var contextMenu = new ContextMenuStrip();
|
||||
@@ -32,6 +34,7 @@ public static class TrayManager
|
||||
_notifyIcon.ContextMenuStrip = contextMenu;
|
||||
|
||||
MainWindow.StateChanged += MainWindow_StateChanged;
|
||||
Init = true;
|
||||
}
|
||||
|
||||
private static void OnExitClick(object? sender, EventArgs e)
|
||||
@@ -57,6 +60,7 @@ public static class TrayManager
|
||||
|
||||
private static void ShowMainWindow()
|
||||
{
|
||||
if (!Init) return;
|
||||
MainWindow.Show();
|
||||
MainWindow.WindowState = WindowState.Normal;
|
||||
_notifyIcon.Visible = false;
|
||||
@@ -64,13 +68,15 @@ public static class TrayManager
|
||||
|
||||
private static void HideMainWindow()
|
||||
{
|
||||
if(IsEnabled == false) return;
|
||||
if (!Init) return;
|
||||
if (IsEnabled == false) return;
|
||||
_notifyIcon.Visible = true;
|
||||
MainWindow.Hide();
|
||||
}
|
||||
|
||||
private static void ExitApplication()
|
||||
{
|
||||
if (!Init) return;
|
||||
_notifyIcon.Dispose();
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
+92
-32
@@ -25,7 +25,7 @@
|
||||
</b:Interaction.Behaviors>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Command="{Binding Path=CopyMafileFromBufferCommand}"
|
||||
<KeyBinding Command="{Binding Path=PasteMafilesFromClipboardCommand}"
|
||||
Key="V"
|
||||
Modifiers="Control"/>
|
||||
</Window.InputBindings>
|
||||
@@ -44,27 +44,27 @@
|
||||
<TextBlock FontWeight="Bold" Foreground="#9a65b8" Text="{Tr MainWindow.Global.DragNDropHint}"/>
|
||||
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36" Kind="FileReplaceOutline" />
|
||||
</StackPanel>
|
||||
<ToolBarTray IsLocked="True" Grid.Row="0">
|
||||
<ToolBarTray IsLocked="True" Grid.Row="0" >
|
||||
<ToolBarTray.Background>
|
||||
<SolidColorBrush Opacity="0.6" Color="#FF1A1C25" />
|
||||
</ToolBarTray.Background>
|
||||
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.75" w:FontScaleWindow.ResizeFont="True">
|
||||
<Menu>
|
||||
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
||||
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Import}" Command="{Binding AddMafileCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
|
||||
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}" Command="{Binding OpenMafileFolderCommand}" />
|
||||
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}" Command="{Binding OpenSettingsDialogCommand}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Menu>
|
||||
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}" Command="{Binding LinkAccountCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}" Command="{Binding RemoveAuthenticatorCommand}"/>
|
||||
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
|
||||
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
|
||||
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Separator />
|
||||
@@ -85,7 +85,15 @@
|
||||
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}" CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
|
||||
</UIElement.InputBindings>
|
||||
</ComboBox>
|
||||
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}" MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}" md:ComboBoxAssist.ShowSelectedItem="False" SelectedItem="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
|
||||
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}"
|
||||
|
||||
MinWidth="80"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
|
||||
md:ComboBoxAssist.ShowSelectedItem="False"
|
||||
SelectedItem="{Binding SelectedProxy}"
|
||||
ItemsSource="{Binding Proxies}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type entities:MaProxy}">
|
||||
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
|
||||
@@ -118,10 +126,29 @@
|
||||
</Binding>
|
||||
</UIElement.Visibility>
|
||||
</md:PackIcon>
|
||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" ></md:PackIcon>
|
||||
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" IsChecked="{Binding TradeTimerEnabled}" Content="{Tr MainWindow.AppBar.TradeTimerHint}"/>
|
||||
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" IsChecked="{Binding MarketTimerEnabled}" Content="{Tr MainWindow.AppBar.MarketTimerHint}"/>
|
||||
<TextBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
|
||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding MarketTimerEnabled}" >
|
||||
<md:PackIcon Kind="ShoppingCart"/>
|
||||
<FrameworkElement.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}" Command="{Binding Path=SwitchMAACOnGroupCommand}" CommandParameter="{StaticResource True}"/>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}" Command="{Binding Path=SwitchMAACOnAllCommand}" CommandParameter="{StaticResource True}"/>
|
||||
</ContextMenu>
|
||||
</FrameworkElement.ContextMenu>
|
||||
</ToggleButton>
|
||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding TradeTimerEnabled}" >
|
||||
<md:PackIcon Kind="AccountArrowRight" />
|
||||
<FrameworkElement.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}" Command="{Binding Path=SwitchMAACOnGroupCommand}" CommandParameter="{StaticResource False}"/>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}" Command="{Binding Path=SwitchMAACOnAllCommand}" CommandParameter="{StaticResource False}"/>
|
||||
</ContextMenu>
|
||||
</FrameworkElement.ContextMenu>
|
||||
</ToggleButton>
|
||||
<TextBox md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
|
||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}" HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}" Foreground="WhiteSmoke" VerticalAlignment="Center" Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
|
||||
<md:PackIcon Kind="Accounts" />
|
||||
</ToggleButton>
|
||||
</ToolBar>
|
||||
</ToolBarTray>
|
||||
<Grid Row="1">
|
||||
@@ -134,15 +161,52 @@
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0" w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="10,15,10,15" DisplayMemberPath="AccountName" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
|
||||
|
||||
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Grid.Row="0" Margin="10,15,0,0" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
|
||||
<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.InputBindings>
|
||||
<KeyBinding Key="C" Modifiers="Control" Command="{Binding DataContext.CopyLoginCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
|
||||
<KeyBinding Key="X" Modifiers="Control" Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
|
||||
</ListBox.InputBindings>
|
||||
<FrameworkElement.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}" Command="{Binding CopyLoginCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}"></MenuItem>
|
||||
<MenuItem InputGestureText="Ctrl+C" Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}" Command="{Binding CopyLoginCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem InputGestureText="Ctrl+X" Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}" Command="{Binding CopyMafileCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}" ItemsSource="{Binding Groups}">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource MaterialDesignMenuItem}" TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="MenuItem.Command" Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<Setter Property="MenuItem.CommandParameter">
|
||||
<Setter Property="Command" Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<Setter Property="CommandParameter">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{StaticResource MultiCommandParameterConverter}">
|
||||
<Binding />
|
||||
@@ -156,17 +220,15 @@
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}" Command="{Binding Path=RemoveGroupCommand}" CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
||||
</ContextMenu>
|
||||
</FrameworkElement.ContextMenu>
|
||||
|
||||
</ListBox>
|
||||
<Border Grid.Row="0" Margin="10" Padding="5" Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}, Mode=OneWay}">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="DarkGray" Opacity="0.5"/>
|
||||
</Border.Background>
|
||||
<TextBlock TextWrapping="WrapWithOverflow" FontSize="16" Text="{Tr MainWindow.Global.StartTip}">
|
||||
|
||||
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="WrapWithOverflow" FontSize="16" Text="{Tr MainWindow.Global.StartTip}"/>
|
||||
</Border>
|
||||
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}" md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10" md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}" md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10,5,10,10" md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
<md:Card Grid.Column="1" Margin="10,10,15,10" UniformCornerRadius="15">
|
||||
<Control.Background>
|
||||
@@ -184,7 +246,11 @@
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True" IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF" Style="{StaticResource MaterialDesignFilledTextBox}" Text="{Binding Code, FallbackValue=Code}" PreviewMouseDown="SteamGuard_DoubleClick" />
|
||||
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True" IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF" Style="{StaticResource MaterialDesignFilledTextBox}" Text="{Binding Code, FallbackValue=Code}">
|
||||
<TextBox.InputBindings>
|
||||
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
<Grid Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -193,16 +259,10 @@
|
||||
<ProgressBar Margin="5" Foreground="#FF9932CC" Height="15" md:TransitionAssist.DisableTransitions="True" Value="{Binding CodeProgress}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Style="{StaticResource MaterialDesignOutlinedButton}" Margin="10" Command="{Binding GetConfirmationsCommand}" Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}">
|
||||
<Button IsEnabled="{Binding IsMafileSelected}" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Style="{StaticResource MaterialDesignOutlinedButton}" Margin="10" Command="{Binding GetConfirmationsCommand}" Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}">
|
||||
<TextBlock TextWrapping="Wrap" TextTrimming="WordEllipsis" Text="{Tr MainWindow.RightPart.LoadConfirmations}"/>
|
||||
</Button>
|
||||
<ListBox md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2" w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True" Margin="10" Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" ItemsSource="{Binding Confirmations}" SelectionChanged="Selector_OnSelectionChanged">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource MaterialDesignListBoxItem}" TargetType="{x:Type ListBoxItem}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
</ListBox>
|
||||
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2" w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True" Margin="10" Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" ItemsSource="{Binding Confirmations}"/>
|
||||
<Button Grid.Row="3" Margin="1,0,1,3" w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Command="{Binding ConfirmLoginCommand}" Content="{Tr MainWindow.RightPart.ConfirmLogin}"/>
|
||||
</Grid>
|
||||
</md:Card>
|
||||
@@ -220,7 +280,7 @@
|
||||
<TextBlock FontWeight="Normal" Text="{Binding SelectedMafile.Group, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
|
||||
</ToolBarPanel>
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Margin="0,0,10,0">
|
||||
<Hyperlink NavigateUri="https://github.com/achiez" Foreground="{DynamicResource PrimaryHueMidBrush}" RequestNavigate="Hyperlink_OnRequestNavigate">by achies</Hyperlink>
|
||||
<Hyperlink NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies" Foreground="{DynamicResource PrimaryHueMidBrush}" RequestNavigate="Hyperlink_OnRequestNavigate">by achies</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Threading;
|
||||
@@ -18,17 +17,15 @@ namespace NebulaAuth;
|
||||
|
||||
public partial class MainWindow
|
||||
{
|
||||
|
||||
private static string CodeCopiedString => LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied");
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
base.DataContext = new MainVM();
|
||||
DataContext = new MainVM();
|
||||
Application.Current.MainWindow = this;
|
||||
TrayManager.InitializeTray();
|
||||
ThemeManager.InitializeTheme();
|
||||
base.Title = base.Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
|
||||
this.Loaded += OnApplicationStarted;
|
||||
Title = Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
|
||||
Loaded += OnApplicationStarted;
|
||||
}
|
||||
|
||||
private async void OnApplicationStarted(object? sender, EventArgs e)
|
||||
@@ -51,35 +48,6 @@ public partial class MainWindow
|
||||
PHandler.SetPassword(pass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var lb = (ListBox)sender;
|
||||
lb.SelectedValue = null;
|
||||
}
|
||||
|
||||
private async void SteamGuard_DoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var tb = (TextBox)sender;
|
||||
if (tb.Text == CodeCopiedString) return;
|
||||
var code = tb.Text;
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(code);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
return;
|
||||
}
|
||||
tb.Text = CodeCopiedString;
|
||||
await Task.Delay(200);
|
||||
if (tb.Text == CodeCopiedString)
|
||||
{
|
||||
tb.Text = code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Dran'n'Drop
|
||||
@@ -106,7 +74,7 @@ public partial class MainWindow
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
|
||||
string[] filePaths = (string[])e.Data.GetData(DataFormats.FileDrop)!;
|
||||
if (filePaths.Length == 0) return;
|
||||
if (this.DataContext is MainVM mainVm)
|
||||
if (DataContext is MainVM mainVm)
|
||||
{
|
||||
await mainVm.AddMafile(filePaths);
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
|
||||
namespace NebulaAuth.Model.Comparers;
|
||||
|
||||
public class ProxyDataComparer : IEqualityComparer<ProxyData>
|
||||
{
|
||||
public bool Equals(ProxyData? x, ProxyData? y)
|
||||
{
|
||||
return Equal(x, y);
|
||||
|
||||
}
|
||||
|
||||
public static bool Equal(ProxyData? x, ProxyData? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return true;
|
||||
if (ReferenceEquals(x, null)) return false;
|
||||
if (ReferenceEquals(y, null)) return false;
|
||||
return x.Address == y.Address
|
||||
&& x.Port == y.Port
|
||||
&& x.Username == y.Username
|
||||
&& x.Password == y.Password;
|
||||
|
||||
}
|
||||
|
||||
public int GetHashCode(ProxyData obj)
|
||||
{
|
||||
return HashCode.Combine(obj.Address, obj.Port, obj.Username, obj.Password);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ public class LoginConfirmationResult
|
||||
{
|
||||
[MemberNotNullWhen(false, nameof(Error))]
|
||||
public bool Success { get; set; }
|
||||
public string IP { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string IP { get; set; } = null!;
|
||||
public string Country { get; set; } = null!;
|
||||
public LoginConfirmationError? Error { get; set; }
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
using SteamLib;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NebulaAuth.Model.MAAC;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib;
|
||||
using SteamLib.Account;
|
||||
|
||||
namespace NebulaAuth.Model.Entities;
|
||||
|
||||
public class Mafile : MobileDataExtended
|
||||
[INotifyPropertyChanged]
|
||||
public partial class Mafile : MobileDataExtended
|
||||
{
|
||||
public MaProxy? Proxy { get; set; }
|
||||
public string? Group { get; set; }
|
||||
public string? Password { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public PortableMaClient? LinkedClient
|
||||
{
|
||||
get => _linkedClient;
|
||||
set => SetProperty(ref _linkedClient, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private PortableMaClient? _linkedClient;
|
||||
|
||||
|
||||
public void SetSessionData(MobileSessionData? sessionData)
|
||||
{
|
||||
SessionData = sessionData;
|
||||
OnPropertyChanged(nameof(SessionData));
|
||||
}
|
||||
|
||||
public static Mafile FromMobileDataExtended(MobileDataExtended data)
|
||||
{
|
||||
return new Mafile
|
||||
@@ -25,8 +47,6 @@ public class Mafile : MobileDataExtended
|
||||
Uri = data.Uri,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static Mafile FromMobileDataExtended(MobileDataExtended data, MaProxy? proxy, string? group, string? password)
|
||||
{
|
||||
var result = FromMobileDataExtended(data);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace NebulaAuth.Model.Exceptions;
|
||||
|
||||
@@ -17,10 +16,4 @@ public class CantAlignTimeException : Exception
|
||||
public CantAlignTimeException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected CantAlignTimeException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
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;
|
||||
using AchiesUtilities.Web.Models;
|
||||
|
||||
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, "Timer {accountName}: Error GetConf in timer.", Mafile.AccountName);
|
||||
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("Timer {accountName}: Sending confirmations. Count: {count}", Mafile.AccountName, toConfirm.Count);
|
||||
var success = await HandleTimerRequest(() => SendConfirmations(toConfirm));
|
||||
Shell.Logger.Debug("Timer {accountName}: Confirmation sent: {confirmResult}", Mafile.AccountName, success);
|
||||
return toConfirm.Count;
|
||||
}
|
||||
catch (ApplicationException ex)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Timer {accountName}: MultiConf error in Timer.", Mafile.AccountName);
|
||||
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, Chp(), GetTimerPrefix());
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
innerException = ex; //Ignored
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
{
|
||||
if (IgnoreTimerErrors())
|
||||
{
|
||||
Shell.Logger.Warn("Timer {accountName}: Session error while requesting in timer. Ignored due to IgnorePatchTuesdayErrors setting", Mafile.AccountName);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 bool IgnoreTimerErrors()
|
||||
{
|
||||
if (Settings.Instance.IgnorePatchTuesdayErrors == false) return false;
|
||||
|
||||
var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
|
||||
var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone);
|
||||
|
||||
var startTime = pstNow.Date.AddHours(15).AddMinutes(55); // 15:55 PST //22:55 GMT //00:55 GMT+2
|
||||
var endTime = pstNow.Date.AddHours(17).AddMinutes(15); // 17:15 PST //00:15 GMT //02:15 GMT+2
|
||||
|
||||
return pstNow.DayOfWeek == DayOfWeek.Tuesday && pstNow >= startTime && pstNow <= endTime;
|
||||
}
|
||||
|
||||
|
||||
private HttpClientHandlerPair Chp() => new(Client, ClientHandler);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,18 @@
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using AchiesUtilities.Web.Models;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Api.Mobile;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Authentication.LoginV2;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.ProtoCore;
|
||||
using SteamLib.ProtoCore.Services;
|
||||
using SteamLib.SteamMobile;
|
||||
using SteamLib.SteamMobile.Confirmations;
|
||||
using SteamLib.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using SteamLib.Api;
|
||||
using SteamLib.Core.Enums;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
@@ -32,26 +26,20 @@ public static class MaClient
|
||||
private static DynamicProxy Proxy { get; }
|
||||
|
||||
public static ProxyData? DefaultProxy { get; set; }
|
||||
public static HttpClientHandlerPair Chp => new(Client, ClientHandler);
|
||||
|
||||
static MaClient()
|
||||
{
|
||||
Proxy = new DynamicProxy(null);
|
||||
Proxy = new DynamicProxy();
|
||||
var pair = ClientBuilder.BuildMobileClient(Proxy, null);
|
||||
Client = pair.Client;
|
||||
ClientHandler = pair.Handler;
|
||||
}
|
||||
|
||||
public static void ClearCookies()
|
||||
{
|
||||
foreach (Cookie allCookie in ClientHandler.CookieContainer.GetAllCookies())
|
||||
{
|
||||
allCookie.Expired = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetAccount(Mafile? account)
|
||||
{
|
||||
ClearCookies();
|
||||
ClientHandler.CookieContainer.ClearAllCookies();
|
||||
if (account != null)
|
||||
{
|
||||
if (account.SessionData != null)
|
||||
@@ -72,73 +60,41 @@ public static class MaClient
|
||||
{
|
||||
ValidateMafile(mafile);
|
||||
SetProxy(mafile);
|
||||
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId.Steam64);
|
||||
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId);
|
||||
}
|
||||
|
||||
public static async Task LoginAgain(Mafile mafile, string password, bool savePassword, ICaptchaResolver? resolver)
|
||||
public static Task LoginAgain(Mafile mafile, string password, bool savePassword, ICaptchaResolver? resolver)
|
||||
{
|
||||
SetProxy(mafile);
|
||||
var sgGenerator = new SteamGuardCodeGenerator(mafile.SharedSecret);
|
||||
var options = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, Client)
|
||||
{
|
||||
Logger = Shell.ExtensionsLogger,
|
||||
SteamGuardProvider = sgGenerator,
|
||||
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
|
||||
WebsiteId = "Mobile"
|
||||
};
|
||||
ClientHandler.CookieContainer.ClearMobileSessionCookies();
|
||||
var result = await LoginV2Executor.DoLogin(options, mafile.AccountName, password);
|
||||
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
|
||||
mafile.SessionData = (MobileSessionData)result;
|
||||
if(PHandler.IsPasswordSet)
|
||||
mafile.Password = (savePassword ? PHandler.Encrypt(password) : null);
|
||||
Storage.UpdateMafile(mafile);
|
||||
return SessionHandler.LoginAgain(Chp, mafile, password, savePassword);
|
||||
}
|
||||
|
||||
|
||||
public static async Task RefreshSession(Mafile mafile)
|
||||
public static Task RefreshSession(Mafile mafile)
|
||||
{
|
||||
ValidateMafile(mafile, true);
|
||||
SetProxy(mafile);
|
||||
var token = mafile.SessionData!.GetMobileToken();
|
||||
if (token == null || token.Value.IsExpired)
|
||||
{
|
||||
var sessionToken = await SteamMobileApi.RefreshJwt(Client, mafile.SessionData!.RefreshToken.Token, mafile.SessionData.SteamId.Steam64);
|
||||
var newToken = SteamTokenHelper.Parse(sessionToken);
|
||||
mafile.SessionData.SetMobileToken(newToken);
|
||||
}
|
||||
|
||||
//RETHINK: Do we need this? Mobile token is enough
|
||||
var communityToken = mafile.SessionData!.GetToken(SteamDomain.Community);
|
||||
if (communityToken == null || communityToken.Value.IsExpired)
|
||||
{
|
||||
var communityTokenString = await SteamGlobalApi.RefreshJwt(Client, SteamDomain.Community);
|
||||
var newToken = SteamTokenHelper.Parse(communityTokenString);
|
||||
mafile.SessionData.SetToken(SteamDomain.Community, newToken);
|
||||
}
|
||||
|
||||
Storage.UpdateMafile(mafile);
|
||||
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
|
||||
return SessionHandler.RefreshMobileToken(Chp, mafile);
|
||||
}
|
||||
|
||||
public static Task<bool> SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
|
||||
{
|
||||
ValidateMafile(mafile);
|
||||
SetProxy(mafile);
|
||||
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
|
||||
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId, mafile, confirm);
|
||||
}
|
||||
|
||||
public static Task<bool> SendMultipleConfirmation(Mafile mafile, IEnumerable<Confirmation> confirmations, bool confirm)
|
||||
{
|
||||
var enumerable = confirmations.ToList();
|
||||
if (!enumerable.Any())
|
||||
if (enumerable.Count == 0)
|
||||
{
|
||||
return Task.FromResult(result: false);
|
||||
}
|
||||
|
||||
ValidateMafile(mafile);
|
||||
SetProxy(mafile);
|
||||
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
|
||||
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId, mafile, confirm);
|
||||
}
|
||||
|
||||
public static Task<RemoveAuthenticator_Response> RemoveAuthenticator(Mafile mafile)
|
||||
@@ -162,15 +118,15 @@ public static class MaClient
|
||||
{
|
||||
if (mafile.SessionData == null) throw new SessionInvalidException();
|
||||
if (mafile.SessionData.RefreshToken.IsExpired)
|
||||
throw new SessionExpiredException();
|
||||
throw new SessionPermanentlyExpiredException();
|
||||
|
||||
if (ignoreAccessToken == false)
|
||||
{
|
||||
var access = mafile.SessionData.GetMobileToken();
|
||||
if (access == null || access.Value.IsExpired)
|
||||
throw new SessionExpiredException();
|
||||
throw new SessionPermanentlyExpiredException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static async Task<LoginConfirmationResult> ConfirmLoginRequest(Mafile mafile)
|
||||
@@ -178,42 +134,25 @@ public static class MaClient
|
||||
ValidateMafile(mafile);
|
||||
SetProxy(mafile);
|
||||
var token = mafile.SessionData!.GetMobileToken()!.Value;
|
||||
|
||||
var sessions = await SteamMobileAuthenticatorApi.GetAuthSessionsForAccount(Client, token.Token);
|
||||
|
||||
var uri = "https://api.steampowered.com/IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" + token.Token;
|
||||
GetAuthSessionsForAccount_Response getsess;
|
||||
try
|
||||
{
|
||||
getsess = await Client.GetProto<GetAuthSessionsForAccount_Response>(uri, new EmptyMessage());
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new SessionExpiredException(string.Empty, ex);
|
||||
}
|
||||
|
||||
if (getsess.ClientIds.Count == 0)
|
||||
if (sessions.ClientIds.Count == 0)
|
||||
{
|
||||
return new LoginConfirmationResult
|
||||
{
|
||||
Error = LoginConfirmationError.NoRequests
|
||||
};
|
||||
}
|
||||
if (getsess.ClientIds.Count > 1)
|
||||
if (sessions.ClientIds.Count > 1)
|
||||
{
|
||||
return new LoginConfirmationResult
|
||||
{
|
||||
Error = LoginConfirmationError.MoreThanOneRequest
|
||||
};
|
||||
}
|
||||
var clientId = getsess.ClientIds.Single();
|
||||
var infoUri = "https://api.steampowered.com/IAuthenticationService/GetAuthSessionInfo/v1?access_token=" + token.Token;
|
||||
var infoReq = new GetAuthSessionInfo_Request
|
||||
{
|
||||
ClientId = clientId
|
||||
};
|
||||
var infoResp = await Client.PostProto<GetAuthSessionInfo_Response>(infoUri, infoReq);
|
||||
var updateUri = "https://api.steampowered.com/IAuthenticationService/UpdateAuthSessionWithMobileConfirmation/v1?access_token=" + token.Token;
|
||||
|
||||
var clientId = sessions.ClientIds.Single();
|
||||
var clientInfo = await SteamMobileAuthenticatorApi.GetAuthSessionInfo(Client, token.Token, clientId);
|
||||
var updateReq = new UpdateAuthSessionWithMobileConfirmation_Request
|
||||
{
|
||||
ClientId = clientId,
|
||||
@@ -222,12 +161,11 @@ public static class MaClient
|
||||
Steamid = mafile.SessionData.SteamId.Steam64.ToUlong(),
|
||||
Version = 1
|
||||
};
|
||||
updateReq.ComputeSignature(mafile.SharedSecret);
|
||||
await Client.PostProtoEnsureSuccess(updateUri, updateReq);
|
||||
await SteamMobileAuthenticatorApi.UpdateAuthSessionStatus(Client, token.Token, mafile.SharedSecret, updateReq);
|
||||
return new LoginConfirmationResult
|
||||
{
|
||||
Country = infoResp.Country,
|
||||
IP = infoResp.IP,
|
||||
Country = clientInfo.Country,
|
||||
IP = clientInfo.IP,
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,22 +5,28 @@ using System.Text;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public static class PHandler //RETHINK: Use SecureString?
|
||||
public static class PHandler
|
||||
{
|
||||
public static bool IsPasswordSet => _k.Length > 0;
|
||||
private static byte[] _k = Array.Empty<byte>();
|
||||
private static byte[] _k = [];
|
||||
|
||||
|
||||
public static void SetPassword(string? password)
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <returns><see langword="true"/> if password was set and not empty. Otherwise - <see langword="false"/></returns>
|
||||
public static bool SetPassword(string? password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
_k = Array.Empty<byte>();
|
||||
return;
|
||||
_k = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
var keyBytes = Encoding.UTF8.GetBytes(password);
|
||||
_k = SHA256.HashData(keyBytes);
|
||||
return _k.Length > 0;
|
||||
}
|
||||
|
||||
public static string Encrypt(string plainText)
|
||||
@@ -56,7 +62,6 @@ public static class PHandler //RETHINK: Use SecureString?
|
||||
{
|
||||
if (_k.Length == 0) throw new Exception("Password not set");
|
||||
var encryptedBytes = Convert.FromBase64String(encryptedText);
|
||||
string decryptedText = null;
|
||||
|
||||
using var aes = Aes.Create();
|
||||
var keyBytes = _k;
|
||||
@@ -70,7 +75,7 @@ public static class PHandler //RETHINK: Use SecureString?
|
||||
using var ms = new MemoryStream(encryptedBytes);
|
||||
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
|
||||
using var reader = new StreamReader(cs);
|
||||
decryptedText = reader.ReadToEnd();
|
||||
var decryptedText = reader.ReadToEnd();
|
||||
|
||||
return decryptedText;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ public static class ProxyStorage
|
||||
public const string FORMAT = ADDRESS_FORMAT + ":{USER}:{PASS}";
|
||||
public const string ADDRESS_FORMAT = "{IP}:{PORT}";
|
||||
|
||||
public static readonly ProxyScheme DefaultScheme = new ProxyScheme(
|
||||
ProxyDefaultFormats.UniversalHostFirstColonDelimiter, false, ProxyProtocol.HTTP,
|
||||
ProxyPatternProtocol.HTTP | ProxyPatternProtocol.HTTPs,
|
||||
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4, PatternRequirement.Optional,
|
||||
public static readonly ProxyParser DefaultScheme = new(
|
||||
ProxyDefaultFormats.UniversalColon, false, ProxyProtocol.HTTP,
|
||||
ProxyPatternProtocol.All,
|
||||
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4,
|
||||
PatternRequirement.Optional,
|
||||
PatternRequirement.Optional);
|
||||
|
||||
|
||||
@@ -94,7 +95,7 @@ public static class ProxyStorage
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void OrderCollection() //RETHINK: maybe there is better way to handle it
|
||||
public static void OrderCollection() //RETHINK: maybe there is a better way to handle it
|
||||
{
|
||||
var proxies = Proxies.OrderBy(p => p.Key)
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
@@ -110,11 +111,6 @@ public static class ProxyStorage
|
||||
Proxies.Remove(id);
|
||||
Save();
|
||||
}
|
||||
public static bool CompareProxy(ProxyData proxyData1, ProxyData proxyData2)
|
||||
{
|
||||
return proxyData1.Equals(proxyData2);
|
||||
}
|
||||
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
@@ -149,7 +145,7 @@ public static class ProxyStorage
|
||||
|
||||
private class ProxiesSchema
|
||||
{
|
||||
public ObservableDictionary<int, ProxyData> ProxiesData = new();
|
||||
public ObservableDictionary<int, ProxyData> ProxiesData = [];
|
||||
public int? DefaultProxy;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,123 @@
|
||||
using NebulaAuth.Core;
|
||||
using AchiesUtilities.Web.Models;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Exceptions;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public static class SessionHandler
|
||||
public static partial class SessionHandler
|
||||
{
|
||||
public static event EventHandler? LoginStarted;
|
||||
public static event EventHandler? LoginCompleted;
|
||||
|
||||
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile)
|
||||
private static readonly SemaphoreSlim Semaphore = new(1, 1);
|
||||
|
||||
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile,
|
||||
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null)
|
||||
{
|
||||
chp ??= MaClient.Chp;
|
||||
await Semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<T> HandleInternal<T>(Func<Task<T>> func, HttpClientHandlerPair chp, Mafile mafile,
|
||||
string? snackbarPrefix = null)
|
||||
{
|
||||
using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler");
|
||||
var mobileTokenExpired = MobileTokenExpired(mafile);
|
||||
var refreshTokenExpired = RefreshTokenExpired(mafile);
|
||||
var password = GetPassword(mafile);
|
||||
|
||||
if (!mobileTokenExpired)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
when (refreshTokenExpired == false || password != null)
|
||||
{
|
||||
if (ex is SessionPermanentlyExpiredException)
|
||||
{
|
||||
Shell.Logger.Debug(ex, "RefreshToken on mafile {name} {steamid} is expired", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
refreshTokenExpired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Shell.Logger.Debug(ex, "MobileToken on mafile {name} {steamid} is expired", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//State: mobileToken invalid/expired, refreshToken maybe not expired
|
||||
if (!refreshTokenExpired)
|
||||
{
|
||||
var refreshed = await RefreshInternal(chp, mafile);
|
||||
if (refreshed)
|
||||
{
|
||||
SnackbarController.SendSnackbar(snackbarPrefix + LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically", "SessionHandler", "SessionWasRefreshedAutomatically"));
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
{
|
||||
Shell.Logger.Info(ex, "MobileToken on {name} {steamid} was refreshed but after it, error occured", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
if (password == null)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shell.Logger.Debug("Session on mafile {name} {steamid} is invalid/expired", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
|
||||
//State: mobileToken invalid/expired, refreshToken invalid/expired
|
||||
if (password != null)
|
||||
{
|
||||
var logged = await LoginAgainInternal(chp, mafile, password, true);
|
||||
if (logged)
|
||||
{
|
||||
Shell.Logger.Debug("Mafile {name} {steamid} was succesfully auto-relogined", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
return await func();
|
||||
}
|
||||
}
|
||||
|
||||
//Nothing to do more, everything is expired
|
||||
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static bool MobileTokenExpired(Mafile mafile)
|
||||
{
|
||||
var mobileToken = mafile.SessionData?.GetMobileToken();
|
||||
return mobileToken == null || mobileToken.Value.IsExpired;
|
||||
}
|
||||
|
||||
private static bool RefreshTokenExpired(Mafile mafile)
|
||||
{
|
||||
return mafile.SessionData?.RefreshToken.IsExpired != false;
|
||||
}
|
||||
|
||||
private static string? GetPassword(Mafile mafile)
|
||||
{
|
||||
string? password = null;
|
||||
try
|
||||
{
|
||||
if (PHandler.IsPasswordSet && !string.IsNullOrWhiteSpace(mafile.Password))
|
||||
{
|
||||
password = PHandler.Decrypt(mafile.Password);
|
||||
return PHandler.Decrypt(mafile.Password);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -26,70 +125,74 @@ public static class SessionHandler
|
||||
// ignored
|
||||
}
|
||||
|
||||
var refreshed = false;
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (SessionInvalidException) when (mafile.SessionData is { RefreshToken.IsExpired: false})
|
||||
{
|
||||
Shell.Logger.Debug("Token on mafile {name} {steamid} expired. Trying to refresh", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
refreshed = await TryRefresh(mafile);
|
||||
}
|
||||
catch (SessionInvalidException)
|
||||
when (password != null)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
if (refreshed)
|
||||
{
|
||||
Shell.Logger.Debug("Token on mafile {name} {steamid} refreshed", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (Exception ex3)
|
||||
when (password != null && ex3 is SessionExpiredException or SessionInvalidException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (password == null)
|
||||
{
|
||||
throw new SessionInvalidException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LoginStarted?.Invoke(null, EventArgs.Empty);
|
||||
await MaClient.LoginAgain(mafile, password, savePassword: true, null);
|
||||
Shell.Logger.Debug("Mafile {name} {steamid} succesfully auto-relogined", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
LoginCompleted?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return await func();
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async Task<bool> TryRefresh(Mafile mafile)
|
||||
|
||||
private static async Task<bool> RefreshInternal(HttpClientHandlerPair chp, Mafile mafile)
|
||||
{
|
||||
try
|
||||
{
|
||||
await MaClient.RefreshSession(mafile);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically", "SessionHandler", "SessionWasRefreshedAutomatically"));
|
||||
await RefreshMobileToken(chp, mafile);
|
||||
return true;
|
||||
}
|
||||
catch (SessionInvalidException)
|
||||
catch(Exception ex)
|
||||
{
|
||||
Shell.Logger.Debug("Token on mafile {name} {steamid} not refreshed", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
Shell.Logger.Debug(ex, "Failed to refresh session on mafile {name} {steamid}", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> LoginAgainInternal(HttpClientHandlerPair chp, Mafile mafile, string password,
|
||||
bool savePassword)
|
||||
{
|
||||
var t = Task.Run(OnLoginStarted);
|
||||
try
|
||||
{
|
||||
|
||||
await LoginAgain(chp, mafile, password, savePassword);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnLoginCompleted();
|
||||
await t;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task OnLoginStarted()
|
||||
{
|
||||
if (DialogHost.IsDialogOpen(null)) return;
|
||||
await Application.Current.Dispatcher.BeginInvoke(async () =>
|
||||
{
|
||||
await DialogHost.Show(new WaitLoginDialog());
|
||||
});
|
||||
}
|
||||
|
||||
private static void OnLoginCompleted()
|
||||
{
|
||||
var currentSession = DialogHost.GetDialogSession(null);
|
||||
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (currentSession is { Content: WaitLoginDialog, IsEnded: false })
|
||||
{
|
||||
try
|
||||
{
|
||||
currentSession.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using AchiesUtilities.Web.Models;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Api.Mobile;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Authentication.LoginV2;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public partial class SessionHandler //API
|
||||
{
|
||||
public static async Task RefreshMobileToken(HttpClientHandlerPair chp, Mafile mafile)
|
||||
{
|
||||
if (mafile.SessionData is not { RefreshToken.IsExpired: false })
|
||||
throw new SessionPermanentlyExpiredException(SessionInvalidException.SESSION_NULL_MSG);
|
||||
|
||||
var mobileToken = await SteamMobileApi.RefreshJwt(chp.Client, mafile.SessionData.RefreshToken.Token, mafile.SessionData.SteamId);
|
||||
Shell.Logger.Info("MobileToken on {name} {steamid} successfully refreshed", mafile.AccountName, mafile.SessionData.SteamId);
|
||||
|
||||
var newToken = SteamTokenHelper.Parse(mobileToken);
|
||||
mafile.SessionData.SetMobileToken(newToken);
|
||||
|
||||
//Trigger PropertyChanged event for PortableMaClient handling session updated from MaClient
|
||||
//RETHINK: it makes double operation when session handled from PortableMaClient (more often scenario) which is unwanted behaviour
|
||||
mafile.SetSessionData(mafile.SessionData);
|
||||
Storage.UpdateMafile(mafile);
|
||||
chp.Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
|
||||
}
|
||||
|
||||
public static async Task LoginAgain(HttpClientHandlerPair chp, Mafile mafile, string password, bool savePassword)
|
||||
{
|
||||
var sgGenerator = new SteamGuardCodeGenerator(mafile.SharedSecret);
|
||||
var options = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, chp.Client)
|
||||
{
|
||||
Logger = Shell.ExtensionsLogger,
|
||||
SteamGuardProvider = sgGenerator,
|
||||
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
|
||||
WebsiteId = "Mobile",
|
||||
};
|
||||
chp.Handler.CookieContainer.ClearMobileSessionCookies();
|
||||
var result = await LoginV2Executor.DoLogin(options, mafile.AccountName, password);
|
||||
Shell.Logger.Info("Logged in again on {name} {steamid}", mafile.AccountName, result.SteamId);
|
||||
AdmissionHelper.TransferCommunityCookies(chp.Handler.CookieContainer);
|
||||
|
||||
//Triggers PropertyChanged event for PortableMaClient handling session updated from MaClient
|
||||
//RETHINK: it makes double operation when session handled from PortableMaClient (more often scenario) which is unwanted behaviour
|
||||
mafile.SetSessionData((MobileSessionData)result);
|
||||
if (PHandler.IsPasswordSet)
|
||||
mafile.Password = savePassword ? PHandler.Encrypt(password) : null;
|
||||
Storage.UpdateMafile(mafile);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ public partial class Settings : ObservableObject
|
||||
{
|
||||
#region Properties
|
||||
|
||||
[ObservableProperty] private bool _disableTimersOnChange = true;
|
||||
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
|
||||
[ObservableProperty] private bool _hideToTray;
|
||||
[ObservableProperty] private int _timerSeconds = 60;
|
||||
@@ -23,6 +22,7 @@ public partial class Settings : ObservableObject
|
||||
[ObservableProperty] private bool _legacyMode = true;
|
||||
[ObservableProperty] private bool _allowAutoUpdate;
|
||||
[ObservableProperty] private bool _useAccountNameAsMafileName;
|
||||
[ObservableProperty] private bool _ignorePatchTuesdayErrors;
|
||||
#endregion
|
||||
|
||||
public static Settings Instance { get; }
|
||||
|
||||
@@ -17,7 +17,7 @@ public static class Shell
|
||||
|
||||
var lp = new NLog.Extensions.Logging.NLogLoggerProvider();
|
||||
var logger = lp.CreateLogger("SteamLib");
|
||||
HealthMonitor.FatalLogger = logger;
|
||||
SteamLibErrorMonitor.MonitorLogger = logger;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
|
||||
|
||||
var loggerFactory = new NLog.Extensions.Logging.NLogLoggerFactory();
|
||||
|
||||
@@ -87,7 +87,7 @@ public static class Storage
|
||||
|
||||
try
|
||||
{
|
||||
var code = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
|
||||
_ = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -115,7 +115,7 @@ public static class Storage
|
||||
public static Mafile ReadMafile(string path)
|
||||
{
|
||||
var str = File.ReadAllText(path);
|
||||
var mafile = MafileSerializer.Deserialize(str, out var mafileData);
|
||||
var mafile = MafileSerializer.Deserialize(str, true, out var mafileData);
|
||||
if (mafileData.IsExtended == false)
|
||||
throw new FormatException("Mafile is not extended data");
|
||||
|
||||
@@ -271,7 +271,7 @@ internal class MafileNameComparer : IComparer<string>
|
||||
{
|
||||
public bool MafileNameMode { get; }
|
||||
private const string MAF_64_START = "765";
|
||||
private static readonly IComparer<string> _defaultComparer = Comparer<string>.Default;
|
||||
private static readonly IComparer<string> DefaultComparer = Comparer<string>.Default;
|
||||
public MafileNameComparer(bool mafileNameMode)
|
||||
{
|
||||
MafileNameMode = mafileNameMode;
|
||||
@@ -300,7 +300,7 @@ internal class MafileNameComparer : IComparer<string>
|
||||
}
|
||||
}
|
||||
|
||||
return _defaultComparer.Compare(x, y);
|
||||
return DefaultComparer.Compare(x, y);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,8 @@
|
||||
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
|
||||
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
|
||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||
<AssemblyVersion>1.5.2</AssemblyVersion>
|
||||
<AssemblyVersion>1.5.3</AssemblyVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -25,13 +26,12 @@
|
||||
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.1" />
|
||||
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.3" />
|
||||
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.1" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
||||
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
|
||||
<PackageReference Include="MaterialDesignExtensions" Version="4.0.0-a02" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
|
||||
<PackageReference Include="NLog" Version="5.3.3" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.12" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!--Card-->
|
||||
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="#26292E"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="#26292E" />
|
||||
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1" />
|
||||
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5" />
|
||||
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1" />
|
||||
</ResourceDictionary>
|
||||
@@ -4,14 +4,14 @@ using System.Windows;
|
||||
|
||||
namespace NebulaAuth.Theme;
|
||||
|
||||
public partial class FontScaleWindow : Window
|
||||
public class FontScaleWindow : Window
|
||||
{
|
||||
|
||||
private readonly Func<double, double, double> _diagonal = (h, w) => Math.Sqrt(h * h + w * w);
|
||||
private double _currentDiagonal;
|
||||
private double _defaultDiagonal = 1;
|
||||
private readonly HashSet<FrameworkElement> _cachedObjects = new();
|
||||
private bool _loaded = false;
|
||||
private bool _loaded;
|
||||
|
||||
public FontScaleWindow()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 365 KiB |
Binary file not shown.
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable IdentifierTypo
|
||||
|
||||
namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
public static class NativeMethods
|
||||
// ReSharper disable once PartialTypeWithSinglePart //Required
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
public const int WM_NCCALCSIZE = 0x83;
|
||||
public const int WM_NCPAINT = 0x85;
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable IdentifierTypo
|
||||
|
||||
namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
public static class WindowChromeHelper
|
||||
{
|
||||
public static Thickness LayoutOffsetThickness => new Thickness(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
|
||||
public static Thickness LayoutOffsetThickness => new(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the properly adjusted window resize border thickness from system parameters.
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
{
|
||||
private Window window;
|
||||
private Window? _window;
|
||||
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
@@ -23,13 +24,13 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
AssociatedObject.ContentRendered -= OnContentRendered;
|
||||
}
|
||||
|
||||
private void OnContentRendered(object sender, EventArgs e)
|
||||
private void OnContentRendered(object? sender, EventArgs e)
|
||||
{
|
||||
window = Window.GetWindow(AssociatedObject);
|
||||
_window = Window.GetWindow(AssociatedObject);
|
||||
|
||||
if (window == null) return;
|
||||
if (_window == null) return;
|
||||
|
||||
var oldWindowChrome = WindowChrome.GetWindowChrome(window);
|
||||
var oldWindowChrome = WindowChrome.GetWindowChrome(_window);
|
||||
|
||||
if (oldWindowChrome == null) return;
|
||||
|
||||
@@ -43,9 +44,9 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
UseAeroCaptionButtons = oldWindowChrome.UseAeroCaptionButtons
|
||||
};
|
||||
|
||||
WindowChrome.SetWindowChrome(window, newWindowChrome);
|
||||
WindowChrome.SetWindowChrome(_window, newWindowChrome);
|
||||
|
||||
var hWnd = new WindowInteropHelper(window).Handle;
|
||||
var hWnd = new WindowInteropHelper(_window).Handle;
|
||||
HwndSource.FromHwnd(hWnd)?.AddHook(WndProc);
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
margins.rightWidth = 0;
|
||||
margins.topHeight = 0;
|
||||
|
||||
var helper = new WindowInteropHelper(window);
|
||||
var helper = new WindowInteropHelper(_window);
|
||||
|
||||
NativeMethods.DwmExtendFrameIntoClientArea(helper.Handle, ref margins);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace NebulaAuth.Theme.WindowStyle
|
||||
namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WindowStyle.xaml
|
||||
/// </summary>
|
||||
partial class WindowStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WindowStyle.xaml
|
||||
/// </summary>
|
||||
partial class WindowStyle
|
||||
public WindowStyle()
|
||||
{
|
||||
public WindowStyle()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnCloseClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void OnMaximizeRestoreClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = window.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;
|
||||
}
|
||||
|
||||
private void OnMinimizeClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void OnMaximizeRestoreClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = window.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;
|
||||
}
|
||||
|
||||
private void OnMinimizeClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public static class ExceptionHandler
|
||||
Shell.Logger.Error(ex);
|
||||
switch (ex)
|
||||
{
|
||||
case SessionExpiredException:
|
||||
case SessionPermanentlyExpiredException:
|
||||
{
|
||||
msg = "SessionExpiredException".GetCodeBehindLocalization();
|
||||
break;
|
||||
@@ -60,7 +60,17 @@ public static class ExceptionHandler
|
||||
}
|
||||
case CantLoadConfirmationsException e:
|
||||
{
|
||||
msg = e.Message;
|
||||
msg = LocManager.GetCodeBehindOrDefault(nameof(CantLoadConfirmationsException), EXCEPTION_HANDLER_LOC_PATH, nameof(CantLoadConfirmationsException), "Common");
|
||||
if (e.Error == LoadConfirmationsError.Unknown)
|
||||
{
|
||||
msg += e.ErrorMessage;
|
||||
msg += ' ';
|
||||
msg += e.ErrorDetails;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += LocManager.GetCodeBehindOrDefault(e.Error.ToString(), EXCEPTION_HANDLER_LOC_PATH, nameof(CantLoadConfirmationsException), e.Error.ToString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EResultException e:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
xmlns:converters="clr-namespace:NebulaAuth.Converters"
|
||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
||||
>
|
||||
xmlns:vm="clr-namespace:NebulaAuth.ViewModel">
|
||||
|
||||
|
||||
<DataTemplate DataType="{x:Type confirmations:Confirmation}">
|
||||
@@ -18,15 +18,15 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="QuestionMark" Margin="0,0,10,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Binding TypeName}"></TextBlock>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Binding TypeName}" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -43,21 +43,21 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0"></materialDesign:PackIcon>
|
||||
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0"></Image>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0" />
|
||||
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" TextWrapping="WrapWithOverflow" >
|
||||
<Run Text="{Tr MainWindow.ConfirmationTemplates.TradeWith, IsDynamic=False}"/>
|
||||
<Run Text="{Binding UserName}" FontWeight="Bold"></Run>
|
||||
<Run Text="{Binding UserName}" FontWeight="Bold" />
|
||||
</TextBlock>
|
||||
<materialDesign:PackIcon Grid.Column="3" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Kind="Gift" Visibility="{Binding IsReceiveNothing, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -76,12 +76,12 @@
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.Recovery}"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -98,14 +98,14 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0" />
|
||||
<Border Grid.Column="1" Background="{DynamicResource MaterialDesignPaper}" MaxHeight="36" HorizontalAlignment="Center" BorderBrush="{DynamicResource PrimaryHueMidBrush}" BorderThickness="0.6" Margin="0,0,10,0">
|
||||
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" ></Image>
|
||||
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" />
|
||||
</Border>
|
||||
<Grid Grid.Column="2">
|
||||
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True" TextWrapping="WrapWithOverflow" Text="{Binding ItemName}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Foreground="LightGray" Text="{Binding PriceString}">
|
||||
<TextBlock Foreground="LightGray" Text="{Binding PriceString}">
|
||||
<TextBlock.FontSize>
|
||||
<Binding ElementName="ItemName" Path="FontSize" ConverterParameter="0.7">
|
||||
<Binding.Converter>
|
||||
@@ -118,12 +118,12 @@
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -139,15 +139,15 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="KeyLink" Margin="0,0,10,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}"></TextBlock>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -162,7 +162,7 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1">
|
||||
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market, IsDynamic=False}"/>
|
||||
<Run Text="{Binding Confirmations.Count, Mode=OneWay}"/>
|
||||
@@ -171,12 +171,12 @@
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="100" d:DesignWidth="400"
|
||||
Height="Auto" Width="Auto" MaxWidth="500"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
<Grid Margin="15">
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ConfirmCancelDialog.xaml
|
||||
/// </summary>
|
||||
public partial class ConfirmCancelDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ConfirmCancelDialog.xaml
|
||||
/// </summary>
|
||||
public partial class ConfirmCancelDialog : UserControl
|
||||
public ConfirmCancelDialog()
|
||||
{
|
||||
public ConfirmCancelDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ConfirmCancelDialog(string msg)
|
||||
{
|
||||
InitializeComponent();
|
||||
ConfirmTextBlock.Text = msg;
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public ConfirmCancelDialog(string msg)
|
||||
{
|
||||
InitializeComponent();
|
||||
ConfirmTextBlock.Text = msg;
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,9 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="250" d:DesignWidth="800"
|
||||
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
|
||||
Foreground="WhiteSmoke" Cursor="Hand"
|
||||
Foreground="WhiteSmoke"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
|
||||
@@ -26,7 +24,7 @@
|
||||
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
|
||||
<Run FontWeight="Bold" Text="{Binding UserName}"/>
|
||||
</TextBlock>
|
||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
|
||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
|
||||
<CheckBox Grid.Row="2" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
|
||||
<Grid Grid.Row="3" Margin="10,10,10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainDialog : UserControl
|
||||
public LoginAgainDialog()
|
||||
{
|
||||
public LoginAgainDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,8 @@
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="250" d:DesignWidth="800"
|
||||
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
|
||||
Foreground="WhiteSmoke" Cursor="Hand"
|
||||
Foreground="WhiteSmoke"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainOnImportVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
|
||||
@@ -28,7 +27,7 @@
|
||||
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
|
||||
<Run FontWeight="Bold" Text="{Binding UserName}"/>
|
||||
</TextBlock>
|
||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
|
||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
|
||||
<ComboBox ToolTip="{Tr LoginAgainDialog.ProxyToolTip}" Grid.Row="2" Margin="10,18,10,0" materialDesign:HintAssist.Hint="{Tr Common.Proxy}" ItemsSource="{Binding Proxies}" SelectedItem="{Binding SelectedProxy}" >
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type entities:MaProxy}">
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainOnImportDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainOnImportDialog
|
||||
public LoginAgainOnImportDialog()
|
||||
{
|
||||
public LoginAgainOnImportDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,12 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NebulaAuth.View.Dialogs"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
Foreground="WhiteSmoke" Cursor="Hand"
|
||||
Foreground="WhiteSmoke"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
|
||||
@@ -21,7 +19,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal" Text="{Tr SetEncryptedPasswordDialog.DialogText}" theme:FontScaleWindow.Scale="0.8" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left"/>
|
||||
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}"></PasswordBox>
|
||||
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}" />
|
||||
<Grid Grid.Row="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
|
||||
/// </summary>
|
||||
public partial class SetCryptPasswordDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
|
||||
/// </summary>
|
||||
public partial class SetCryptPasswordDialog : UserControl
|
||||
public SetCryptPasswordDialog()
|
||||
{
|
||||
public SetCryptPasswordDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" IsIndeterminate="True"></ProgressBar>
|
||||
<TextBlock Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}" VerticalAlignment="Center"></TextBlock>
|
||||
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" IsIndeterminate="True" />
|
||||
<TextBlock Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Grid x:Name="CaptchaGrid" Margin="0,25,0,0" Visibility="Collapsed" Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -27,7 +27,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image x:Name="CaptchaImage" Stretch="Uniform"></Image>
|
||||
<Image x:Name="CaptchaImage" Stretch="Uniform" />
|
||||
<TextBox Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" x:Name="CaptchaTB"/>
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
|
||||
@@ -7,66 +7,65 @@ using System.Windows.Media.Imaging;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Exceptions;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WaitLoginDialog.xaml
|
||||
/// </summary>
|
||||
public partial class WaitLoginDialog : ICaptchaResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WaitLoginDialog.xaml
|
||||
/// </summary>
|
||||
public partial class WaitLoginDialog : ICaptchaResolver
|
||||
private TaskCompletionSource<string> _tcs = new();
|
||||
public WaitLoginDialog()
|
||||
{
|
||||
private TaskCompletionSource<string> _tcs = new();
|
||||
public WaitLoginDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public async Task<string> Resolve(Uri imageUrl, HttpClient client)
|
||||
{
|
||||
|
||||
CaptchaGrid.Visibility = Visibility.Visible;
|
||||
var stream = await client.GetStreamAsync(imageUrl);
|
||||
return await Application.Current.Dispatcher.Invoke(async () =>
|
||||
{
|
||||
var image = await LoadImage(stream);
|
||||
CaptchaImage.Source = image;
|
||||
try
|
||||
{
|
||||
return await _tcs.Task;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw new LoginException(LoginError.CaptchaRequired);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<BitmapImage> LoadImage(Stream stream)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
var image = new BitmapImage();
|
||||
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = ms;
|
||||
image.EndInit();
|
||||
await stream.DisposeAsync();
|
||||
return image;
|
||||
}
|
||||
|
||||
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_tcs.SetCanceled();
|
||||
}
|
||||
|
||||
private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return;
|
||||
var oldTcs = _tcs;
|
||||
_tcs = new TaskCompletionSource<string>();
|
||||
oldTcs.SetResult(CaptchaTB.Text);
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> Resolve(Uri imageUrl, HttpClient client)
|
||||
{
|
||||
|
||||
CaptchaGrid.Visibility = Visibility.Visible;
|
||||
var stream = await client.GetStreamAsync(imageUrl);
|
||||
return await Application.Current.Dispatcher.Invoke(async () =>
|
||||
{
|
||||
var image = await LoadImage(stream);
|
||||
CaptchaImage.Source = image;
|
||||
try
|
||||
{
|
||||
return await _tcs.Task;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw new LoginException(LoginError.CaptchaRequired);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<BitmapImage> LoadImage(Stream stream)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
var image = new BitmapImage();
|
||||
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = ms;
|
||||
image.EndInit();
|
||||
await stream.DisposeAsync();
|
||||
return image;
|
||||
}
|
||||
|
||||
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_tcs.SetCanceled();
|
||||
}
|
||||
|
||||
private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return;
|
||||
var oldTcs = _tcs;
|
||||
_tcs = new TaskCompletionSource<string>();
|
||||
oldTcs.SetResult(CaptchaTB.Text);
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,10 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NebulaAuth.View"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
MinHeight="640"
|
||||
@@ -63,12 +61,19 @@
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}"/>
|
||||
<TextBlock Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" >
|
||||
<Hyperlink Command="{Binding OpenTroubleshootingCommand}">
|
||||
<Run Text="{Tr LinkerDialog.GotErrorHyperlinkText, IsDynamic=False}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Background="DarkGray" Grid.Row="1"></Separator>
|
||||
<Separator Background="DarkGray" Grid.Row="1" />
|
||||
<Grid Grid.Row="2" Margin="10,20,10,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -86,48 +91,58 @@
|
||||
</ComboBox>
|
||||
<Button Grid.Column="1" IsEnabled="{Binding IsPasswordFieldVisible}" Margin="5,0,0,0" Command="{Binding ResetProxyCommand}" Content="{materialDesign:PackIcon Trash}"/>
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="10" Tag="{Binding IsLogin}">
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="7" Tag="{Binding IsLogin}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.Authorization}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Authorization}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="10" Tag="{Binding IsEmailCode}">
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailCode}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.EmailCode}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailCode}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="10" Tag="{Binding IsPhoneNumber}">
|
||||
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="7" Tag="{Binding IsPhoneNumber}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.PhoneNumber}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.PhoneNumber}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="10" Tag="{Binding IsEmailConfirmation}">
|
||||
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailConfirmation}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.EmailLink}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailLink}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="7" Orientation="Horizontal" Margin="10" Tag="{Binding IsLinkCode}">
|
||||
<StackPanel Grid.Row="7" Orientation="Horizontal" Margin="7" Tag="{Binding IsLinkCode}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.SmsOrCode}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.SmsOrCode}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="8" Orientation="Horizontal" Margin="10" Tag="{Binding IsCompleted}">
|
||||
<StackPanel Grid.Row="8" Orientation="Horizontal" Margin="7" Tag="{Binding IsCompleted}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.Completed}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Completed}"/>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="9" Margin="10">
|
||||
<Grid Grid.Row="9" Margin="10,20,10,10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Padding="10" Text="{Binding FieldText}" Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10"></TextBox>
|
||||
<TextBox Grid.Row="1" Padding="10" Text="{Binding PassFieldText}" Visibility="{Binding IsPasswordFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16"/>
|
||||
<TextBox Padding="7" Text="{Binding FieldText}" Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10" />
|
||||
<TextBox Grid.Row="1" Padding="7" Text="{Binding PassFieldText}" Visibility="{Binding IsPasswordFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16"/>
|
||||
<GroupBox Grid.Row="2" MaxWidth="400" BorderBrush="{StaticResource PrimaryHueMidBrush}" VerticalAlignment="Bottom" BorderThickness="1" Margin="0,10,0,0" Padding="5">
|
||||
<GroupBox.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Tr LinkerDialog.Message}" FontSize="16" Foreground="GhostWhite"/>
|
||||
</DataTemplate>
|
||||
</GroupBox.HeaderTemplate>
|
||||
<TextBlock Foreground="GhostWhite" FontSize="16" Text="{Binding HintText}" HorizontalAlignment="Stretch" TextWrapping="Wrap"/>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Foreground="GhostWhite" FontSize="16" Text="{Binding HintText}" HorizontalAlignment="Stretch" TextWrapping="Wrap"/>
|
||||
<Button Command="{Binding CopyCodeCommand}" Visibility="{Binding IsCompleted, Converter={StaticResource BooleanToVisibilityConverter}}" Grid.Row="1">
|
||||
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<Button Grid.Row="10" FontSize="18" Command="{Binding ProceedCommand}" Content="{Tr LinkerDialog.ProceedButton}"/>
|
||||
<Button Grid.Row="10" Height="35" FontSize="17" Command="{Binding ProceedCommand}" Content="{Tr LinkerDialog.ProceedButton}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LinkerView.xaml
|
||||
/// </summary>
|
||||
public partial class LinkerView
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LinkerView.xaml
|
||||
/// </summary>
|
||||
public partial class LinkerView : UserControl
|
||||
public LinkerView()
|
||||
{
|
||||
public LinkerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,11 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NebulaAuth.View"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DesignHeight="500" d:DesignWidth="400"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{md:MaterialDesignFont}"
|
||||
MinHeight="500"
|
||||
@@ -15,12 +14,6 @@
|
||||
MaxHeight="550"
|
||||
d:DataContext="{d:DesignInstance other:ProxyManagerVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
<d:DesignerProperties.DesignStyle>
|
||||
<Style TargetType="UserControl">
|
||||
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
|
||||
</Style>
|
||||
</d:DesignerProperties.DesignStyle>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -36,11 +29,11 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Margin="10" FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}"/>
|
||||
<Button Margin="0,0,10,0" IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static md:DialogHost.CloseDialogCommand}">
|
||||
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></md:PackIcon>
|
||||
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
<Separator Grid.Row="1"></Separator>
|
||||
<Separator Grid.Row="1" />
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -53,8 +46,8 @@
|
||||
<Run Text="{Binding DefaultProxy.Value, Converter='{StaticResource ProxyDataTextConverter}', Mode=OneWay, FallbackValue=''}"/>
|
||||
</TextBlock>
|
||||
|
||||
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}" Cursor="Hand">
|
||||
<md:PackIcon Kind="ClearBox" Width="20" Height="20"></md:PackIcon>
|
||||
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
|
||||
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<md:Card Grid.Row="3" Margin="10">
|
||||
@@ -98,7 +91,7 @@
|
||||
<Button Style="{StaticResource MaterialDesignIconButton}" Padding="0" Width="24" Height="24" md:RippleAssist.IsDisabled="True" Grid.Column="1" HorizontalAlignment="Right"
|
||||
Command="{Binding DataContext.SetDefaultCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<md:PackIcon Height="16" Width="16" Kind="Heart"></md:PackIcon>
|
||||
<md:PackIcon Height="16" Width="16" Kind="Heart" />
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
@@ -113,12 +106,12 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True" AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}" Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}"></TextBox>
|
||||
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True" AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}" Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}" />
|
||||
<Button IsDefault="True" Grid.Column="1" Command="{Binding AddProxyCommand}">
|
||||
<md:PackIcon Kind="Add"></md:PackIcon>
|
||||
<md:PackIcon Kind="Add" />
|
||||
</Button>
|
||||
<Button Grid.Column="2" Command="{Binding RemoveProxyCommand}">
|
||||
<md:PackIcon Kind="Trash"></md:PackIcon>
|
||||
<md:PackIcon Kind="Trash" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ProxyManagerView.xaml
|
||||
/// </summary>
|
||||
public partial class ProxyManagerView
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ProxyManagerView.xaml
|
||||
/// </summary>
|
||||
public partial class ProxyManagerView : UserControl
|
||||
public ProxyManagerView()
|
||||
{
|
||||
public ProxyManagerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,20 +6,15 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignHeight="650"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
MinHeight="300"
|
||||
MinHeight="600"
|
||||
MinWidth="400"
|
||||
|
||||
MaxWidth="200"
|
||||
d:DataContext="{d:DesignInstance other:SettingsVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
<d:DesignerProperties.DesignStyle>
|
||||
<Style TargetType="UserControl">
|
||||
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
|
||||
</Style>
|
||||
</d:DesignerProperties.DesignStyle>
|
||||
<Grid>
|
||||
<Grid Margin="15,10,15,15">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -35,15 +30,14 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr SettingsDialog.Title}"/>
|
||||
<Button IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Margin="0,10,0,0" />
|
||||
|
||||
<StackPanel Grid.Row="2" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" ></ComboBox>
|
||||
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" ></ComboBox>
|
||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding DisableTimersOnChange}" Content="{Tr SettingsDialog.DisableTimersOnSwitch}"/>
|
||||
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" />
|
||||
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" />
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}" ToolTip="{Tr SettingsDialog.UseIndicatorHint}"/>
|
||||
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
|
||||
@@ -58,16 +52,17 @@
|
||||
<PasswordBox MinWidth="250" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Height="Auto" VerticalAlignment="Center" FontSize="16" Margin="0,10,0,0"
|
||||
Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
|
||||
materialDesign:HintAssist.Hint="{Tr SettingsDialog.PasswordBox.CurrentCryptPassword}"
|
||||
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}"></PasswordBox>
|
||||
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" />
|
||||
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom" Grid.Column="1"> <materialDesign:PackIcon Kind="ContentSave"/></Button>
|
||||
</Grid>
|
||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding LegacyMode}" Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}"/>
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
|
||||
<!--<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>-->
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseAccountNameAsMafileName}" >
|
||||
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
|
||||
</CheckBox>
|
||||
|
||||
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" ToolTip="{Tr SettingsDialog.IgnorePatchTuesdayErrorsHint}">
|
||||
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.IgnorePatchTuesdayErrors}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class SettingsView
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class SettingsView : UserControl
|
||||
public SettingsView()
|
||||
{
|
||||
public SettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ColorPicker_OnColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
|
||||
{
|
||||
Debug.WriteLine(e.NewValue);
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,10 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NebulaAuth.View"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="500"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
d:DataContext="{d:DesignInstance other:UpdaterVM}"
|
||||
@@ -31,7 +27,7 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock FontSize="24" Text="Обновления"/>
|
||||
<Separator Grid.Row="1"></Separator>
|
||||
<Separator Grid.Row="1" />
|
||||
|
||||
<TextBlock FontSize="16" Margin="5,10,0,10" TextWrapping="WrapWithOverflow" Grid.Row="2" >
|
||||
<Run Text="Доступна новая версия:"/>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для UpdaterView.xaml
|
||||
/// </summary>
|
||||
public partial class UpdaterView
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для UpdaterView.xaml
|
||||
/// </summary>
|
||||
public partial class UpdaterView : UserControl
|
||||
public UpdaterView()
|
||||
{
|
||||
public UpdaterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using JetBrains.Annotations;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -21,8 +21,9 @@ public partial class MainVM : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Mafile> _maFiles = Storage.MaFiles;
|
||||
public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
|
||||
|
||||
[UsedImplicitly]
|
||||
public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
|
||||
|
||||
|
||||
public Mafile? SelectedMafile
|
||||
@@ -32,39 +33,40 @@ public partial class MainVM : ObservableObject
|
||||
}
|
||||
private Mafile? _selectedMafile;
|
||||
|
||||
public bool IsMafileSelected => SelectedMafile != null;
|
||||
|
||||
|
||||
public MainVM()
|
||||
{
|
||||
CreateCodeTimer();
|
||||
_confirmTimer = new Timer(ConfirmByTimer, null, TimeSpan.FromSeconds(_timerCheckSeconds), TimeSpan.FromSeconds(_timerCheckSeconds));
|
||||
Proxies = new ObservableCollection<MaProxy>(ProxyStorage.Proxies.Select(kvp =>
|
||||
new MaProxy(kvp.Key, kvp.Value)));
|
||||
Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged;
|
||||
QueryGroups();
|
||||
SessionHandler.LoginStarted += SessionHandlerOnLoginStarted;
|
||||
SessionHandler.LoginCompleted += SessionHandlerOnLoginCompleted;
|
||||
UpdateManager.CheckForUpdates();
|
||||
if (Storage.DuplicateFound > 0)
|
||||
{
|
||||
SnackbarController.SendSnackbar(
|
||||
GetLocalizationOrDefault("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
|
||||
GetLocalization("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
|
||||
TimeSpan.FromSeconds(4));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void SetMafile(Mafile? mafile)
|
||||
{
|
||||
if (mafile != SelectedMafile)
|
||||
{
|
||||
_selectedMafile = mafile;
|
||||
OnPropertyChanged(nameof(SelectedMafile));
|
||||
OnPropertyChanged(nameof(TradeTimerEnabled));
|
||||
OnPropertyChanged(nameof(MarketTimerEnabled));
|
||||
MaClient.SetAccount(mafile);
|
||||
OnPropertyChanged(nameof(ConfirmationsVisible));
|
||||
if (Settings.DisableTimersOnChange) OffTimer(dispatcher: false);
|
||||
SetCurrentProxy();
|
||||
OnPropertyChanged(nameof(IsDefaultProxy));
|
||||
if (mafile != null) Code = SteamGuardCodeGenerator.GenerateCode(mafile.SharedSecret);
|
||||
OnPropertyChanged(nameof(IsMafileSelected));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +83,14 @@ public partial class MainVM : ObservableObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var password = loginAgainVm.Password;
|
||||
var waitDialog = new WaitLoginDialog();
|
||||
var wait = DialogHost.Show(waitDialog);
|
||||
try
|
||||
{
|
||||
await MaClient.LoginAgain(SelectedMafile, password, loginAgainVm.SavePassword, waitDialog);
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
|
||||
}
|
||||
catch (LoginException ex)
|
||||
{
|
||||
@@ -118,7 +120,7 @@ public partial class MainVM : ObservableObject
|
||||
try
|
||||
{
|
||||
await MaClient.RefreshSession(SelectedMafile);
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SessionRefreshed"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("SessionRefreshed"));
|
||||
}
|
||||
catch (Exception ex) when (ExceptionHandler.Handle(ex))
|
||||
{
|
||||
@@ -129,7 +131,6 @@ public partial class MainVM : ObservableObject
|
||||
[RelayCommand]
|
||||
public async Task LinkAccount()
|
||||
{
|
||||
OffTimer(false);
|
||||
await DialogsController.ShowLinkerDialog();
|
||||
}
|
||||
|
||||
@@ -140,16 +141,16 @@ public partial class MainVM : ObservableObject
|
||||
if (selectedMafile == null) return;
|
||||
if (string.IsNullOrWhiteSpace(selectedMafile.RevocationCode))
|
||||
{
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalizationOrDefault("MissingRCode"));
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalization("MissingRCode"));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmRemovingAuthenticator")))
|
||||
if (await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmRemovingAuthenticator")))
|
||||
{
|
||||
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(selectedMafile), selectedMafile);
|
||||
SnackbarController.SendSnackbar(
|
||||
result.Success ? GetLocalizationOrDefault("AuthenticatorRemoved") : GetLocalizationOrDefault("AuthenticatorNotRemoved"));
|
||||
result.Success ? GetLocalization("AuthenticatorRemoved") : GetLocalization("AuthenticatorNotRemoved"));
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
@@ -176,17 +177,17 @@ public partial class MainVM : ObservableObject
|
||||
|
||||
try
|
||||
{
|
||||
LoginConfirmationResult res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
|
||||
var res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
|
||||
if (res.Success)
|
||||
{
|
||||
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
|
||||
SnackbarController.SendSnackbar($"{GetLocalization("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
|
||||
}
|
||||
else
|
||||
{
|
||||
string msg = res.Error switch
|
||||
{
|
||||
LoginConfirmationError.NoRequests => GetLocalizationOrDefault("ConfirmLoginFailedNoRequests"),
|
||||
LoginConfirmationError.MoreThanOneRequest => GetLocalizationOrDefault("ConfirmLoginFailedMoreThanOneRequest"), //TODO
|
||||
LoginConfirmationError.NoRequests => GetLocalization("ConfirmLoginFailedNoRequests"),
|
||||
LoginConfirmationError.MoreThanOneRequest => GetLocalization("ConfirmLoginFailedMoreThanOneRequest"), //TODO
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
SnackbarController.SendSnackbar(msg);
|
||||
@@ -198,4 +199,11 @@ public partial class MainVM : ObservableObject
|
||||
Shell.Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string GetLocalization(string key)
|
||||
{
|
||||
const string locPath = "MainVM";
|
||||
return LocManager.GetCodeBehindOrDefault(key, locPath, key);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using SteamLib.SteamMobile;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
@@ -12,7 +16,7 @@ public partial class MainVM
|
||||
{
|
||||
private Timer _codeTimer;
|
||||
[ObservableProperty] private double _codeProgress;
|
||||
[ObservableProperty] private string _code;
|
||||
[ObservableProperty] private string _code = "Code";
|
||||
|
||||
|
||||
[MemberNotNull(nameof(_codeTimer))]
|
||||
@@ -21,12 +25,12 @@ public partial class MainVM
|
||||
_codeTimer = new Timer(UpdateCode, null, 0, 1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void UpdateCode(object? state = null)
|
||||
{
|
||||
var currentTime = TimeAligner.GetSteamTime();
|
||||
var untilChange = currentTime - currentTime / 30L * 30L;
|
||||
var untilChange = currentTime % 30;
|
||||
var codeProgress = untilChange / 30D * 100;
|
||||
|
||||
string? code = null;
|
||||
@@ -37,11 +41,35 @@ public partial class MainVM
|
||||
|
||||
|
||||
if (Application.Current == null) return;
|
||||
Application.Current.Dispatcher.Invoke((string? c) =>
|
||||
Application.Current.Dispatcher.BeginInvoke((string? c) =>
|
||||
{
|
||||
if (Application.Current.MainWindow?.WindowState == WindowState.Minimized) return;
|
||||
CodeProgress = codeProgress;
|
||||
if (c != null) Code = c;
|
||||
}, DispatcherPriority.Background, code);
|
||||
}, DispatcherPriority.DataBind, code);
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false)]
|
||||
private async Task CopyCode()
|
||||
{
|
||||
var selectedMafile = SelectedMafile;
|
||||
if (selectedMafile == null) return;
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(Code);
|
||||
Code = LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(200);
|
||||
selectedMafile = SelectedMafile;
|
||||
if (selectedMafile != null)
|
||||
Code = SteamGuardCodeGenerator.GenerateCode(selectedMafile.SharedSecret);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -40,17 +40,20 @@ public partial class MainVM //Confirmations
|
||||
_confirmationsLoadedForMafile = maf;
|
||||
OnPropertyChanged(nameof(ConfirmationsVisible));
|
||||
Confirmations.Clear();
|
||||
var marketConfirmations = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction).ToList();
|
||||
var marketConfirmations = conf
|
||||
.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction)
|
||||
.Cast<MarketConfirmation>()
|
||||
.ToList();
|
||||
|
||||
if (marketConfirmations.Count > 1)
|
||||
{
|
||||
var indexOfLast = conf.IndexOf(marketConfirmations.First());
|
||||
var indexOfLast = conf.IndexOf(marketConfirmations.Last());
|
||||
foreach (var mCon in marketConfirmations)
|
||||
{
|
||||
conf.Remove(mCon);
|
||||
}
|
||||
|
||||
var mConf = new MarketMultiConfirmation(marketConfirmations.Cast<MarketConfirmation>());
|
||||
var mConf = new MarketMultiConfirmation(marketConfirmations);
|
||||
conf.Insert(indexOfLast, mConf);
|
||||
}
|
||||
|
||||
@@ -60,12 +63,25 @@ public partial class MainVM //Confirmations
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task Confirm(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, true);
|
||||
}
|
||||
[RelayCommand]
|
||||
private Task Cancel(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, false);
|
||||
}
|
||||
|
||||
private async Task SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
|
||||
{
|
||||
bool result;
|
||||
try
|
||||
{
|
||||
if (confirmation is MarketMultiConfirmation multi)
|
||||
if (confirmation is MarketMultiConfirmation multi)
|
||||
{
|
||||
result = await MaClient.SendMultipleConfirmation(mafile, multi.Confirmations, confirm);
|
||||
}
|
||||
@@ -86,20 +102,7 @@ public partial class MainVM //Confirmations
|
||||
}
|
||||
else
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("ConfirmationError"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("ConfirmationError"));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task Confirm(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, true);
|
||||
}
|
||||
[RelayCommand]
|
||||
private Task Cancel(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, false);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Exceptions;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
using AutoUpdaterDotNET;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -39,7 +38,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
if (mafilePath != null)
|
||||
{
|
||||
path = $"/select, \"{mafilePath}\""; ;
|
||||
path = $"/select, \"{mafilePath}\"";
|
||||
}
|
||||
|
||||
try
|
||||
@@ -66,7 +65,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
var fs = openFileDialog.ShowDialog();
|
||||
if (fs != true) return Task.CompletedTask;
|
||||
var path = openFileDialog.FileName;
|
||||
return AddMafile(new[] { path });
|
||||
return AddMafile([path]);
|
||||
|
||||
}
|
||||
public async Task AddMafile(string[] path)
|
||||
@@ -88,7 +87,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmMafileOverwrite"));
|
||||
confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmMafileOverwrite"));
|
||||
|
||||
if (confirmOverwrite == true)
|
||||
{
|
||||
@@ -116,24 +115,24 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
else
|
||||
{
|
||||
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("MafileImportError")} {Path.GetFileName(str)}{GetLocalizationOrDefault("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
|
||||
SnackbarController.SendSnackbar($"{GetLocalization("MafileImportError")} {Path.GetFileName(str)}{GetLocalization("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var msg = GetLocalizationOrDefault("Import");
|
||||
var msg = GetLocalization("Import");
|
||||
if (added > 0)
|
||||
{
|
||||
msg += $" {GetLocalizationOrDefault("ImportAdded")} {added}.";
|
||||
msg += $" {GetLocalization("ImportAdded")} {added}.";
|
||||
}
|
||||
if (notAdded > 0)
|
||||
{
|
||||
msg += $" {GetLocalizationOrDefault("ImportSkipped")} {notAdded}.";
|
||||
msg += $" {GetLocalization("ImportSkipped")} {notAdded}.";
|
||||
}
|
||||
|
||||
if (errors > 0)
|
||||
{
|
||||
msg += $" {GetLocalizationOrDefault("ImportErrors")} {errors}.";
|
||||
msg += $" {GetLocalization("ImportErrors")} {errors}.";
|
||||
}
|
||||
SnackbarController.SendSnackbar(msg, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
@@ -156,7 +155,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
try
|
||||
{
|
||||
await MaClient.LoginAgain(data, password, loginAgainVm.SavePassword, waitDialog);
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
|
||||
}
|
||||
catch (LoginException ex)
|
||||
{
|
||||
@@ -189,7 +188,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
{
|
||||
if (SelectedMafile == null) return;
|
||||
var confirm =
|
||||
await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("RemoveMafileConfirmation"));
|
||||
await DialogsController.ShowConfirmCancelDialog(GetLocalization("RemoveMafileConfirmation"));
|
||||
|
||||
if (!confirm) return;
|
||||
try
|
||||
@@ -198,12 +197,12 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRemoveAlreadyExist"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("CantRemoveAlreadyExist"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SnackbarController.SendSnackbar(
|
||||
$"{GetLocalizationOrDefault("CantRemoveMafile")} {ex.Message}");
|
||||
$"{GetLocalization("CantRemoveMafile")} {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +218,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CopyMafileFromBuffer()
|
||||
private async Task PasteMafilesFromClipboard()
|
||||
{
|
||||
StringCollection files;
|
||||
try
|
||||
@@ -250,7 +249,40 @@ public partial class MainVM //File //TODO: Refactor
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(maf.AccountName);
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("LoginCopied"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("LoginCopied"));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 19)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
|
||||
}
|
||||
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyMafile(object? mafile)
|
||||
{
|
||||
if (mafile is not Mafile maf) return;
|
||||
var i = 0;
|
||||
var path = Storage.TryFindMafilePath(maf);
|
||||
if (path == null)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalization("MafileNotCopied"));
|
||||
return;
|
||||
}
|
||||
|
||||
while (i < 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetFileDropList([path]);
|
||||
SnackbarController.SendSnackbar(GetLocalization("MafileCopied"));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace NebulaAuth.ViewModel;
|
||||
public partial class MainVM //Groups
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> _groups = new();
|
||||
private ObservableCollection<string> _groups = [];
|
||||
|
||||
|
||||
public string? SelectedGroup
|
||||
@@ -109,6 +109,7 @@ public partial class MainVM //Groups
|
||||
|
||||
private void PerformQuery()
|
||||
{
|
||||
MaacDisplay = false;
|
||||
if (string.IsNullOrWhiteSpace(SelectedGroup) && string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
MaFiles = Storage.MaFiles;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using NebulaAuth.Core;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM
|
||||
{
|
||||
private const string LOC_PATH = "MainVM";
|
||||
private static string? GetLocalization(string key)
|
||||
{
|
||||
return LocManager.GetCodeBehind(LOC_PATH, key);
|
||||
}
|
||||
|
||||
private static string GetLocalizationOrDefault(string key)
|
||||
{
|
||||
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Model.MAAC;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM //MAAC
|
||||
{
|
||||
public bool MaacDisplay
|
||||
{
|
||||
get => _maacDisplay;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _maacDisplay, value))
|
||||
{
|
||||
SwitchMAACDisplay(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool _maacDisplay;
|
||||
|
||||
public bool MarketTimerEnabled
|
||||
{
|
||||
get => SelectedMafile?.LinkedClient?.AutoConfirmMarket ?? false;
|
||||
set => SetMarketTimer(value);
|
||||
}
|
||||
public bool TradeTimerEnabled
|
||||
{
|
||||
get => SelectedMafile?.LinkedClient?.AutoConfirmTrades ?? false;
|
||||
set => SetTradeTimer(value);
|
||||
}
|
||||
|
||||
public int TimerCheckSeconds
|
||||
{
|
||||
get => Settings.Instance.TimerSeconds;
|
||||
set => SetTimer(value);
|
||||
}
|
||||
|
||||
private void SetMarketTimer(bool value)
|
||||
{
|
||||
var selectedMafile = SelectedMafile;
|
||||
if (selectedMafile == null) return;
|
||||
if (value && selectedMafile.LinkedClient == null)
|
||||
{
|
||||
MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile);
|
||||
}
|
||||
if (!value && selectedMafile is { LinkedClient.AutoConfirmTrades: false })
|
||||
{
|
||||
MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile);
|
||||
}
|
||||
|
||||
if (selectedMafile.LinkedClient != null)
|
||||
{
|
||||
selectedMafile.LinkedClient.AutoConfirmMarket = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTradeTimer(bool value)
|
||||
{
|
||||
var selectedMafile = SelectedMafile;
|
||||
if (selectedMafile == null) return;
|
||||
if (value && selectedMafile.LinkedClient == null)
|
||||
{
|
||||
MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile);
|
||||
}
|
||||
|
||||
if (!value && selectedMafile is { LinkedClient.AutoConfirmMarket: false })
|
||||
{
|
||||
MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile);
|
||||
}
|
||||
|
||||
if (selectedMafile.LinkedClient != null)
|
||||
{
|
||||
selectedMafile.LinkedClient.AutoConfirmTrades = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTimer(int value)
|
||||
{
|
||||
var timerCheckSeconds = Settings.TimerSeconds;
|
||||
if (timerCheckSeconds == value) return;
|
||||
if (timerCheckSeconds < 10)
|
||||
{
|
||||
timerCheckSeconds = 10; //Guard
|
||||
}
|
||||
if (value < 10)
|
||||
{
|
||||
value = timerCheckSeconds;
|
||||
SnackbarController.SendSnackbar(GetLocalization("TimerTooFast"));
|
||||
}
|
||||
Settings.TimerSeconds = value;
|
||||
OnPropertyChanged(nameof(TimerCheckSeconds));
|
||||
if (timerCheckSeconds != TimerCheckSeconds)
|
||||
SnackbarController.SendSnackbar(GetLocalization("TimerChanged"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchMAAC(bool market)
|
||||
{
|
||||
var maf = SelectedMafile;
|
||||
if (maf == null) return;
|
||||
SwitchMAACOn([maf], market);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchMAACOnGroup(bool market)
|
||||
{
|
||||
var group = SelectedGroup;
|
||||
if(group == null) return;
|
||||
var mafilesInGroup = MaFiles.Where(m => group.Equals(m.Group));
|
||||
SwitchMAACOn(mafilesInGroup, market);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchMAACOnAll(bool market)
|
||||
{
|
||||
SwitchMAACOn(MaFiles, market);
|
||||
}
|
||||
|
||||
private void SwitchMAACOn(IEnumerable<Mafile> mafiles, bool market)
|
||||
{
|
||||
mafiles = mafiles.ToArray();
|
||||
|
||||
var turnOn = mafiles.All(m => m.LinkedClient == null || GetCurrentMode(m.LinkedClient) == false);
|
||||
if (turnOn)
|
||||
{
|
||||
foreach (var mafile in mafiles)
|
||||
{
|
||||
MultiAccountAutoConfirmer.TryAddToConfirm(mafile);
|
||||
SetCurrentMode(mafile.LinkedClient, turnOn);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var mafile in mafiles)
|
||||
{
|
||||
SetCurrentMode(mafile.LinkedClient, turnOn);
|
||||
if(PretendsToRemove(mafile))
|
||||
MultiAccountAutoConfirmer.RemoveFromConfirm(mafile);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool PretendsToRemove(Mafile mafile) => mafile.LinkedClient is {AutoConfirmMarket: false, AutoConfirmTrades: false};
|
||||
bool GetCurrentMode(PortableMaClient linkedClient) => market ? linkedClient.AutoConfirmMarket : linkedClient.AutoConfirmTrades;
|
||||
void SetCurrentMode(PortableMaClient? linkedClient, bool value)
|
||||
{
|
||||
if (linkedClient == null) return;
|
||||
if (market)
|
||||
{
|
||||
linkedClient.AutoConfirmMarket = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
linkedClient.AutoConfirmTrades = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchMAACDisplay(bool newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
MaFiles = MultiAccountAutoConfirmer.Clients;
|
||||
}
|
||||
else
|
||||
{
|
||||
PerformQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM //Other
|
||||
{
|
||||
private void SessionHandlerOnLoginCompleted(object? sender, EventArgs e)
|
||||
{
|
||||
var currentSession = DialogHost.GetDialogSession(null);
|
||||
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (currentSession is { Content: WaitLoginDialog, IsEnded: false })
|
||||
{
|
||||
try
|
||||
{
|
||||
currentSession.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void SessionHandlerOnLoginStarted(object? sender, EventArgs e)
|
||||
{
|
||||
if (DialogHost.IsDialogOpen(null)) return;
|
||||
await Application.Current.Dispatcher.BeginInvoke(async () =>
|
||||
{
|
||||
await DialogHost.Show(new WaitLoginDialog());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using NebulaAuth.Model.Entities;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NebulaAuth.Model.Comparers;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -23,16 +22,17 @@ public partial class MainVM
|
||||
{
|
||||
OnPropertyChanged(nameof(IsDefaultProxy));
|
||||
OnProxyChanged();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private MaProxy? _selectedProxy;
|
||||
|
||||
[ObservableProperty] private bool _proxyExist = true;
|
||||
public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null;
|
||||
public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null && SelectedMafile != null;
|
||||
private bool _handleProxyChange;
|
||||
|
||||
//TODO: Refactor this method
|
||||
private void SetCurrentProxy()
|
||||
{
|
||||
if (ReferenceEquals(_selectedProxy, SelectedMafile?.Proxy) == false && _selectedProxy?.Equals(SelectedMafile?.Proxy) == false)
|
||||
@@ -41,6 +41,7 @@ public partial class MainVM
|
||||
if (SelectedMafile == null)
|
||||
{
|
||||
SelectedProxy = null;
|
||||
ProxyExist = true;
|
||||
return;
|
||||
}
|
||||
if (SelectedMafile.Proxy == null)
|
||||
@@ -52,7 +53,7 @@ public partial class MainVM
|
||||
var existed = Proxies.FirstOrDefault(p => p.Id == SelectedMafile.Proxy.Id);
|
||||
|
||||
|
||||
if (existed == null || ProxyDataComparer.Equal(existed.Data, SelectedMafile.Proxy.Data) == false)
|
||||
if (existed == null || existed.Data.Equals(SelectedMafile.Proxy.Data) == false)
|
||||
{
|
||||
SelectedProxy = SelectedMafile.Proxy;
|
||||
}
|
||||
@@ -127,7 +128,7 @@ public partial class MainVM
|
||||
var canSave = Storage.ValidateCanSave(data);
|
||||
if (!canSave)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRetrieveSteamIDToUpdate"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("CantRetrieveSteamIDToUpdate"));
|
||||
}
|
||||
return canSave;
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using AchiesUtilities.Web.Proxy;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Utility;
|
||||
@@ -19,12 +20,12 @@ using SteamLib.SteamMobile.AuthenticatorLinker;
|
||||
using SteamLib.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using NebulaAuth.Core;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
@@ -60,8 +61,8 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
private TaskCompletionSource _emailConfTcs = new();
|
||||
private TaskCompletionSource<string> _linkCodeTcs = new();
|
||||
|
||||
private bool isLinkStarted;
|
||||
|
||||
private bool _isLinkStarted;
|
||||
private string _rCode = string.Empty;
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ProceedCommand))]
|
||||
private bool _canProceed = true;
|
||||
@@ -178,12 +179,12 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
|
||||
#endregion
|
||||
|
||||
if (isLinkStarted)
|
||||
if (_isLinkStarted)
|
||||
goto linkStarted;
|
||||
|
||||
try
|
||||
{
|
||||
isLinkStarted = true;
|
||||
_isLinkStarted = true;
|
||||
var linkOptions = new LinkOptions(Client, LoginV2Executor.NullConsumer, this,
|
||||
null, this, this, backupHandler: Backup, Logger2);
|
||||
_linker = new SteamAuthenticatorLinker(linkOptions);
|
||||
@@ -194,10 +195,11 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
Storage.SaveMafile(mafile);
|
||||
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
|
||||
HintText =
|
||||
string.Format(GetLocalizationOrDefault("MafileLinked"),
|
||||
string.Format(GetLocalizationOrDefault("MafileLinked"),
|
||||
mafile.RevocationCode,
|
||||
mafile.SessionData?.SteamId.Steam64);
|
||||
|
||||
_rCode = mafile.RevocationCode ?? string.Empty;
|
||||
CanProceed = true;
|
||||
return;
|
||||
}
|
||||
@@ -312,11 +314,12 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
IsLogin = false;
|
||||
IsFieldVisible = true;
|
||||
IsEmailCode = false;
|
||||
isLinkStarted = false;
|
||||
_isLinkStarted = false;
|
||||
IsPhoneNumber = false;
|
||||
IsEmailConfirmation = false;
|
||||
CanProceed = true;
|
||||
_emailCodeTcs = new TaskCompletionSource<string>();
|
||||
_rCode = string.Empty;
|
||||
}
|
||||
|
||||
private void Backup(MobileDataExtended data)
|
||||
@@ -401,6 +404,33 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
|
||||
#endregion
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenTroubleshooting()
|
||||
{
|
||||
const string troubleshootingURI =
|
||||
"https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/docs/{0}/LinkingTroubleshooting";
|
||||
|
||||
var localized = string.Format(troubleshootingURI, LocManager.GetCurrentLanguageCode());
|
||||
Process.Start(new ProcessStartInfo(new Uri(localized).ToString())
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyCode()
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(_rCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex, "Error whily copying RCode");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string GetLocalizationOrDefault(string key)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,4 @@ public partial class LoginAgainVM : ObservableObject
|
||||
|
||||
|
||||
public bool IsFormValid => !string.IsNullOrWhiteSpace(Password);
|
||||
|
||||
public LoginAgainVM()
|
||||
{ }
|
||||
}
|
||||
@@ -11,11 +11,6 @@ public partial class SettingsVM : ObservableObject
|
||||
{
|
||||
public Settings Settings => Settings.Instance;
|
||||
|
||||
public bool DisableTimersOnChange
|
||||
{
|
||||
get => Settings.DisableTimersOnChange;
|
||||
set => Settings.DisableTimersOnChange = value;
|
||||
}
|
||||
public BackgroundMode BackgroundMode
|
||||
{
|
||||
get => Settings.BackgroundMode;
|
||||
@@ -115,14 +110,19 @@ public partial class SettingsVM : ObservableObject
|
||||
set => Settings.UseAccountNameAsMafileName = value;
|
||||
}
|
||||
|
||||
[ObservableProperty] private string _password;
|
||||
public bool IgnorePatchTuesdayErrors
|
||||
{
|
||||
get => Settings.IgnorePatchTuesdayErrors;
|
||||
set => Settings.IgnorePatchTuesdayErrors = value;
|
||||
}
|
||||
|
||||
[ObservableProperty] private string? _password;
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public void SetPassword()
|
||||
private void SetPassword()
|
||||
{
|
||||
PHandler.SetPassword(Password);
|
||||
Settings.IsPasswordSet = PHandler.IsPasswordSet;
|
||||
Settings.IsPasswordSet = PHandler.SetPassword(Password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public partial class UpdaterVM : ObservableObject
|
||||
public class UpdaterVM : ObservableObject
|
||||
{
|
||||
|
||||
public UpdateInfoEventArgs UpdateInfoEventArgs { get; }
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"ru": "Чтобы начать пользоваться программой вы можете привязать аккаунт через меню \"Аккаунт\", либо импортировать существующие мафайлы одним из способов:\n1. Скопировать их в папку mafiles и перезапустить приложение\n2. Перетянуть файлы прямо в окно программы\n3. Скопировать файлы и нажать CTRL+V в окне программы\n4. Через меню \"Файл\" - \"Импорт\"",
|
||||
"en": "To start using the program, you can link an account through the \"Account\" menu, or import existing mafiles in one of the following ways:\n1. Copy them to the mafiles folder and restart the application\n2. Drag files directly into the program window\n3. Copy files and press CTRL+V in the program window\n4. Through the \"File\" - \"Import\" menu",
|
||||
"ua": "Щоб почати користуватися програмою, ви можете прив'язати акаунт через меню \"Акаунт\", або імпортувати існуючі мафайли одним із способів:\n1. Скопіювати їх у папку mafiles та перезапустити програму\n2. Перетягнути файли безпосередньо в вікно програми\n3. Скопіювати файли та натиснути CTRL+V в вікні програми\n4. Через меню \"Файл\" - \"Імпорт\""
|
||||
}
|
||||
}
|
||||
},
|
||||
"Menu": {
|
||||
"File": {
|
||||
@@ -172,14 +172,31 @@
|
||||
}
|
||||
},
|
||||
"TradeTimerHint": {
|
||||
"en": "Trade",
|
||||
"ru": "Трейд",
|
||||
"ua": "Трейд"
|
||||
"en": "Auto-confirm trades. RMC for mass control",
|
||||
"ru": "Автоподтверждение трейдов. ПКМ для массового управления",
|
||||
"ua": "Автопідтвердження трейдів. ПКМ для масового управління"
|
||||
},
|
||||
"MarketTimerHint": {
|
||||
"en": "Market",
|
||||
"ru": "Маркет",
|
||||
"ua": "Маркет"
|
||||
"en": "Auto-confirm market listings. RMC for mass control",
|
||||
"ru": "Автоподтверждение лотов на маркете. ПКМ для массового управления",
|
||||
"ua": "Автопідтвердження лотів на маркеті. ПКМ для масового управління"
|
||||
},
|
||||
"TimersContextMenu": {
|
||||
"SwitchMAACGroup": {
|
||||
"en": "Switch in group",
|
||||
"ru": "Переключить в группе",
|
||||
"ua": "Перемкнути в групі"
|
||||
},
|
||||
"SwitchMAACAll": {
|
||||
"en": "Switch all",
|
||||
"ru": "Переключить все",
|
||||
"ua": "Перемкнути всі"
|
||||
}
|
||||
},
|
||||
"ShowAutoConfirmAccountsHint": {
|
||||
"en": "Show accounts with auto-confirmation enabled.",
|
||||
"ru": "Показать аккаунты с включенным автоподтверждением.",
|
||||
"ua": "Показати акаунти з включеним автопідтвердженням."
|
||||
}
|
||||
},
|
||||
"LeftPart": {
|
||||
@@ -247,6 +264,11 @@
|
||||
"ru": "Скопировать логин",
|
||||
"ua": "Скопіювати логін"
|
||||
},
|
||||
"CopyMafile": {
|
||||
"en": "Copy mafile",
|
||||
"ru": "Скопировать мафайл",
|
||||
"ua": "Скопіювати мафайл"
|
||||
},
|
||||
"AddToGroup": {
|
||||
"en": "Add to group",
|
||||
"ru": "Добавить в группу",
|
||||
@@ -300,11 +322,6 @@
|
||||
"ua": "Без фону"
|
||||
}
|
||||
},
|
||||
"DisableTimersOnSwitch": {
|
||||
"en": "Disable timers on account switch",
|
||||
"ru": "Отключать таймеры при смене аккаунта",
|
||||
"ua": "Вимикати таймери при зміні акаунта"
|
||||
},
|
||||
"MinimizeToTray": {
|
||||
"en": "Minimize to tray",
|
||||
"ru": "Сворачивать в трей",
|
||||
@@ -356,7 +373,19 @@
|
||||
"en": "Use account name on mafiles",
|
||||
"ru": "Использовать имя аккаунта на мафайлах",
|
||||
"ua": "Використовувати ім'я акаунта на мафайлах"
|
||||
},
|
||||
"IgnorePatchTuesdayErrors": {
|
||||
"en": "Ignore Patch Tuesday errors in timer (exp.)",
|
||||
"ru": "Игнорировать ошибки Patch Tuesday в таймере (эксп.)",
|
||||
"ua": "Ігнорувати помилки Patch Tuesday у таймері (експ.)"
|
||||
},
|
||||
"IgnorePatchTuesdayErrorsHint": {
|
||||
"en": "Ignore errors that occur every Tuesday (Wednesday night in GMT) when Steam doing technical works and auto-confirm timers turns off unnecessarily",
|
||||
"ru": "Игнорировать ошибки, которые возникают каждый вторник (ночь среды по GMT) когда Steam проводит технические работы и автоподтверждения отключаются без надобности",
|
||||
"ua": "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби"
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
"LoginAgainDialog": {
|
||||
"Title": {
|
||||
@@ -398,7 +427,7 @@
|
||||
"en": "Press 'DEL' to remove",
|
||||
"ru": "Нажмите 'DEL' для удаления",
|
||||
"ua": "Натисніть 'DEL' для видалення"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProxyManagerDialog": {
|
||||
"Title": {
|
||||
@@ -430,6 +459,11 @@
|
||||
"ru": "Привязка",
|
||||
"ua": "Прив'язка"
|
||||
},
|
||||
"GotErrorHyperlinkText": {
|
||||
"en": "(getting error?)",
|
||||
"ru": "(возникает ошибка?)",
|
||||
"ua": "(виникає помилка?)"
|
||||
},
|
||||
"Proxy": {
|
||||
"en": "Proxy",
|
||||
"ru": "Прокси",
|
||||
@@ -451,14 +485,14 @@
|
||||
"ua": "Номер телефону"
|
||||
},
|
||||
"EmailLink": {
|
||||
"en": "Email link",
|
||||
"ru": "Ссылка из письма",
|
||||
"ua": "Посилання з листа"
|
||||
"en": "EMail link",
|
||||
"ru": "Ссылка из email",
|
||||
"ua": "Посилання з email"
|
||||
},
|
||||
"SmsOrCode": {
|
||||
"en": "Sms or code",
|
||||
"ru": "Смс или код",
|
||||
"ua": "Смс або код"
|
||||
"en": "SMS or code",
|
||||
"ru": "СМС или код",
|
||||
"ua": "СМС або код"
|
||||
},
|
||||
"Completed": {
|
||||
"en": "Completed",
|
||||
@@ -600,26 +634,6 @@
|
||||
"en": "Can't remove mafile:",
|
||||
"ua": "Не вдалося видалити (перемістити) мафайл:"
|
||||
},
|
||||
"TimerConfirmed": {
|
||||
"ru": "Авто: подтверждено ",
|
||||
"en": "Auto: confirmed ",
|
||||
"ua": "Авто: підтверджено "
|
||||
},
|
||||
"TimerNotConfirmed": {
|
||||
"ru": "Авто: подтверждение не сработало",
|
||||
"en": "Auto: confirmation was unsuccessful",
|
||||
"ua": "Авто: підтвердження не спрацювало"
|
||||
},
|
||||
"TimerSessionError": {
|
||||
"ru": "Авто: необходимо обновить сессию или перелогиниться",
|
||||
"en": "Auto: need to refresh session or relogin",
|
||||
"ua": "Авто: необхідно оновити сесію або перелогінитися"
|
||||
},
|
||||
"TimerPrefix": {
|
||||
"ru": "Авто: ",
|
||||
"en": "Auto: ",
|
||||
"ua": "Авто: "
|
||||
},
|
||||
"TimerTooFast": {
|
||||
"ru": "Слишком быстрый таймер.",
|
||||
"en": "Too fast timer.",
|
||||
@@ -644,7 +658,17 @@
|
||||
"en": "Login copied",
|
||||
"ru": "Логин скопирован",
|
||||
"ua": "Логін скопійовано"
|
||||
}
|
||||
},
|
||||
"MafileCopied": {
|
||||
"en": "Mafile copied",
|
||||
"ru": "Мафайл скопирован",
|
||||
"ua": "Мафайл скопійовано"
|
||||
},
|
||||
"MafileNotCopied": {
|
||||
"en": "Mafile not copied",
|
||||
"ru": "Мафайл не скопирован",
|
||||
"ua": "Мафайл не скопійовано"
|
||||
}
|
||||
},
|
||||
"ErrorTranslator": {
|
||||
"Login": {
|
||||
@@ -892,9 +916,9 @@
|
||||
"ua": "Не вдається згенерувати коди"
|
||||
},
|
||||
"InvalidStateWithStatus2": {
|
||||
"ru": "Неверное состояние (InvalidState) со статусом 2. Попробуйте привязку с помощью СМС. Если СМС придет, но ошибка повторится. Нужно попробовать еще раз.",
|
||||
"en": "Invalid state (InvalidState) with status 2. Try to attach with SMS. If SMS will come, but error will repeat. You need to try again.",
|
||||
"ua": "Невірний стан (InvalidState) зі статусом 2. Спробуйте прив'язати з допомогою СМС. Якщо СМС прийде, але помилка повториться. Потрібно спробувати ще раз."
|
||||
"ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.",
|
||||
"en": "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
|
||||
"ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна."
|
||||
},
|
||||
"GeneralFailure": {
|
||||
"ru": "General Failure",
|
||||
@@ -904,15 +928,15 @@
|
||||
}
|
||||
},
|
||||
"ExceptionHandler": {
|
||||
"SessionExpiredException": {
|
||||
"ru": "Сессия истекла. Попробуйте обновить ее через меню",
|
||||
"en": "Session expired. Try to refresh it through menu",
|
||||
"ua": "Сесія закінчилася. Спробуйте оновити її через меню"
|
||||
},
|
||||
"SessionInvalidException": {
|
||||
"ru": "Сессия невалидна. Нужно залогиниться заново",
|
||||
"en": "Session invalid. Need to login again",
|
||||
"ua": "Сесія невалідна. Потрібно залогінитися знову"
|
||||
"ru": "Сессия истекла. Попробуйте обновить ее через меню или залогиниться заново",
|
||||
"en": "Session expired. Try to refresh it through menu or login again",
|
||||
"ua": "Сесія прострочена. Спробуйте оновити її через меню або залогінитися знову"
|
||||
},
|
||||
"SessionExpiredException": {
|
||||
"ru": "Сессия полностью истекла. Нужно залогиниться заново",
|
||||
"en": "Session fully expired. Need to login again",
|
||||
"ua": "Сесія повніст'ю прострочена'. Потрібно залогінитися знову"
|
||||
},
|
||||
"TaskCanceledException": {
|
||||
"ru": "Произошла отмена операции",
|
||||
@@ -938,6 +962,23 @@
|
||||
"ru": "Неизвестная ошибка: ",
|
||||
"en": "Unknown error: ",
|
||||
"ua": "Невідома помилка: "
|
||||
},
|
||||
"CantLoadConfirmationsException": {
|
||||
"Common": {
|
||||
"ru": "Не удалось загрузить потверждения из-за ошибки Steam: ",
|
||||
"en": "Can't load confirmations due to Steam error: ",
|
||||
"ua": "Не вдалося завантажити підтвердження через помилку Steam: "
|
||||
},
|
||||
"TryAgainLater": {
|
||||
"ru": "Попробуйте позже",
|
||||
"en": "Try again later",
|
||||
"ua": "Спробуйте пізніше"
|
||||
},
|
||||
"NotSetupToReceiveConfirmations": {
|
||||
"ru": "Аккаунт не настроен на получение подтверждений",
|
||||
"en": "Account is not set up to receive confirmations",
|
||||
"ua": "Акаунт не налаштований на отримання підтверджень"
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
@@ -1022,9 +1063,31 @@
|
||||
"ua": "На телефон {0} було відправлено СМС"
|
||||
},
|
||||
"EnterPhoneNumber": {
|
||||
"ru": "Введите номер телефона (без знака '+' и др. символов)",
|
||||
"en": "Enter phone number (without '+' and other symbols)",
|
||||
"ua": "Введіть номер телефону (без знака '+' та ін. символів)"
|
||||
"ru": "Введите номер телефона (без знака '+' и др. символов). По-желанию.",
|
||||
"en": "Enter phone number (without '+' and other symbols). Optional",
|
||||
"ua": "Введіть номер телефону (без знака '+' та ін. символів). За бажанням"
|
||||
}
|
||||
},
|
||||
"MAAC": {
|
||||
"TimerConfirmed": {
|
||||
"ru": "Авто: подтверждено ",
|
||||
"en": "Auto: confirmed ",
|
||||
"ua": "Авто: підтверджено "
|
||||
},
|
||||
"TimerNotConfirmed": {
|
||||
"ru": "Авто: подтверждение не сработало",
|
||||
"en": "Auto: confirmation was unsuccessful",
|
||||
"ua": "Авто: підтвердження не спрацювало"
|
||||
},
|
||||
"TimerSessionError": {
|
||||
"ru": "необходимо обновить сессию или перелогиниться",
|
||||
"en": "need to refresh session or relogin",
|
||||
"ua": "необхідно оновити сесію або перелогінитися"
|
||||
},
|
||||
"TimerPrefix": {
|
||||
"ru": "Авто ",
|
||||
"en": "Auto ",
|
||||
"ua": "Авто "
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>1.5.2.0</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.2/NebulaAuth.1.5.2.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.2.html</changelog>
|
||||
<version>1.5.3.0</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.3/NebulaAuth.1.5.3.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.3.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
</item>
|
||||
@@ -54,7 +54,7 @@ public static class SteamAuthenticatorLinkerApi
|
||||
};
|
||||
|
||||
var resp = await client.PostProtoMsg<AddAuthenticator_Response>(reqUri, req);
|
||||
if (resp is {Result: EResult.InvalidState, ResponseMsg.Status: 2})
|
||||
if (resp is { Result: EResult.InvalidState, ResponseMsg.Status: 2 })
|
||||
{
|
||||
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidStateWithStatus2);
|
||||
}
|
||||
@@ -73,8 +73,8 @@ public static class SteamAuthenticatorLinkerApi
|
||||
{
|
||||
var validateSmsReq = new FinalizeAddAuthenticator_Request
|
||||
{
|
||||
SteamId = data.SteamId.Steam64.ToUlong(),
|
||||
AuthenticatorTime = (ulong) time,
|
||||
SteamId = data.SteamId.Steam64.ToUlong(),
|
||||
AuthenticatorTime = (ulong)time,
|
||||
ConfirmationCode = confirmationCode,
|
||||
ValidateConfirmationCode = true
|
||||
};
|
||||
@@ -182,7 +182,7 @@ public static class SteamAuthenticatorLinkerApi
|
||||
while (i < 5)
|
||||
{
|
||||
i++;
|
||||
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri, new EmptyMessage()); ;
|
||||
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri, new EmptyMessage());
|
||||
|
||||
if (resp.IsWaiting == false) return true;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Net;
|
||||
using JetBrains.Annotations;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib.Core;
|
||||
using SteamLib.Exceptions;
|
||||
@@ -7,14 +6,14 @@ using SteamLib.ProtoCore;
|
||||
using SteamLib.ProtoCore.Enums;
|
||||
using SteamLib.ProtoCore.Exceptions;
|
||||
using SteamLib.ProtoCore.Services;
|
||||
using System.Net;
|
||||
using SteamLib.Account;
|
||||
|
||||
namespace SteamLib.Api.Mobile;
|
||||
|
||||
|
||||
[PublicAPI]
|
||||
public static class SteamMobileApi
|
||||
//TODO: cancellation token
|
||||
//TODO: HealthMonitor
|
||||
{
|
||||
|
||||
private const string GENERATE_ACCESS_TOKEN =
|
||||
@@ -27,30 +26,31 @@ public static class SteamMobileApi
|
||||
/// <param name="client"></param>
|
||||
/// <param name="refreshToken"></param>
|
||||
/// <param name="steamId"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns>Refreshed AccessToken</returns>
|
||||
/// <exception cref="SessionInvalidException"></exception>
|
||||
public static async Task<string> RefreshJwt(HttpClient client, string refreshToken, long steamId)
|
||||
public static async Task<string> RefreshJwt(HttpClient client, string refreshToken, SteamId steamId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var req = new GenerateAccessTokenForApp_Request
|
||||
{
|
||||
RefreshToken = refreshToken,
|
||||
SteamId = steamId,
|
||||
SteamId = steamId.Steam64,
|
||||
TokenRenewalType = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var resp = await client.PostProto<GenerateAccessTokenForApp_Response>(GENERATE_ACCESS_TOKEN, req);
|
||||
var resp = await client.PostProto<GenerateAccessTokenForApp_Response>(GENERATE_ACCESS_TOKEN, req, cancellationToken: cancellationToken);
|
||||
return resp.AccessToken;
|
||||
}
|
||||
catch (EResultException ex)
|
||||
when (ex.Result == EResult.AccessDenied)
|
||||
{
|
||||
throw new SessionInvalidException("RefreshToken is not accepted by Steam. You must login again and use new token");
|
||||
throw new SessionPermanentlyExpiredException("RefreshToken is not accepted by Steam. You must login again and use new token");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> HasPhoneAttached(HttpClient client, string sessionId)
|
||||
public static async Task<bool> HasPhoneAttached(HttpClient client, string sessionId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
@@ -60,14 +60,18 @@ public static class SteamMobileApi
|
||||
|
||||
};
|
||||
var content = new FormUrlEncodedContent(data);
|
||||
var resp = await client.PostAsync(SteamConstants.STEAM_COMMUNITY + "steamguard/phoneajax", content);
|
||||
var respContent = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
|
||||
var json = JObject.Parse(respContent);
|
||||
return json["has_phone"]!.Value<bool>();
|
||||
var resp = await client.PostAsync(SteamConstants.STEAM_COMMUNITY + "steamguard/phoneajax", content, cancellationToken);
|
||||
var respContent = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
return SteamLibErrorMonitor.HandleResponse(respContent, () =>
|
||||
{
|
||||
var json = JObject.Parse(respContent);
|
||||
return json["has_phone"]!.Value<bool>();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static async Task<RemoveAuthenticator_Response> RemoveAuthenticator(HttpClient client, string accessToken, string rCode)
|
||||
public static async Task<RemoveAuthenticator_Response> RemoveAuthenticator(HttpClient client, string accessToken, string rCode, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var req = SteamConstants.STEAM_API + "ITwoFactorService/RemoveAuthenticator/v1?access_token=" + accessToken;
|
||||
var reqData = new RemoveAuthenticator_Request
|
||||
@@ -78,12 +82,15 @@ public static class SteamMobileApi
|
||||
};
|
||||
try
|
||||
{
|
||||
return await client.PostProto<RemoveAuthenticator_Response>(req, reqData);
|
||||
return await client.PostProto<RemoveAuthenticator_Response>(req, reqData, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
when (ex.StatusCode is HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new SessionExpiredException("Session expired");
|
||||
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using SteamLib.Core;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.ProtoCore;
|
||||
using SteamLib.ProtoCore.Services;
|
||||
using System.Net;
|
||||
|
||||
namespace SteamLib.Api.Mobile;
|
||||
|
||||
public static class SteamMobileAuthenticatorApi
|
||||
{
|
||||
public static async Task<GetAuthSessionsForAccount_Response> GetAuthSessionsForAccount(HttpClient client, string accessToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" + accessToken;
|
||||
|
||||
try
|
||||
{
|
||||
return await client.GetProto<GetAuthSessionsForAccount_Response>(req, new EmptyMessage(), cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<GetAuthSessionInfo_Response> GetAuthSessionInfo(HttpClient client, string accessToken, ulong clientId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionInfo/v1?access_token=" + accessToken;
|
||||
var reqData = new GetAuthSessionInfo_Request
|
||||
{
|
||||
ClientId = clientId
|
||||
};
|
||||
try
|
||||
{
|
||||
return await client.PostProto<GetAuthSessionInfo_Response>(req, reqData, cancellationToken: cancellationToken);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static async Task<bool> UpdateAuthSessionStatus(HttpClient client, string accessToken, string sharedSecret,
|
||||
UpdateAuthSessionWithMobileConfirmation_Request request)
|
||||
{
|
||||
var req = SteamConstants.STEAM_API + "IAuthenticationService/UpdateAuthSessionWithMobileConfirmation/v1?access_token=" + accessToken;
|
||||
|
||||
request.ComputeSignature(sharedSecret);
|
||||
await client.PostProtoEnsureSuccess(req, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +1,54 @@
|
||||
using SteamLib.Core;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile.Confirmations;
|
||||
using SteamLib.SteamMobile;
|
||||
using System.Net;
|
||||
using AchiesUtilities.Web.Extensions;
|
||||
using SteamLib.Web.Scrappers.JSON;
|
||||
using AchiesUtilities.Web.Extensions;
|
||||
using JetBrains.Annotations;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Core;
|
||||
using SteamLib.Core.StatusCodes;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile;
|
||||
using SteamLib.SteamMobile.Confirmations;
|
||||
using SteamLib.Utility;
|
||||
using SteamLib.Web.Scrappers.JSON;
|
||||
using System.Net;
|
||||
|
||||
namespace SteamLib.Api.Mobile;
|
||||
|
||||
[PublicAPI]
|
||||
public static class SteamMobileConfirmationsApi
|
||||
{
|
||||
private const string CONF = SteamConstants.STEAM_COMMUNITY + "mobileconf";
|
||||
private const string CONF_OP = SteamConstants.STEAM_COMMUNITY + "mobileconf/ajaxop";
|
||||
private const string MULTI_CONF_OP = SteamConstants.STEAM_COMMUNITY + "mobileconf/multiajaxop";
|
||||
|
||||
public static async Task<IEnumerable<Confirmation>> GetConfirmations(HttpClient client, MobileData data, long steamId)
|
||||
public static async Task<IEnumerable<Confirmation>> GetConfirmations(HttpClient client, MobileData data, SteamId steamId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var nvc = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, "list");
|
||||
|
||||
var req = new Uri(CONF + "/getlist" + nvc.ToQueryString());
|
||||
var reqMsg = new HttpRequestMessage(HttpMethod.Get, req);
|
||||
var resp = await client.SendAsync(reqMsg);
|
||||
|
||||
var respStr = await resp.Content.ReadAsStringAsync();
|
||||
var resp = await client.SendAsync(reqMsg, cancellationToken);
|
||||
|
||||
|
||||
if (resp.StatusCode == HttpStatusCode.Redirect)
|
||||
{
|
||||
throw new SessionExpiredException("Mobile session expired");
|
||||
throw new SessionInvalidException("Mobile session expired");
|
||||
}
|
||||
|
||||
var respStr = await resp.Content.ReadAsStringAsync(cancellationToken);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
|
||||
try
|
||||
{
|
||||
return MobileConfirmationScrapper.Scrap(respStr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is not (SessionInvalidException or CantLoadConfirmationsException))
|
||||
{
|
||||
SteamLibErrorMonitor.LogErrorResponse(respStr, ex);
|
||||
throw;
|
||||
|
||||
return HealthMonitor.LogOnException(respStr, MobileConfirmationScrapper.Scrap, typeof(SessionExpiredException));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> SendConfirmation(HttpClient client, Confirmation confirmation, long steamId, MobileData data, bool confirm)
|
||||
public static async Task<bool> SendConfirmation(HttpClient client, Confirmation confirmation, SteamId steamId, MobileData data, bool confirm, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var op = confirm ? "allow" : "cancel";
|
||||
|
||||
@@ -50,8 +62,8 @@ public static class SteamMobileConfirmationsApi
|
||||
query.Add(new KeyValuePair<string, string>("ck", key));
|
||||
|
||||
var req = CONF_OP + query.ToQueryString();
|
||||
var resp = await client.GetStringAsync(req);
|
||||
var successCode = HealthMonitor.LogOnException(resp, () => SteamStatusCode.Translate<SteamStatusCode>(resp, out _));
|
||||
var resp = await client.GetStringAsync(req, cancellationToken);
|
||||
var successCode = SteamLibErrorMonitor.HandleResponse(resp, () => SteamStatusCode.Translate<SteamStatusCode>(resp, out _));
|
||||
|
||||
return successCode.Equals(SteamStatusCode.Ok);
|
||||
|
||||
@@ -59,7 +71,7 @@ public static class SteamMobileConfirmationsApi
|
||||
|
||||
|
||||
public static async Task<bool> SendMultipleConfirmations(HttpClient client, IEnumerable<Confirmation> confirmations,
|
||||
long steamId, MobileData data, bool confirm)
|
||||
SteamId steamId, MobileData data, bool confirm, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var op = confirm ? "allow" : "cancel";
|
||||
var query = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, op).ToList();
|
||||
@@ -72,25 +84,25 @@ public static class SteamMobileConfirmationsApi
|
||||
}
|
||||
|
||||
var content = new FormUrlEncodedContent(query);
|
||||
var resp = await client.PostAsync(MULTI_CONF_OP, content);
|
||||
var respStr = await resp.Content.ReadAsStringAsync();
|
||||
var successCode = HealthMonitor.LogOnException(respStr, () => SteamStatusCode.Translate<SteamStatusCode>(respStr, out _));
|
||||
var resp = await client.PostAsync(MULTI_CONF_OP, content, cancellationToken);
|
||||
var respStr = await resp.Content.ReadAsStringAsync(cancellationToken);
|
||||
var successCode = SteamLibErrorMonitor.HandleResponse(respStr, () => SteamStatusCode.Translate<SteamStatusCode>(respStr, out _));
|
||||
return successCode.Equals(SteamStatusCode.Ok);
|
||||
|
||||
}
|
||||
|
||||
internal static IEnumerable<KeyValuePair<string, string>> GetConfirmationKvp(long steamId, string deviceId, string identitySecret, string tag = "conf")
|
||||
internal static IEnumerable<KeyValuePair<string, string>> GetConfirmationKvp(SteamId steamId, string deviceId, string identitySecret, string tag = "conf")
|
||||
{
|
||||
var time = TimeAligner.GetSteamTime();
|
||||
var hash = EncryptionHelper.GenerateConfirmationHash(time, identitySecret, tag);
|
||||
return new KeyValuePair<string, string>[]
|
||||
{
|
||||
new("p", deviceId),
|
||||
new("a", steamId.ToString()),
|
||||
new("k", hash),
|
||||
new("t", time.ToString()),
|
||||
new("m", "react"),
|
||||
new("tag", tag)
|
||||
};
|
||||
return
|
||||
[
|
||||
KeyValuePair.Create("p", deviceId),
|
||||
KeyValuePair.Create("a", steamId.Steam64.ToString()),
|
||||
KeyValuePair.Create("k", hash),
|
||||
KeyValuePair.Create("t", time.ToString()),
|
||||
KeyValuePair.Create("m", "react"),
|
||||
KeyValuePair.Create("tag", tag)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public static class AdmissionHelper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear and set new session. Not recommended. Uses <see cref="IMobileSessionData.GetMobileToken()"/> for domain <see cref="SteamDomain.Community"/> instead of its own cookie. It's okay to use it only for confirmations. But Market, Trading and other pages won't be authenticated
|
||||
/// Clear and set new session. Not recommended. Uses <see cref="IMobileSessionData.GetMobileToken()"/> for domain <see cref="SteamDomain.Community"/> instead of its own cookie. It's okay to use it only for confirmations. But Market, Trading and other pages won't be authorized
|
||||
/// </summary>
|
||||
public static void SetSteamMobileCookiesWithMobileToken(this CookieContainer container, IMobileSessionData mobileSession,
|
||||
string setLanguage = "english")
|
||||
@@ -210,6 +210,14 @@ public static class AdmissionHelper
|
||||
.Value;
|
||||
}
|
||||
|
||||
public static void ClearAllCookies(this CookieContainer container)
|
||||
{
|
||||
foreach (Cookie allCookie in container.GetAllCookies())
|
||||
{
|
||||
allCookie.Expired = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -2,30 +2,29 @@
|
||||
|
||||
namespace SteamLib.Authentication.LoginV2;
|
||||
|
||||
class FinalizeLoginJson
|
||||
public class FinalizeLoginJson
|
||||
{
|
||||
[JsonProperty("steamID")]
|
||||
public ulong SteamId { get; set; }
|
||||
|
||||
[JsonProperty("transfer_info")]
|
||||
public List<TransferInfo> TransferInfo { get; set; }
|
||||
[JsonProperty("transfer_info")]
|
||||
public List<TransferInfo> TransferInfo { get; set; } = [];
|
||||
}
|
||||
|
||||
class TransferInfo
|
||||
public class TransferInfo
|
||||
{
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; } = null!;
|
||||
|
||||
[JsonProperty("params")]
|
||||
public TransferInfoParams TransferInfoParams { get; set; }
|
||||
public TransferInfoParams TransferInfoParams { get; set; } = null!;
|
||||
}
|
||||
|
||||
|
||||
class TransferInfoParams
|
||||
public class TransferInfoParams
|
||||
{
|
||||
[JsonProperty("nonce")]
|
||||
public string Nonce { get; set; }
|
||||
public string Nonce { get; set; } = null!;
|
||||
|
||||
[JsonProperty("auth")]
|
||||
public string Auth { get; set; }
|
||||
[JsonProperty("auth")]
|
||||
public string Auth { get; set; } = null!;
|
||||
}
|
||||
@@ -42,8 +42,8 @@ public class LoginV2Executor
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Do login on <see href="https://steamcommunity.com/">SteamCommunity</see>.<br/>
|
||||
/// Some functions requires proper SessionId. But <see cref="SessionData"/> contains only SteamCommunity related SessionId and some functions on other services may not work
|
||||
/// Do log in on <see href="https://steamcommunity.com/">SteamCommunity</see>.<br/>
|
||||
/// Some functions require proper SessionId. But <see cref="SessionData"/> contains only SteamCommunity related SessionId and some functions on other services may not work
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="username"></param>
|
||||
@@ -163,7 +163,7 @@ public class LoginV2Executor
|
||||
var finalizeContent = await finalize.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
var finalizeResp =
|
||||
HealthMonitor.LogOnException(finalizeContent, () => JsonConvert.DeserializeObject<FinalizeLoginJson>(finalizeContent)!);
|
||||
SteamLibErrorMonitor.HandleResponse(finalizeContent, () => JsonConvert.DeserializeObject<FinalizeLoginJson>(finalizeContent)!);
|
||||
|
||||
|
||||
List<SteamAuthToken> tokens = new();
|
||||
|
||||
@@ -1,149 +1,173 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SteamLib.Exceptions.General;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.ExceptionServices;
|
||||
|
||||
namespace SteamLib.Core;
|
||||
|
||||
public static class HealthMonitor
|
||||
/// <summary>
|
||||
/// Provides unified error handling, logging, and mapping for responses from Steam.
|
||||
/// This class is used to log and analyze errors that occur due to server changes
|
||||
/// or code issues, ensuring consistency in logging and exception handling.
|
||||
/// </summary>
|
||||
public static class SteamLibErrorMonitor
|
||||
{
|
||||
public static ILogger? FatalLogger { get; set; }
|
||||
internal static void LogUnexpected(string response, Exception ex)
|
||||
/// <summary>
|
||||
/// Logger for capturing and analyzing error-related issues.
|
||||
/// </summary>
|
||||
public static ILogger? MonitorLogger { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Logs the response and the associated exception using the <see cref="MonitorLogger"/>.
|
||||
/// Differentiates between <see cref="UnsupportedResponseException"/> and other exceptions.
|
||||
/// </summary>
|
||||
/// <param name="response">The response string to log.</param>
|
||||
/// <param name="ex">The exception that occurred.</param>
|
||||
internal static void LogErrorResponse(string response, Exception ex)
|
||||
{
|
||||
FatalLogger?.LogCritical(ex, "Unexpected response detected:\n{response}", response);
|
||||
if (ex is UnsupportedResponseException)
|
||||
{
|
||||
MonitorLogger?.LogError(ex, "Unsupported response detected");
|
||||
}
|
||||
else
|
||||
{
|
||||
MonitorLogger?.LogError(ex, "Unsupported response detected: {response}", response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the error to an <see cref="UnsupportedResponseException"/>, logs it, and then throws the exception.
|
||||
/// This method is used to handle unexpected responses and map them to a known exception type.
|
||||
/// </summary>
|
||||
/// <param name="response">The response that triggered the error.</param>
|
||||
/// <param name="ex">The original exception.</param>
|
||||
/// <exception cref="UnsupportedResponseException">Always throws after logging the response.</exception>
|
||||
[DoesNotReturn]
|
||||
internal static void MapAndThrowException(string response, Exception ex)
|
||||
{
|
||||
var e = new UnsupportedResponseException(response, ex);
|
||||
ExceptionDispatchInfo.SetCurrentStackTrace(e);
|
||||
LogErrorResponse(response, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
internal static T LogOnException<T>(string resp, Func<T> del)
|
||||
[DoesNotReturn]
|
||||
internal static T MapAndThrowException<T>(string response, Exception ex)
|
||||
{
|
||||
var e = new UnsupportedResponseException(response, ex);
|
||||
ExceptionDispatchInfo.SetCurrentStackTrace(e);
|
||||
LogErrorResponse(response, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
|
||||
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
|
||||
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The return type of the delegate.</typeparam>
|
||||
/// <param name="resp">The response string for logging purposes.</param>
|
||||
/// <param name="del">The delegate to execute.</param>
|
||||
/// <returns>The result of the delegate execution.</returns>
|
||||
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
|
||||
internal static T HandleResponse<T>(string resp, Func<T> del)
|
||||
{
|
||||
try
|
||||
{
|
||||
return del();
|
||||
}
|
||||
catch (UnsupportedResponseException ex)
|
||||
{
|
||||
LogErrorResponse(resp, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
MapAndThrowException(resp, ex);
|
||||
return default; //Not reachable
|
||||
}
|
||||
}
|
||||
|
||||
internal static T LogOnException<T>(string resp, Func<string, T> del)
|
||||
/// <summary>
|
||||
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
|
||||
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
|
||||
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The return type of the delegate.</typeparam>
|
||||
/// <param name="resp">The response string for logging purposes.</param>
|
||||
/// <param name="del">The delegate to execute.</param>
|
||||
/// <returns>The result of the delegate execution.</returns>
|
||||
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
|
||||
internal static T HandleResponse<T>(string resp, Func<string, T> del)
|
||||
{
|
||||
try
|
||||
{
|
||||
return del(resp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (UnsupportedResponseException ex)
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
LogErrorResponse(resp, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
internal static T LogOnException<T>(string resp, Func<T> del, params Type[] exceptExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
return del();
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (exceptExceptions.Any(t => t == ex.GetType()) == false)
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
MapAndThrowException(resp, ex);
|
||||
return default; //Not reachable
|
||||
}
|
||||
}
|
||||
|
||||
internal static T LogOnException<T>(string resp, Func<string, T> del, params Type[] exceptExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
return del(resp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (exceptExceptions.Any(t => t == ex.GetType()) == false)
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static T LogOnException<T>(string resp, Func<T> del, Func<Exception, bool> logPredicate)
|
||||
{
|
||||
try
|
||||
{
|
||||
return del();
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (logPredicate(ex))
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static T LogOnException<T>(string resp, Func<string, T> del, Func<Exception, bool> logPredicate)
|
||||
{
|
||||
try
|
||||
{
|
||||
return del(resp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (logPredicate(ex))
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogOnException(string resp, Action del)
|
||||
/// <summary>
|
||||
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
|
||||
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
|
||||
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
|
||||
/// </summary>
|
||||
/// <param name="resp">The response string for logging purposes.</param>
|
||||
/// <param name="del">The delegate to execute.</param>
|
||||
/// <returns>The result of the delegate execution.</returns>
|
||||
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
|
||||
internal static void HandleResponse(string resp, Action del)
|
||||
{
|
||||
try
|
||||
{
|
||||
del();
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (UnsupportedResponseException ex)
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
LogErrorResponse(resp, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
internal static void LogOnException(string resp, Action del, Func<Exception, bool> logPredicate)
|
||||
{
|
||||
try
|
||||
{
|
||||
del();
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (logPredicate(ex))
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
MapAndThrowException(resp, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static async Task<T> LogOnExceptionAsync<T>(string resp, Func<Task<T>> del)
|
||||
/// <summary>
|
||||
/// Ensures the execution of a delegate and handles any exceptions by mapping them to a known type.
|
||||
/// If an <see cref="UnsupportedResponseException"/> is caught, it is logged and rethrown.
|
||||
/// Otherwise, any other exceptions are remapped to <see cref="UnsupportedResponseException"/>.
|
||||
/// </summary>
|
||||
/// <param name="resp">The response string for logging purposes.</param>
|
||||
/// <param name="del">The delegate to execute.</param>
|
||||
/// <returns>The result of the delegate execution.</returns>
|
||||
/// <exception cref="UnsupportedResponseException">Thrown when an unsupported response is encountered.</exception>
|
||||
internal static void HandleResponse(string resp, Action<string> del)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await del();
|
||||
del(resp);
|
||||
}
|
||||
catch (UnsupportedResponseException ex)
|
||||
{
|
||||
LogErrorResponse(resp, ex);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
MapAndThrowException(resp, ex);
|
||||
}
|
||||
}
|
||||
internal static async Task<T> LogOnExceptionAsync<T>(string resp, Func<Task<T>> del, params Type[] exceptExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await del();
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (exceptExceptions.Any(t => t == ex.GetType()) == false)
|
||||
{
|
||||
LogUnexpected(resp, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using SteamLib.Exceptions;
|
||||
using AchiesUtilities.Models;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.Utility;
|
||||
using SteamLib.Utility.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace SteamLib.Core.StatusCodes;
|
||||
|
||||
@@ -11,7 +11,6 @@ public static class SteamConstants
|
||||
public const string STEAM_TV = "https://steam.tv/";
|
||||
public const string STEAM_CHECKOUT = "https://checkout.steampowered.com/";
|
||||
public const string LOGIN_STEAM = "https://login.steampowered.com/";
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -22,15 +21,9 @@ public static class SteamConstants
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Misc
|
||||
|
||||
public const string ENGLISH = "english";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -4,12 +4,11 @@
|
||||
public class CantLoadConfirmationsException : Exception
|
||||
{
|
||||
public LoadConfirmationsError Error { get; init; }
|
||||
public string? ErrorMessage { get; init; }
|
||||
public string? ErrorDetails { get; init; }
|
||||
public CantLoadConfirmationsException() { }
|
||||
public CantLoadConfirmationsException(string message) : base(message) { }
|
||||
public CantLoadConfirmationsException(string message, Exception inner) : base(message, inner) { }
|
||||
protected CantLoadConfirmationsException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace SteamLib.Exceptions.Mobile;
|
||||
namespace SteamLib.Exceptions.Mobile;
|
||||
|
||||
[Serializable]
|
||||
public class BadMobileCookiesException : Exception
|
||||
@@ -12,7 +10,7 @@ public class BadMobileCookiesException : Exception
|
||||
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
|
||||
//
|
||||
|
||||
public BadMobileCookiesException() : base("You are using deafult HttpClient without mobile specific cookies. Login can't be proceeded with these cookies")
|
||||
public BadMobileCookiesException() : base("You are using default HttpClient without mobile specific cookies. Login can't be proceeded with these cookies")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -23,10 +21,4 @@ public class BadMobileCookiesException : Exception
|
||||
public BadMobileCookiesException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected BadMobileCookiesException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
namespace SteamLib.Exceptions;
|
||||
public class SessionExpiredException : SessionInvalidException
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Unlike <see cref="SessionInvalidException"/>, this exception indicates a definite session expiration. Refreshing the JWT token will not help.
|
||||
/// </summary>
|
||||
public class SessionPermanentlyExpiredException : SessionInvalidException
|
||||
{
|
||||
public const string SESSION_EXPIRED_MSG = "Session expired and won't longer work. You must login to get new session";
|
||||
public SessionExpiredException() { }
|
||||
public SessionExpiredException(string message) : base(message) { }
|
||||
public SessionExpiredException(string message, Exception inner) : base(message, inner) { }
|
||||
protected SessionExpiredException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||
public SessionPermanentlyExpiredException() { }
|
||||
public SessionPermanentlyExpiredException(string message) : base(message) { }
|
||||
public SessionPermanentlyExpiredException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
|
||||
public class SessionInvalidException : Exception
|
||||
{
|
||||
public const string SESSION_NULL_MSG = "Session was null. Before acting SteamClient must be logged in";
|
||||
public const string SESSION_NULL_MSG = "Session is null. SteamClient must be logged in before proceeding.";
|
||||
public const string GOT_401_MSG = "Steam indicates the session is invalid. Received a 401 Unauthorized response.";
|
||||
public SessionInvalidException() { }
|
||||
public SessionInvalidException(string message) : base(message) { }
|
||||
public SessionInvalidException(string message, Exception? inner) : base(message, inner) { }
|
||||
protected SessionInvalidException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||
public SessionInvalidException(string message) : base(message) { }
|
||||
public SessionInvalidException(string message, Exception? inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public class MobileData
|
||||
[JsonRequired] public string SharedSecret { get; set; } = null!;
|
||||
[JsonRequired] public string IdentitySecret { get; set; } = null!;
|
||||
[JsonRequired] public string DeviceId { get; set; } = null!;
|
||||
//TODO: This property used only for tracing purposes in Steam, so if it's not provided, we can generate it manually
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
namespace SteamLib.ProtoCore.Enums;
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable IdentifierTypo
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace SteamLib.ProtoCore.Enums;
|
||||
|
||||
[PublicAPI]
|
||||
public enum EResult
|
||||
{
|
||||
Invalid = 0,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user