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