mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-26 06:41:45 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a093840b35 | |||
| c705caac8f | |||
| ef8e12e20d | |||
| 317d8b9754 | |||
| a6c5e01a9d | |||
| e77a04e447 | |||
| cddc6ea2d3 | |||
| f00015b00f | |||
| 9016f96b6a | |||
| 06748d6f83 | |||
| acf187fc6b | |||
| 1e65cd4a06 | |||
| c185533bb5 | |||
| 56ae190695 | |||
| 44382b369b | |||
| fb18d4c5f3 | |||
| 2f3055cb44 | |||
| e3f1202eba | |||
| a98e10f001 | |||
| 3385036dbe | |||
| f618accd80 | |||
| 787326f355 | |||
| 99be9d64c6 | |||
| a286403ef4 | |||
| a0d1cf8ba3 | |||
| 7eab9fddd1 | |||
| 769f102d16 | |||
| 80d7c09b0d | |||
| e6ae762d5a | |||
| 89ca08f6cf | |||
| e401bcccfd | |||
| 195ac95b36 | |||
| d1f660381e | |||
| c8aa7ba8e7 | |||
| 50a7d21d52 | |||
| aa092bdd67 | |||
| 6a1b03163c | |||
| 9a277d07db | |||
| fcd4056619 | |||
| 4cc69e9a57 | |||
| 672ca22662 | |||
| a9fdb58cac | |||
| 939fca31e9 | |||
| c5191d6a40 | |||
| 79f62f266c | |||
| b7a971b37e | |||
| 6c11d80508 | |||
| 4724a4c094 | |||
| 3297546ac2 | |||
| 760cfbf4f1 | |||
| 11a13174b8 | |||
| 7a8bdc5073 |
@@ -0,0 +1,108 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: "8.x"
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore NebulaAuth.sln
|
||||
|
||||
- name: Build
|
||||
run: dotnet build NebulaAuth.sln --configuration Release
|
||||
|
||||
- name: Get version from assembly
|
||||
id: get-version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$content = Get-Content -Path "NebulaAuth/NebulaAuth.csproj" -Raw
|
||||
$version = [regex]::Match($content, '<AssemblyVersion>(.*?)<\/AssemblyVersion>').Groups[1].Value
|
||||
Write-Output "VERSION=$version" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Check if tag exists
|
||||
id: tag_exists
|
||||
run: |
|
||||
if (git tag -l | Select-String -Pattern "^${env:VERSION}$") {
|
||||
Write-Output "Version $env:VERSION already exists."
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Check changelog
|
||||
run: |
|
||||
if (-not (Test-Path "changelog/${env:VERSION}.html")) {
|
||||
Write-Output "Changelog file changelog/${env:VERSION}.html does not exist."
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Insert date into changelog
|
||||
run: |
|
||||
$date = Get-Date -Format "dd.MM.yyyy"
|
||||
(Get-Content "changelog/${env:VERSION}.html") -replace '(?<=<div class="date">).*?(?=</div>)', $date | Set-Content "changelog/${env:VERSION}.html"
|
||||
|
||||
- name: Extract changelog description
|
||||
id: extract_description
|
||||
run: |
|
||||
$description = (Get-Content "changelog/${env:VERSION}.html" | Select-String -Pattern '(?<=<div class="description">).*?(?=</div>)' | ForEach-Object { $_.Matches.Value }) -replace '<br\/>', "`n"
|
||||
Write-Output "DESCRIPTION=$description" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Create ZIP
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path release -Force
|
||||
Compress-Archive -Path NebulaAuth/bin/Release -DestinationPath release/NebulaAuth.${env:VERSION}.zip
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ env.VERSION }}
|
||||
release_name: NebulaAuth ${{ env.VERSION }}
|
||||
body: |
|
||||
${{ env.DESCRIPTION }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: release/NebulaAuth.${{ env.VERSION }}.zip
|
||||
asset_name: NebulaAuth.${{ env.VERSION }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Update XML and Changelog html
|
||||
shell: pwsh
|
||||
run: |
|
||||
$xmlContent = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>${env:VERSION}.0</version>
|
||||
<url>https://github.com/${env:GITHUB_REPOSITORY}/releases/download/${env:VERSION}/NebulaAuth.${env:VERSION}.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/${env:VERSION}.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
</item>
|
||||
"@
|
||||
$xmlContent | Out-File -FilePath update.xml -Encoding UTF8 -Force
|
||||
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
git add changelog/${env:VERSION}.html update.xml
|
||||
git commit -m "Update version to ${env:VERSION} and add changelog"
|
||||
git push origin master
|
||||
@@ -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,183 @@
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Utility.MaFiles;
|
||||
|
||||
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
if (currentPath != null)
|
||||
Environment.CurrentDirectory = currentPath;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.LegacyConverter;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Utility.MafileSerialization;
|
||||
|
||||
|
||||
Console.WriteLine(currentPath);
|
||||
foreach (var path in args)
|
||||
|
||||
try
|
||||
{
|
||||
var mafileSerializer = new MafileSerializer(new MafileSerializerSettings
|
||||
{
|
||||
DeserializationOptions =
|
||||
{
|
||||
AllowDeviceIdGeneration = true,
|
||||
AllowSessionIdGeneration = true,
|
||||
}
|
||||
});
|
||||
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
if (currentPath != null)
|
||||
Environment.CurrentDirectory = currentPath;
|
||||
const string toStoreFolder = "ConvertedMafiles";
|
||||
|
||||
if (Path.Exists(path) == false)
|
||||
if (Directory.Exists(toStoreFolder) == false)
|
||||
{
|
||||
Console.WriteLine($"NOT VALID PATH: '{path}'");
|
||||
continue;
|
||||
Directory.CreateDirectory(toStoreFolder);
|
||||
}
|
||||
Console.WriteLine("Reading: " + path);
|
||||
try
|
||||
else
|
||||
{
|
||||
var text = File.ReadAllText(path);
|
||||
var maf = MafileSerializer.Deserialize(text, out _);
|
||||
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
Write(legacy, name);
|
||||
var isEmpty = Directory.GetFiles(toStoreFolder).Length == 0;
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
if (!isEmpty)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"WARNING! 'ConverterdMafiles' folder is not empty. Please backup data from it and then continue.");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine("Press Y to continue");
|
||||
while (Console.ReadKey(true).Key != ConsoleKey.Y)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("DONE: " + name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
var decryptMode = false;
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {ex.Message}");
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.WriteLine("-----------------------------------------");
|
||||
Console.WriteLine("Press 'D' to select decrypt mode. Press 'C' to convert mode. Press ESC to exit");
|
||||
var key = Console.ReadKey(true);
|
||||
switch (key.Key)
|
||||
{
|
||||
case ConsoleKey.D:
|
||||
decryptMode = true;
|
||||
break;
|
||||
case ConsoleKey.C:
|
||||
break;
|
||||
case ConsoleKey.Escape:
|
||||
return;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Manifest? manifest = null;
|
||||
string? password = null;
|
||||
if (decryptMode)
|
||||
{
|
||||
var files = Directory.GetFiles(currentPath, "manifest.json");
|
||||
var manifestPath = files.FirstOrDefault();
|
||||
if (manifestPath == null)
|
||||
{
|
||||
Console.WriteLine("No manifest.json found in current directory");
|
||||
return;
|
||||
}
|
||||
|
||||
var manifestText = File.ReadAllText(manifestPath);
|
||||
try
|
||||
{
|
||||
manifest = JsonConvert.DeserializeObject<Manifest>(manifestText)!;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Can't read manifest: " + ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (manifest.Encrypted == false)
|
||||
{
|
||||
Console.WriteLine("Manifest is not encrypted");
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.WriteLine("Please enter your encryption password: ");
|
||||
password = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine(currentPath);
|
||||
|
||||
|
||||
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 data = mafileSerializer.Deserialize(text);
|
||||
var maf = data.Data;
|
||||
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
|
||||
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
|
||||
Write(legacy, fileNameWithoutExtension);
|
||||
|
||||
Console.WriteLine("DONE: " + fileNameWithoutExtension);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ERROR: {ex.Message}");
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.WriteLine("-----------------------------------------");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//Local Functions
|
||||
|
||||
void Write(string maf, string name)
|
||||
{
|
||||
|
||||
var path = Path.Combine(toStoreFolder, name + "_legacy.mafile");
|
||||
File.WriteAllText(path, maf);
|
||||
}
|
||||
|
||||
string? DecryptMafile(string fileName, string cipherText)
|
||||
{
|
||||
if (password == null) return null;
|
||||
var entry = manifest?.Entries.FirstOrDefault(x => x.Filename.EqualsIgnoreCase(fileName));
|
||||
if (entry == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var iv = entry.EncryptionIv;
|
||||
var salt = entry.EncryptionSalt;
|
||||
return SDAEncryptor.DecryptData(password, salt, iv, cipherText);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
|
||||
|
||||
void Write(string maf, string name)
|
||||
finally
|
||||
{
|
||||
|
||||
var path = Path.Combine(name + "_legacy.mafile");
|
||||
File.WriteAllText(path, maf);
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||||
namespace NebulaAuth.LegacyConverter;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
#pragma warning disable all
|
||||
#pragma warning disable SYSLIB0023
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
#pragma warning disable SYSLIB0022
|
||||
#pragma warning disable SYSLIB0041
|
||||
//Resharper disable all
|
||||
//Pragma is disabled because it's legacy code from SDA
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This class provides the controls that will encrypt and decrypt the *.maFile files
|
||||
///
|
||||
/// Passwords entered will be passed into 100k rounds of PBKDF2 (RFC2898) with a cryptographically random salt.
|
||||
/// The generated key will then be passed into AES-256 (RijndalManaged) which will encrypt the data
|
||||
/// in cypher block chaining (CBC) mode, and then write both the PBKDF2 salt and encrypted data onto the disk.
|
||||
/// </summary>
|
||||
public static class SDAEncryptor
|
||||
{
|
||||
private const int PBKDF2_ITERATIONS = 50000; //Set to 50k to make program not unbearably slow. May increase in future.
|
||||
private const int SALT_LENGTH = 8;
|
||||
private const int KEY_SIZE_BYTES = 32;
|
||||
private const int IV_LENGTH = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an 8-byte cryptographically random salt in base64 encoding
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetRandomSalt()
|
||||
{
|
||||
byte[] salt = new byte[SALT_LENGTH];
|
||||
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
|
||||
{
|
||||
rng.GetBytes(salt);
|
||||
}
|
||||
return Convert.ToBase64String(salt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a 16-byte cryptographically random initialization vector (IV) in base64 encoding
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetInitializationVector()
|
||||
{
|
||||
byte[] IV = new byte[IV_LENGTH];
|
||||
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
|
||||
{
|
||||
rng.GetBytes(IV);
|
||||
}
|
||||
return Convert.ToBase64String(IV);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generates an encryption key derived using a password, a random salt, and specified number of rounds of PBKDF2
|
||||
///
|
||||
/// TODO: pass in password via SecureString?
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="salt"></param>
|
||||
/// <returns></returns>
|
||||
private static byte[] GetEncryptionKey(string password, string salt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
throw new ArgumentException("Password is empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(salt))
|
||||
{
|
||||
throw new ArgumentException("Salt is empty");
|
||||
}
|
||||
using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(salt), PBKDF2_ITERATIONS))
|
||||
{
|
||||
return pbkdf2.GetBytes(KEY_SIZE_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to decrypt and return data given an encrypted base64 encoded string. Must use the same
|
||||
/// password, salt, IV, and ciphertext that was used during the original encryption of the data.
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="passwordSalt"></param>
|
||||
/// <param name="IV">Initialization Vector</param>
|
||||
/// <param name="encryptedData"></param>
|
||||
/// <returns></returns>
|
||||
public static string DecryptData(string password, string passwordSalt, string IV, string encryptedData)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
throw new ArgumentException("Password is empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(passwordSalt))
|
||||
{
|
||||
throw new ArgumentException("Salt is empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(IV))
|
||||
{
|
||||
throw new ArgumentException("Initialization Vector is empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(encryptedData))
|
||||
{
|
||||
throw new ArgumentException("Encrypted data is empty");
|
||||
}
|
||||
|
||||
byte[] cipherText = Convert.FromBase64String(encryptedData);
|
||||
byte[] key = GetEncryptionKey(password, passwordSalt);
|
||||
string plaintext = null;
|
||||
|
||||
using (RijndaelManaged aes256 = new RijndaelManaged())
|
||||
{
|
||||
aes256.IV = Convert.FromBase64String(IV);
|
||||
aes256.Key = key;
|
||||
aes256.Padding = PaddingMode.PKCS7;
|
||||
aes256.Mode = CipherMode.CBC;
|
||||
|
||||
//create decryptor to perform the stream transform
|
||||
ICryptoTransform decryptor = aes256.CreateDecryptor(aes256.Key, aes256.IV);
|
||||
|
||||
//wrap in a try since a bad password yields a bad key, which would throw an exception on decrypt
|
||||
try
|
||||
{
|
||||
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
|
||||
{
|
||||
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
|
||||
{
|
||||
plaintext = srDecrypt.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
plaintext = null;
|
||||
}
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a string given a password, salt, and initialization vector, then returns result in base64 encoded string.
|
||||
///
|
||||
/// To retrieve this data, you must decrypt with the same password, salt, IV, and cyphertext that was used during encryption
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="passwordSalt"></param>
|
||||
/// <param name="IV"></param>
|
||||
/// <param name="plaintext"></param>
|
||||
/// <returns></returns>
|
||||
public static string EncryptData(string password, string passwordSalt, string IV, string plaintext)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
throw new ArgumentException("Password is empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(passwordSalt))
|
||||
{
|
||||
throw new ArgumentException("Salt is empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(IV))
|
||||
{
|
||||
throw new ArgumentException("Initialization Vector is empty");
|
||||
}
|
||||
if (string.IsNullOrEmpty(plaintext))
|
||||
{
|
||||
throw new ArgumentException("Plaintext data is empty");
|
||||
}
|
||||
byte[] key = GetEncryptionKey(password, passwordSalt);
|
||||
byte[] ciphertext;
|
||||
|
||||
using (RijndaelManaged aes256 = new RijndaelManaged())
|
||||
{
|
||||
aes256.Key = key;
|
||||
aes256.IV = Convert.FromBase64String(IV);
|
||||
aes256.Padding = PaddingMode.PKCS7;
|
||||
aes256.Mode = CipherMode.CBC;
|
||||
|
||||
ICryptoTransform encryptor = aes256.CreateEncryptor(aes256.Key, aes256.IV);
|
||||
|
||||
using (MemoryStream msEncrypt = new MemoryStream())
|
||||
{
|
||||
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
|
||||
{
|
||||
using (StreamWriter swEncypt = new StreamWriter(csEncrypt))
|
||||
{
|
||||
swEncypt.Write(plaintext);
|
||||
}
|
||||
ciphertext = msEncrypt.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Convert.ToBase64String(ciphertext);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAuth.LegacyConverter"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamLibForked", "SteamLibForked\SteamLibForked.csproj", "{09F02072-F91D-4DAA-87BC-A34D3E150570}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{161BFADE-FCF3-45D1-82EA-8A1B187529F7}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
changelog\1.3.4.html = changelog\1.3.4.html
|
||||
changelog\1.4.4.html = changelog\1.4.4.html
|
||||
changelog\1.4.5.html = changelog\1.4.5.html
|
||||
changelog\1.4.6.html = changelog\1.4.6.html
|
||||
changelog\1.4.7.html = changelog\1.4.7.html
|
||||
changelog\1.4.8.html = changelog\1.4.8.html
|
||||
changelog\1.4.9.html = changelog\1.4.9.html
|
||||
changelog\1.5.0.html = changelog\1.5.0.html
|
||||
changelog\1.5.1.html = changelog\1.5.1.html
|
||||
changelog\1.5.2.html = changelog\1.5.2.html
|
||||
changelog\1.5.3.html = changelog\1.5.3.html
|
||||
changelog\1.5.4.html = changelog\1.5.4.html
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=MAAC/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
+3
-5
@@ -1,9 +1,7 @@
|
||||
<Application x:Class="NebulaAuth.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:NebulaAuth"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
xmlns:converters="clr-namespace:NebulaAuth.Converters"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:background="clr-namespace:NebulaAuth.Converters.Background"
|
||||
@@ -15,11 +13,12 @@
|
||||
<FontFamily x:Key="RobotoSymbols">pack://application:,,,/Fonts/Roboto/#Roboto Symbols</FontFamily>
|
||||
<converters:CoefficientConverter x:Key="CoefficientConverter"/>
|
||||
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter"/>
|
||||
<converters:OnOffBoolTextConverter x:Key="OnOffBoolTextConverter"/>
|
||||
<converters:SelectedProxyTextConverter x:Key="SelectedProxyTextConverter"/>
|
||||
<converters:ProxyTextConverter x:Key="ProxyTextConverter"/>
|
||||
<converters:ProxyDataTextConverter x:Key="ProxyDataTextConverter"/>
|
||||
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter"/>
|
||||
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
|
||||
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter"/>
|
||||
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter"/>
|
||||
<!-- Background converters-->
|
||||
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter"/>
|
||||
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter"/>
|
||||
@@ -32,7 +31,6 @@
|
||||
|
||||
|
||||
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="LightGreen" SecondaryColor="Purple" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignExtensions;component/Themes/Generic.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.snackbar.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.Dark.xaml" />
|
||||
|
||||
@@ -3,12 +3,11 @@ using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using CodingSeb.Localization;
|
||||
|
||||
namespace NebulaAuth;
|
||||
|
||||
|
||||
public partial class App : Application
|
||||
public partial class App
|
||||
{
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
@@ -22,14 +21,15 @@ public partial class App : Application
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
var msg = ex.ToString();
|
||||
if (ex is CantAlignTimeException)
|
||||
{
|
||||
msg = Loc.Tr(LocManager.GetCodeBehind("CantAlignTimeError"));
|
||||
msg = LocManager.Get("CantAlignTimeError");
|
||||
}
|
||||
|
||||
MessageBox.Show(msg);
|
||||
MessageBox.Show(msg, "Error", MessageBoxButton.OK, MessageBoxImage.Stop, MessageBoxResult.OK, MessageBoxOptions.DefaultDesktopOnly);
|
||||
throw;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
public class AnyMafilesToVisibilityConverter : IValueConverter
|
||||
{
|
||||
private static bool _everAnyMafiles;
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (_everAnyMafiles)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
if (value is 0)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
_everAnyMafiles = true;
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,13 @@ namespace NebulaAuth.Converters.Background;
|
||||
|
||||
public class BackgroundImageVisibleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is not BackgroundMode.Color ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace NebulaAuth.Converters.Background;
|
||||
|
||||
public class BackgroundSourceConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is BackgroundMode.Custom)
|
||||
{
|
||||
@@ -21,8 +21,8 @@ public class BackgroundSourceConverter : IValueConverter
|
||||
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg"));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,16 @@ namespace NebulaAuth.Converters;
|
||||
[ValueConversion(typeof(double), typeof(double))]
|
||||
public class CoefficientConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return System.Convert.ToDouble(value) * System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return (double)value / (double)parameter;
|
||||
var first = value as double? ?? 0;
|
||||
var second = parameter as double? ?? 0;
|
||||
if (second == 0) second = 1;
|
||||
return first / second;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,6 @@ public class MultiCommandParameterConverter : IMultiValueConverter
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
public class OnOffBoolTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not bool b)
|
||||
{
|
||||
throw new InvalidCastException($"Can't cast value {value} to 'bool'");
|
||||
}
|
||||
|
||||
return b ? "вкл" : "выкл";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
public class PortableMaClientStatusToColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is false)
|
||||
{
|
||||
return new SolidColorBrush(Color.FromRgb(187, 224, 139));
|
||||
}
|
||||
return new SolidColorBrush(Color.FromRgb(224, 139, 139));
|
||||
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,43 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using NebulaAuth.Model.Entities;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
public class ProxyTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not MaProxy p)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return $"{p.Id}: {p.Data.Address}";
|
||||
return $"{p.Id}: {p.Data.Address}:{p.Data.Port}";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class ProxyDataTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not ProxyData p)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return $"{p.Address}:{p.Port}";
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,23 @@ namespace NebulaAuth.Converters;
|
||||
|
||||
public class ReverseBooleanConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return !(bool)value;
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return !boolValue;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Value must be of type bool", nameof(value));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return !(bool)value;
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return !boolValue;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Value must be of type bool", nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using NebulaAuth.Model.Entities;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
public class SelectedProxyTextConverter : IMultiValueConverter
|
||||
{
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values[0] is not MaProxy proxy)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var proxyExist = (bool)values[1];
|
||||
return proxyExist ? $"{proxy.Id}: {proxy.Data.Address}" : "";
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@ public class ValueConverterGroup : List<IValueConverter>, IValueConverter
|
||||
{
|
||||
#region IValueConverter Members
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.View;
|
||||
@@ -36,7 +37,23 @@ public static class DialogsController
|
||||
{
|
||||
UserName = username
|
||||
};
|
||||
var content = new LoginAgainDialog()
|
||||
var content = new LoginAgainDialog
|
||||
{
|
||||
DataContext = vm
|
||||
};
|
||||
var result = await DialogHost.Show(content);
|
||||
if (result is true)
|
||||
{
|
||||
return vm;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<LoginAgainOnImportVM?> ShowLoginAgainOnImportDialog(Mafile mafile, IEnumerable<MaProxy> proxies)
|
||||
{
|
||||
var vm = new LoginAgainOnImportVM(mafile, proxies);
|
||||
var content = new LoginAgainOnImportDialog()
|
||||
{
|
||||
DataContext = vm
|
||||
};
|
||||
|
||||
@@ -18,6 +18,10 @@ public static class LocManager
|
||||
//Thread.CurrentThread.CurrentCulture= CultureInfo.GetCultureInfo(GetLanguageCode(language));
|
||||
}
|
||||
|
||||
public static string GetCurrentLanguageCode()
|
||||
{
|
||||
return Loc.Instance.CurrentLanguage;
|
||||
}
|
||||
|
||||
public static string GetLanguageCode(LocalizationLanguage language)
|
||||
{
|
||||
|
||||
@@ -7,11 +7,8 @@ public class SnackbarController
|
||||
{
|
||||
|
||||
public static SnackbarMessageQueue MessageQueue { get; } = new() { DiscardDuplicates = true};
|
||||
private const int MIN_SNACKBAR_TIME = 1000;
|
||||
public SnackbarController()
|
||||
{
|
||||
private const int MIN_SNACKBAR_TIME = 1200;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -27,7 +24,9 @@ public class SnackbarController
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="duration">Default duration is 1 second</param>
|
||||
/// <param name="actionText">Default: 'OK'</param>
|
||||
public static void SendSnackbarWithButton(string text, string actionText = "OK", Action? action = null, TimeSpan? duration = null)
|
||||
{
|
||||
duration ??= GetSnackbarTime(text);
|
||||
@@ -46,10 +45,12 @@ public class SnackbarController
|
||||
|
||||
private static TimeSpan GetSnackbarTime(string str)
|
||||
{
|
||||
if (str.Length <= 100) return TimeSpan.FromMilliseconds(MIN_SNACKBAR_TIME);
|
||||
|
||||
var length = str.Length / 0.07;
|
||||
return TimeSpan.FromMilliseconds(length);
|
||||
var duration = str.Length / 0.03;
|
||||
if (duration < MIN_SNACKBAR_TIME)
|
||||
{
|
||||
duration = MIN_SNACKBAR_TIME;
|
||||
}
|
||||
return TimeSpan.FromMilliseconds(duration);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
using System;
|
||||
using NebulaAuth.Model;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using NebulaAuth.Model;
|
||||
using Application = System.Windows.Application;
|
||||
|
||||
namespace NebulaAuth.Core;
|
||||
|
||||
public static class TrayManager
|
||||
{
|
||||
private static NotifyIcon _notifyIcon;
|
||||
private static NotifyIcon? _notifyIcon;
|
||||
private static readonly Window MainWindow = Application.Current.MainWindow!;
|
||||
public static bool IsEnabled => Settings.Instance.HideToTray;
|
||||
private static bool IsEnabled => Settings.Instance.HideToTray;
|
||||
|
||||
[MemberNotNullWhen(true, nameof(_notifyIcon))]
|
||||
private static bool Init { get; set; }
|
||||
|
||||
public static void InitializeTray()
|
||||
{
|
||||
_notifyIcon = new NotifyIcon();
|
||||
_notifyIcon.Text = "NebulaAuth";
|
||||
|
||||
Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/nebula lock.ico"))!.Stream;
|
||||
var iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
|
||||
_notifyIcon.Icon = new Icon(iconStream);
|
||||
|
||||
|
||||
_notifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick;
|
||||
|
||||
var contextMenu = new ContextMenuStrip();
|
||||
@@ -32,6 +34,7 @@ public static class TrayManager
|
||||
_notifyIcon.ContextMenuStrip = contextMenu;
|
||||
|
||||
MainWindow.StateChanged += MainWindow_StateChanged;
|
||||
Init = true;
|
||||
}
|
||||
|
||||
private static void OnExitClick(object? sender, EventArgs e)
|
||||
@@ -57,6 +60,7 @@ public static class TrayManager
|
||||
|
||||
private static void ShowMainWindow()
|
||||
{
|
||||
if (!Init) return;
|
||||
MainWindow.Show();
|
||||
MainWindow.WindowState = WindowState.Normal;
|
||||
_notifyIcon.Visible = false;
|
||||
@@ -64,13 +68,15 @@ public static class TrayManager
|
||||
|
||||
private static void HideMainWindow()
|
||||
{
|
||||
if(IsEnabled == false) return;
|
||||
if (!Init) return;
|
||||
if (IsEnabled == false) return;
|
||||
_notifyIcon.Visible = true;
|
||||
MainWindow.Hide();
|
||||
}
|
||||
|
||||
private static void ExitApplication()
|
||||
{
|
||||
if (!Init) return;
|
||||
_notifyIcon.Dispose();
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
@@ -17,8 +17,60 @@ public static class UpdateManager
|
||||
if (Settings.Instance.AllowAutoUpdate)
|
||||
AutoUpdater.UpdateMode = Mode.ForcedDownload;
|
||||
AutoUpdater.Start(UPDATE_URL);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//static UpdateManager()
|
||||
//{
|
||||
// //AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
|
||||
|
||||
//}
|
||||
|
||||
//private static async void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
|
||||
//{
|
||||
// if (args.Error == null)
|
||||
// {
|
||||
// if (args.IsUpdateAvailable)
|
||||
// {
|
||||
// DialogResult dialogResult;
|
||||
// var dialog = new UpdaterView()
|
||||
// {
|
||||
// DataContext = new UpdaterVM(args)
|
||||
// };
|
||||
|
||||
// await DialogHost.Show(dialog);
|
||||
// Application.Current.Shutdown();
|
||||
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (args.Error is WebException)
|
||||
// {
|
||||
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
//private static void RunUpdate(UpdateInfoEventArgs args)
|
||||
//{
|
||||
// Application.Current.Dispatcher.Invoke(() =>
|
||||
// {
|
||||
// if (AutoUpdater.DownloadUpdate(args))
|
||||
// {
|
||||
// Application.Current.Shutdown();
|
||||
// }
|
||||
// }, DispatcherPriority.ContextIdle);
|
||||
//}
|
||||
}
|
||||
+113
-32
@@ -25,7 +25,7 @@
|
||||
</b:Interaction.Behaviors>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Command="{Binding Path=CopyMafileFromBufferCommand}"
|
||||
<KeyBinding Command="{Binding Path=PasteMafilesFromClipboardCommand}"
|
||||
Key="V"
|
||||
Modifiers="Control"/>
|
||||
</Window.InputBindings>
|
||||
@@ -41,37 +41,41 @@
|
||||
<Rectangle Grid.Row="0" Grid.RowSpan="2" Opacity="0.5" Fill="#FF1F1D1D" Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}" />
|
||||
<Rectangle Name="DragNDropOverlay" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="1" Fill="#242123" Opacity="0.9" Visibility="Hidden" />
|
||||
<StackPanel Name="DragNDropPanel" Grid.Row="0" ZIndex="2" w:FontScaleWindow.Scale="1" w:FontScaleWindow.ResizeFont="True" Grid.RowSpan="3" Visibility="Hidden" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<TextBlock FontWeight="Bold" Foreground="#9a65b8">Отпустите, чтобы добавить mafile</TextBlock>
|
||||
<TextBlock FontWeight="Bold" Foreground="#9a65b8" Text="{Tr MainWindow.Global.DragNDropHint}"/>
|
||||
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36" Kind="FileReplaceOutline" />
|
||||
</StackPanel>
|
||||
<ToolBarTray IsLocked="True" Grid.Row="0">
|
||||
<ToolBarTray IsLocked="True" Grid.Row="0" >
|
||||
<ToolBarTray.Background>
|
||||
<SolidColorBrush Opacity="0.6" Color="#FF1A1C25" />
|
||||
</ToolBarTray.Background>
|
||||
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.75" w:FontScaleWindow.ResizeFont="True">
|
||||
<Menu>
|
||||
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
||||
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Import}" Command="{Binding AddMafileCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
|
||||
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}" Command="{Binding OpenMafileFolderCommand}" />
|
||||
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}" Command="{Binding OpenSettingsDialogCommand}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Menu>
|
||||
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}" Command="{Binding LinkAccountCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}" Command="{Binding RemoveAuthenticatorCommand}"/>
|
||||
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
|
||||
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
|
||||
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Separator />
|
||||
<ComboBox MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}" md:TextFieldAssist.HasClearButton="True" IsEditable="True" ItemsSource="{Binding Groups}" SelectedValue="{Binding SelectedGroup}">
|
||||
<ComboBox ToolTip="{Tr MainWindow.AppBar.GroupToolTip}" md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}"
|
||||
MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
|
||||
ItemsSource="{Binding Groups}"
|
||||
SelectedValue="{Binding SelectedGroup}">
|
||||
|
||||
<FrameworkElement.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style TargetType="{x:Type md:PackIcon}">
|
||||
<Style TargetType="md:PackIcon">
|
||||
<Setter Property="Width" Value="15" />
|
||||
<Setter Property="Height" Value="15" />
|
||||
</Style>
|
||||
@@ -81,7 +85,15 @@
|
||||
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}" CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
|
||||
</UIElement.InputBindings>
|
||||
</ComboBox>
|
||||
<ComboBox MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}" md:ComboBoxAssist.ShowSelectedItem="False" SelectedItem="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
|
||||
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}"
|
||||
|
||||
MinWidth="80"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
|
||||
md:ComboBoxAssist.ShowSelectedItem="False"
|
||||
SelectedItem="{Binding SelectedProxy}"
|
||||
ItemsSource="{Binding Proxies}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type entities:MaProxy}">
|
||||
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
|
||||
@@ -96,7 +108,13 @@
|
||||
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
|
||||
</UIElement.InputBindings>
|
||||
</ComboBox>
|
||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFF0000" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.MafileProxyInUse}" ToolTipService.InitialShowDelay="600">
|
||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFF0000" Margin="3" ToolTipService.InitialShowDelay="300">
|
||||
<md:PackIcon.ToolTip>
|
||||
<TextBlock>
|
||||
<Run Text="{Tr MainWindow.AppBar.Proxy.ProxyAlert.MafileProxyInUse, IsDynamic=False}"/>
|
||||
<Run Text=" "/><Run Text="{Binding SelectedMafile.Proxy.Data, FallbackValue='', Mode=OneWay}"/>
|
||||
</TextBlock>
|
||||
</md:PackIcon.ToolTip>
|
||||
<UIElement.Visibility>
|
||||
<Binding Path="ProxyExist">
|
||||
<Binding.Converter>
|
||||
@@ -108,10 +126,29 @@
|
||||
</Binding>
|
||||
</UIElement.Visibility>
|
||||
</md:PackIcon>
|
||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="600" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipAccentCheckBox}" IsChecked="{Binding TradeTimerEnabled}" Content="{Tr MainWindow.AppBar.TradeTimerHint}"/>
|
||||
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipAccentCheckBox}" IsChecked="{Binding MarketTimerEnabled}" Content="{Tr MainWindow.AppBar.MarketTimerHint}"/>
|
||||
<TextBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
|
||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding MarketTimerEnabled}" >
|
||||
<md:PackIcon Kind="ShoppingCart"/>
|
||||
<FrameworkElement.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}" Command="{Binding Path=SwitchMAACOnGroupCommand}" CommandParameter="{StaticResource True}"/>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}" Command="{Binding Path=SwitchMAACOnAllCommand}" CommandParameter="{StaticResource True}"/>
|
||||
</ContextMenu>
|
||||
</FrameworkElement.ContextMenu>
|
||||
</ToggleButton>
|
||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding TradeTimerEnabled}" >
|
||||
<md:PackIcon Kind="AccountArrowRight" />
|
||||
<FrameworkElement.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}" Command="{Binding Path=SwitchMAACOnGroupCommand}" CommandParameter="{StaticResource False}"/>
|
||||
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}" Command="{Binding Path=SwitchMAACOnAllCommand}" CommandParameter="{StaticResource False}"/>
|
||||
</ContextMenu>
|
||||
</FrameworkElement.ContextMenu>
|
||||
</ToggleButton>
|
||||
<TextBox md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
|
||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}" HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}" Foreground="WhiteSmoke" VerticalAlignment="Center" Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
|
||||
<md:PackIcon Kind="Accounts" />
|
||||
</ToggleButton>
|
||||
</ToolBar>
|
||||
</ToolBarTray>
|
||||
<Grid Row="1">
|
||||
@@ -119,19 +156,58 @@
|
||||
<ColumnDefinition Width="0.4*" />
|
||||
<ColumnDefinition Width="0.6*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid>
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="10,15,10,15" DisplayMemberPath="AccountName" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
|
||||
|
||||
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Grid.Row="0" Margin="10,15,0,0" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate >
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Stretch" Text="{Binding AccountName}">
|
||||
<TextBlock.Foreground>
|
||||
<Binding Mode="OneWay" Path="LinkedClient.IsError" Converter="{StaticResource PortableMaClientStatusToColorConverter}">
|
||||
<Binding.FallbackValue>
|
||||
<SolidColorBrush Color="WhiteSmoke"/>
|
||||
</Binding.FallbackValue>
|
||||
</Binding>
|
||||
</TextBlock.Foreground>
|
||||
</TextBlock>
|
||||
<StackPanel Visibility="{Binding LinkedClient, Converter={StaticResource NullableToVisibilityConverter}}" Grid.Column="1" Orientation="Horizontal">
|
||||
<md:PackIcon Kind="ShoppingCart" Visibility="{Binding LinkedClient.AutoConfirmMarket, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"/>
|
||||
<md:PackIcon Kind="AccountArrowRight" Visibility="{Binding LinkedClient.AutoConfirmTrades, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
<ListBox.InputBindings>
|
||||
<KeyBinding Key="C" Modifiers="Control" Command="{Binding DataContext.CopyLoginCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
|
||||
<KeyBinding Key="X" Modifiers="Control" Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
|
||||
</ListBox.InputBindings>
|
||||
<FrameworkElement.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem InputGestureText="Ctrl+C" Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}" Command="{Binding CopyLoginCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}" Command="{Binding CopySteamIdCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem InputGestureText="Ctrl+X" Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}" Command="{Binding CopyMafileCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}" ItemsSource="{Binding Groups}">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource MaterialDesignMenuItem}" TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="MenuItem.Command" Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<Setter Property="MenuItem.CommandParameter">
|
||||
<Setter Property="Command" Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<Setter Property="CommandParameter">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{StaticResource MultiCommandParameterConverter}">
|
||||
<Binding />
|
||||
@@ -145,8 +221,15 @@
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}" Command="{Binding Path=RemoveGroupCommand}" CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
||||
</ContextMenu>
|
||||
</FrameworkElement.ContextMenu>
|
||||
|
||||
</ListBox>
|
||||
<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}" />
|
||||
<Border Grid.Row="0" Margin="10" Padding="5" Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}, Mode=OneWay}">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="DarkGray" Opacity="0.5"/>
|
||||
</Border.Background>
|
||||
<TextBlock TextWrapping="WrapWithOverflow" FontSize="16" Text="{Tr MainWindow.Global.StartTip}"/>
|
||||
</Border>
|
||||
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}" md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10,5,10,10" md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid>
|
||||
<md:Card Grid.Column="1" Margin="10,10,15,10" UniformCornerRadius="15">
|
||||
<Control.Background>
|
||||
@@ -164,7 +247,11 @@
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True" IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF" Style="{StaticResource MaterialDesignFilledTextBox}" Text="{Binding Code, FallbackValue=Code}" PreviewMouseDown="SteamGuard_DoubleClick" />
|
||||
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True" IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF" Style="{StaticResource MaterialDesignFilledTextBox}" Text="{Binding Code, FallbackValue=Code}">
|
||||
<TextBox.InputBindings>
|
||||
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
<Grid Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -173,16 +260,10 @@
|
||||
<ProgressBar Margin="5" Foreground="#FF9932CC" Height="15" md:TransitionAssist.DisableTransitions="True" Value="{Binding CodeProgress}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Style="{StaticResource MaterialDesignOutlinedButton}" Margin="10" Command="{Binding GetConfirmationsCommand}" Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}">
|
||||
<Button IsEnabled="{Binding IsMafileSelected}" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Style="{StaticResource MaterialDesignOutlinedButton}" Margin="10" Command="{Binding GetConfirmationsCommand}" Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}">
|
||||
<TextBlock TextWrapping="Wrap" TextTrimming="WordEllipsis" Text="{Tr MainWindow.RightPart.LoadConfirmations}"/>
|
||||
</Button>
|
||||
<ListBox md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2" w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True" Margin="10" Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" ItemsSource="{Binding Confirmations}" SelectionChanged="Selector_OnSelectionChanged">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style BasedOn="{StaticResource MaterialDesignListBoxItem}" TargetType="{x:Type ListBoxItem}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
</ListBox>
|
||||
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2" w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True" Margin="10" Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" ItemsSource="{Binding Confirmations}"/>
|
||||
<Button Grid.Row="3" Margin="1,0,1,3" w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Command="{Binding ConfirmLoginCommand}" Content="{Tr MainWindow.RightPart.ConfirmLogin}"/>
|
||||
</Grid>
|
||||
</md:Card>
|
||||
@@ -200,7 +281,7 @@
|
||||
<TextBlock FontWeight="Normal" Text="{Binding SelectedMafile.Group, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
|
||||
</ToolBarPanel>
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Margin="0,0,10,0">
|
||||
<Hyperlink NavigateUri="https://github.com/achiez" Foreground="{DynamicResource PrimaryHueMidBrush}" RequestNavigate="Hyperlink_OnRequestNavigate">by achies</Hyperlink>
|
||||
<Hyperlink NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies" Foreground="{DynamicResource PrimaryHueMidBrush}" RequestNavigate="Hyperlink_OnRequestNavigate">by achies</Hyperlink>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Threading;
|
||||
@@ -18,17 +17,15 @@ namespace NebulaAuth;
|
||||
|
||||
public partial class MainWindow
|
||||
{
|
||||
|
||||
private static string CodeCopiedString => LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied");
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
base.DataContext = new MainVM();
|
||||
DataContext = new MainVM();
|
||||
Application.Current.MainWindow = this;
|
||||
TrayManager.InitializeTray();
|
||||
ThemeManager.InitializeTheme();
|
||||
base.Title = base.Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
|
||||
this.Loaded += OnApplicationStarted;
|
||||
Title = Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
|
||||
Loaded += OnApplicationStarted;
|
||||
}
|
||||
|
||||
private async void OnApplicationStarted(object? sender, EventArgs e)
|
||||
@@ -51,35 +48,6 @@ public partial class MainWindow
|
||||
PHandler.SetPassword(pass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var lb = (ListBox)sender;
|
||||
lb.SelectedValue = null;
|
||||
}
|
||||
|
||||
private async void SteamGuard_DoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var tb = (TextBox)sender;
|
||||
if (tb.Text == CodeCopiedString) return;
|
||||
var code = tb.Text;
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(code);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
return;
|
||||
}
|
||||
tb.Text = CodeCopiedString;
|
||||
await Task.Delay(200);
|
||||
if (tb.Text == CodeCopiedString)
|
||||
{
|
||||
tb.Text = code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region Dran'n'Drop
|
||||
@@ -106,7 +74,7 @@ public partial class MainWindow
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
|
||||
string[] filePaths = (string[])e.Data.GetData(DataFormats.FileDrop)!;
|
||||
if (filePaths.Length == 0) return;
|
||||
if (this.DataContext is MainVM mainVm)
|
||||
if (DataContext is MainVM mainVm)
|
||||
{
|
||||
await mainVm.AddMafile(filePaths);
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
|
||||
namespace NebulaAuth.Model.Comparers;
|
||||
|
||||
public class ProxyDataComparer : IEqualityComparer<ProxyData>
|
||||
{
|
||||
public bool Equals(ProxyData? x, ProxyData? y)
|
||||
{
|
||||
return Equal(x, y);
|
||||
|
||||
}
|
||||
|
||||
public static bool Equal(ProxyData? x, ProxyData? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return true;
|
||||
if (ReferenceEquals(x, null)) return false;
|
||||
if (ReferenceEquals(y, null)) return false;
|
||||
return x.Address == y.Address
|
||||
&& x.Port == y.Port
|
||||
&& x.Username == y.Username
|
||||
&& x.Password == y.Password;
|
||||
|
||||
}
|
||||
|
||||
public int GetHashCode(ProxyData obj)
|
||||
{
|
||||
return HashCode.Combine(obj.Address, obj.Port, obj.Username, obj.Password);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ public class LoginConfirmationResult
|
||||
{
|
||||
[MemberNotNullWhen(false, nameof(Error))]
|
||||
public bool Success { get; set; }
|
||||
public string IP { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string IP { get; set; } = null!;
|
||||
public string Country { get; set; } = null!;
|
||||
public LoginConfirmationError? Error { get; set; }
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
using SteamLib;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NebulaAuth.Model.MAAC;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib;
|
||||
using SteamLib.Account;
|
||||
|
||||
namespace NebulaAuth.Model.Entities;
|
||||
|
||||
public class Mafile : MobileDataExtended
|
||||
[INotifyPropertyChanged]
|
||||
public partial class Mafile : MobileDataExtended
|
||||
{
|
||||
public MaProxy? Proxy { get; set; }
|
||||
public string? Group { get; set; }
|
||||
public string? Password { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public PortableMaClient? LinkedClient
|
||||
{
|
||||
get => _linkedClient;
|
||||
set => SetProperty(ref _linkedClient, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
private PortableMaClient? _linkedClient;
|
||||
|
||||
|
||||
public void SetSessionData(MobileSessionData? sessionData)
|
||||
{
|
||||
SessionData = sessionData;
|
||||
OnPropertyChanged(nameof(SessionData));
|
||||
}
|
||||
|
||||
public static Mafile FromMobileDataExtended(MobileDataExtended data)
|
||||
{
|
||||
return new Mafile
|
||||
@@ -23,10 +45,9 @@ public class Mafile : MobileDataExtended
|
||||
ServerTime = data.ServerTime,
|
||||
TokenGid = data.TokenGid,
|
||||
Uri = data.Uri,
|
||||
SteamId = data.SteamId
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static Mafile FromMobileDataExtended(MobileDataExtended data, MaProxy? proxy, string? group, string? password)
|
||||
{
|
||||
var result = FromMobileDataExtended(data);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace NebulaAuth.Model.Exceptions;
|
||||
|
||||
@@ -17,10 +16,4 @@ public class CantAlignTimeException : Exception
|
||||
public CantAlignTimeException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected CantAlignTimeException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib;
|
||||
|
||||
namespace NebulaAuth.Model.Exceptions;
|
||||
|
||||
public class MafileNeedReloginException : Exception
|
||||
{
|
||||
public Mafile Mafile { get; }
|
||||
|
||||
public MafileNeedReloginException(Mafile mafile)
|
||||
{
|
||||
Mafile = mafile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NebulaAuth.Model.MAAC;
|
||||
|
||||
public static class MultiAccountAutoConfirmer
|
||||
{
|
||||
public static ObservableCollection<Mafile> Clients { get; }
|
||||
private static Timer Timer { get; }
|
||||
private static readonly ReaderWriterLockSlim Lock = new();
|
||||
private const string LOC_PATH = "MAAC";
|
||||
|
||||
static MultiAccountAutoConfirmer()
|
||||
{
|
||||
Clients = [];
|
||||
Timer = new Timer(TimerConfirm);
|
||||
Settings.Instance.PropertyChanged += SettingsOnPropertyChanged;
|
||||
UpdateTimer();
|
||||
}
|
||||
|
||||
private static async void TimerConfirm(object? state)
|
||||
{
|
||||
var clients = Lock.ReadLock(() => Clients.ToArray());
|
||||
var enabledClients = clients.Where(x => x.LinkedClient is { IsError: false }).ToArray();
|
||||
enabledClients = DistributeEvenly(enabledClients).ToArray();
|
||||
var confirmed = 0;
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
foreach (var client in enabledClients)
|
||||
{
|
||||
var conf = 0;
|
||||
try
|
||||
{
|
||||
conf = await client.LinkedClient!.Confirm();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex, "Internal error while confirming {accountName}", client.AccountName);
|
||||
}
|
||||
|
||||
confirmed += conf;
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
if (confirmed > 0)
|
||||
SnackbarController.SendSnackbar(GetLocalization("TimerConfirmed") + confirmed);
|
||||
return;
|
||||
|
||||
//This function helps us to prevent 429 as much as it possible.
|
||||
//It's not perfect but better than nothing
|
||||
static IEnumerable<Mafile> DistributeEvenly(IEnumerable<Mafile> input)
|
||||
{
|
||||
var elementCounts = input
|
||||
.GroupBy(x => x.Proxy?.Id ?? -1)
|
||||
.ToDictionary(g => g.Key, g => new Queue<Mafile>(g));
|
||||
|
||||
var result = new List<Mafile>();
|
||||
bool added;
|
||||
do
|
||||
{
|
||||
added = false;
|
||||
foreach (var key in elementCounts.Keys.ToList())
|
||||
{
|
||||
if (elementCounts[key].Count > 0)
|
||||
{
|
||||
result.Add(elementCounts[key].Dequeue());
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (added);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static bool TryAddToConfirm(Mafile mafile)
|
||||
{
|
||||
return Lock.WriteLock(() =>
|
||||
{
|
||||
if (Clients.Contains(mafile)) return false;
|
||||
Clients.Add(mafile);
|
||||
mafile.LinkedClient = new PortableMaClient(mafile);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static void RemoveFromConfirm(Mafile mafile)
|
||||
{
|
||||
Lock.WriteLock(() =>
|
||||
{
|
||||
mafile.LinkedClient?.Dispose();
|
||||
mafile.LinkedClient = null;
|
||||
Clients.Remove(mafile);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(Settings.TimerSeconds)) return;
|
||||
UpdateTimer();
|
||||
}
|
||||
|
||||
private static void UpdateTimer()
|
||||
{
|
||||
var timerInterval = Settings.Instance.TimerSeconds;
|
||||
var intervalTimeSpan = TimeSpan.FromSeconds(timerInterval);
|
||||
Timer.Change(intervalTimeSpan, intervalTimeSpan);
|
||||
}
|
||||
|
||||
private static string GetLocalization(string key)
|
||||
{
|
||||
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Utility;
|
||||
using SteamLib.Api.Mobile;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile.Confirmations;
|
||||
using SteamLib.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AchiesUtilities.Web.Models;
|
||||
|
||||
namespace NebulaAuth.Model.MAAC;
|
||||
|
||||
public partial class PortableMaClient : ObservableObject, IDisposable
|
||||
{
|
||||
public Mafile Mafile { get; }
|
||||
private HttpClient Client { get; }
|
||||
private HttpClientHandler ClientHandler { get; }
|
||||
private DynamicProxy Proxy { get; }
|
||||
|
||||
[ObservableProperty] private bool _autoConfirmTrades;
|
||||
[ObservableProperty] private bool _autoConfirmMarket;
|
||||
[ObservableProperty] private bool _isError;
|
||||
private const string LOC_PATH = "MAAC";
|
||||
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
public PortableMaClient(Mafile mafile)
|
||||
{
|
||||
Mafile = mafile;
|
||||
Proxy = new DynamicProxy();
|
||||
Proxy.SetData(mafile.Proxy?.Data);
|
||||
var pair = ClientBuilder.BuildMobileClient(Proxy, mafile.SessionData);
|
||||
Client = pair.Client;
|
||||
ClientHandler = pair.Handler;
|
||||
UpdateCookies(mafile.SessionData);
|
||||
Mafile.PropertyChanged += Mafile_PropertyChanged;
|
||||
}
|
||||
|
||||
private void Mafile_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Mafile.SessionData))
|
||||
{
|
||||
UpdateCookies(Mafile.SessionData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void UpdateCookies(IMobileSessionData? sessionData)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() => IsError = sessionData == null);
|
||||
ClientHandler.CookieContainer.ClearAllCookies();
|
||||
if (sessionData != null)
|
||||
{
|
||||
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientHandler.CookieContainer.ClearSteamCookies();
|
||||
ClientHandler.CookieContainer.AddMinimalMobileCookies();
|
||||
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<int> Confirm()
|
||||
{
|
||||
Proxy.SetData(Mafile.Proxy?.Data ?? MaClient.DefaultProxy);
|
||||
List<Confirmation> conf;
|
||||
try
|
||||
{
|
||||
conf = (await HandleTimerRequest(GetConfirmations)).ToList();
|
||||
}
|
||||
catch (ApplicationException ex)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Timer {accountName}: Error GetConf in timer.", Mafile.AccountName);
|
||||
return 0;
|
||||
}
|
||||
var toConfirm = new List<Confirmation>();
|
||||
if (AutoConfirmMarket)
|
||||
{
|
||||
var market = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction);
|
||||
toConfirm.AddRange(market);
|
||||
}
|
||||
if (AutoConfirmTrades)
|
||||
{
|
||||
var trade = conf.Where(c => c.ConfType == ConfirmationType.Trade);
|
||||
toConfirm.AddRange(trade);
|
||||
}
|
||||
|
||||
if (toConfirm.Count == 0) return 0;
|
||||
try
|
||||
{
|
||||
Shell.Logger.Debug("Timer {accountName}: Sending confirmations. Count: {count}", Mafile.AccountName, toConfirm.Count);
|
||||
var success = await HandleTimerRequest(() => SendConfirmations(toConfirm));
|
||||
Shell.Logger.Debug("Timer {accountName}: Confirmation sent: {confirmResult}", Mafile.AccountName, success);
|
||||
return toConfirm.Count;
|
||||
}
|
||||
catch (ApplicationException ex)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Timer {accountName}: MultiConf error in Timer.", Mafile.AccountName);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<IEnumerable<Confirmation>> GetConfirmations()
|
||||
{
|
||||
return await SteamMobileConfirmationsApi.GetConfirmations(Client, Mafile, Mafile.SessionData!.SteamId, _cts.Token);
|
||||
}
|
||||
|
||||
private async Task<bool> SendConfirmations(IEnumerable<Confirmation> confirmations)
|
||||
{
|
||||
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, confirmations, Mafile.SessionData!.SteamId, Mafile, true, _cts.Token);
|
||||
}
|
||||
|
||||
private async Task<T> HandleTimerRequest<T>(Func<Task<T>> func)
|
||||
{
|
||||
Exception innerException;
|
||||
try
|
||||
{
|
||||
return await SessionHandler.Handle(func, Mafile, Chp(), GetTimerPrefix());
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
innerException = ex; //Ignored
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
{
|
||||
if (IgnoreTimerErrors())
|
||||
{
|
||||
Shell.Logger.Warn("Timer {accountName}: Session error while requesting in timer. Ignored due to IgnorePatchTuesdayErrors setting", Mafile.AccountName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Shell.Logger.Warn("Timer {accountName}: Session error while requesting in timer. Timer disabled", Mafile.AccountName);
|
||||
SetError();
|
||||
}
|
||||
|
||||
innerException = ex;
|
||||
}
|
||||
catch (Exception ex) when (ExceptionHandler.Handle(ex, prefix: GetTimerPrefix()))
|
||||
{
|
||||
innerException = ex;
|
||||
}
|
||||
throw new ApplicationException("Swallowed", innerException);
|
||||
}
|
||||
|
||||
|
||||
private bool IgnoreTimerErrors()
|
||||
{
|
||||
if (Settings.Instance.IgnorePatchTuesdayErrors == false) return false;
|
||||
|
||||
var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
|
||||
var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone);
|
||||
|
||||
var startTime = pstNow.Date.AddHours(15).AddMinutes(55); // 15:55 PST //22:55 GMT //00:55 GMT+2
|
||||
var endTime = pstNow.Date.AddHours(17).AddMinutes(15); // 17:15 PST //00:15 GMT //02:15 GMT+2
|
||||
|
||||
return pstNow.DayOfWeek == DayOfWeek.Tuesday && pstNow >= startTime && pstNow <= endTime;
|
||||
}
|
||||
|
||||
|
||||
private HttpClientHandlerPair Chp() => new(Client, ClientHandler);
|
||||
|
||||
private static string GetLocalization(string key)
|
||||
{
|
||||
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
|
||||
}
|
||||
|
||||
private string GetTimerPrefix()
|
||||
{
|
||||
return GetLocalization("TimerPrefix") + Mafile.AccountName + ": ";
|
||||
}
|
||||
|
||||
public void SetError()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() => IsError = true);
|
||||
Shell.Logger.Warn("Timer {accountName}: disabled due to error.", Mafile.AccountName);
|
||||
SnackbarController.SendSnackbar(GetTimerPrefix() + GetLocalization("TimerSessionError"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_cts.Dispose();
|
||||
Mafile.PropertyChanged -= Mafile_PropertyChanged;
|
||||
Client.Dispose();
|
||||
ClientHandler.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,18 @@
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using AchiesUtilities.Web.Models;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Api.Mobile;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Authentication.LoginV2;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.ProtoCore;
|
||||
using SteamLib.ProtoCore.Services;
|
||||
using SteamLib.SteamMobile;
|
||||
using SteamLib.SteamMobile.Confirmations;
|
||||
using SteamLib.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using SteamLib.Api;
|
||||
using SteamLib.Core.Enums;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
@@ -32,26 +26,20 @@ public static class MaClient
|
||||
private static DynamicProxy Proxy { get; }
|
||||
|
||||
public static ProxyData? DefaultProxy { get; set; }
|
||||
public static HttpClientHandlerPair Chp => new(Client, ClientHandler);
|
||||
|
||||
static MaClient()
|
||||
{
|
||||
Proxy = new DynamicProxy(null);
|
||||
Proxy = new DynamicProxy();
|
||||
var pair = ClientBuilder.BuildMobileClient(Proxy, null);
|
||||
Client = pair.Client;
|
||||
ClientHandler = pair.Handler;
|
||||
}
|
||||
|
||||
public static void ClearCookies()
|
||||
{
|
||||
foreach (Cookie allCookie in ClientHandler.CookieContainer.GetAllCookies())
|
||||
{
|
||||
allCookie.Expired = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetAccount(Mafile? account)
|
||||
{
|
||||
ClearCookies();
|
||||
ClientHandler.CookieContainer.ClearAllCookies();
|
||||
if (account != null)
|
||||
{
|
||||
if (account.SessionData != null)
|
||||
@@ -72,71 +60,41 @@ public static class MaClient
|
||||
{
|
||||
ValidateMafile(mafile);
|
||||
SetProxy(mafile);
|
||||
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId.Steam64);
|
||||
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId);
|
||||
}
|
||||
|
||||
public static async Task LoginAgain(Mafile mafile, string password, bool savePassword, ICaptchaResolver? resolver)
|
||||
public static Task LoginAgain(Mafile mafile, string password, bool savePassword, ICaptchaResolver? resolver)
|
||||
{
|
||||
SetProxy(mafile);
|
||||
var sgGenerator = new SteamGuardCodeGenerator(mafile.SharedSecret);
|
||||
var options = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, Client)
|
||||
{
|
||||
Logger = Shell.ExtensionsLogger,
|
||||
SteamGuardProvider = sgGenerator,
|
||||
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
|
||||
WebsiteId = "Mobile"
|
||||
};
|
||||
ClientHandler.CookieContainer.ClearMobileSessionCookies();
|
||||
var result = await LoginV2Executor.DoLogin(options, mafile.AccountName, password);
|
||||
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
|
||||
mafile.SessionData = (MobileSessionData)result;
|
||||
if(PHandler.IsPasswordSet)
|
||||
mafile.Password = (savePassword ? PHandler.Encrypt(password) : null);
|
||||
Storage.UpdateMafile(mafile);
|
||||
return SessionHandler.LoginAgain(Chp, mafile, password, savePassword);
|
||||
}
|
||||
|
||||
public static async Task RefreshSession(Mafile mafile)
|
||||
|
||||
public static Task RefreshSession(Mafile mafile)
|
||||
{
|
||||
ValidateMafile(mafile, true);
|
||||
SetProxy(mafile);
|
||||
var token = mafile.SessionData!.GetMobileToken();
|
||||
if (token == null || token.Value.IsExpired)
|
||||
{
|
||||
var sessionToken = await SteamMobileApi.RefreshJwt(Client, mafile.SessionData!.RefreshToken.Token, mafile.SessionData.SteamId.Steam64);
|
||||
var newToken = SteamTokenHelper.Parse(sessionToken);
|
||||
mafile.SessionData.SetMobileToken(newToken);
|
||||
}
|
||||
|
||||
var communityToken = mafile.SessionData!.GetToken(SteamDomain.Community);
|
||||
if (communityToken == null || communityToken.Value.IsExpired)
|
||||
{
|
||||
var communityTokenString = await SteamGlobalApi.RefreshJwt(Client, SteamDomain.Community);
|
||||
var newToken = SteamTokenHelper.Parse(communityTokenString);
|
||||
mafile.SessionData.SetToken(SteamDomain.Community, newToken);
|
||||
}
|
||||
|
||||
Storage.UpdateMafile(mafile);
|
||||
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
|
||||
return SessionHandler.RefreshMobileToken(Chp, mafile);
|
||||
}
|
||||
|
||||
public static Task<bool> SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
|
||||
{
|
||||
ValidateMafile(mafile);
|
||||
SetProxy(mafile);
|
||||
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
|
||||
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId, mafile, confirm);
|
||||
}
|
||||
|
||||
public static Task<bool> SendMultipleConfirmation(Mafile mafile, IEnumerable<Confirmation> confirmations, bool confirm)
|
||||
{
|
||||
var enumerable = confirmations.ToList();
|
||||
if (!enumerable.Any())
|
||||
if (enumerable.Count == 0)
|
||||
{
|
||||
return Task.FromResult(result: false);
|
||||
}
|
||||
|
||||
ValidateMafile(mafile);
|
||||
SetProxy(mafile);
|
||||
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
|
||||
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId, mafile, confirm);
|
||||
}
|
||||
|
||||
public static Task<RemoveAuthenticator_Response> RemoveAuthenticator(Mafile mafile)
|
||||
@@ -160,62 +118,41 @@ public static class MaClient
|
||||
{
|
||||
if (mafile.SessionData == null) throw new SessionInvalidException();
|
||||
if (mafile.SessionData.RefreshToken.IsExpired)
|
||||
throw new SessionExpiredException();
|
||||
throw new SessionPermanentlyExpiredException();
|
||||
|
||||
if (ignoreAccessToken == false)
|
||||
{
|
||||
var access = mafile.SessionData.GetMobileToken();
|
||||
if (access == null || access.Value.IsExpired)
|
||||
throw new SessionExpiredException();
|
||||
throw new SessionPermanentlyExpiredException();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static async Task<LoginConfirmationResult> ConfirmLoginRequest(Mafile mafile)
|
||||
{
|
||||
if (mafile.SessionData == null)
|
||||
{
|
||||
throw new SessionExpiredException();
|
||||
}
|
||||
|
||||
ValidateMafile(mafile);
|
||||
var token = mafile.SessionData.GetMobileToken()!.Value;
|
||||
SetProxy(mafile);
|
||||
var token = mafile.SessionData!.GetMobileToken()!.Value;
|
||||
var sessions = await SteamMobileAuthenticatorApi.GetAuthSessionsForAccount(Client, token.Token);
|
||||
|
||||
var uri = "https://api.steampowered.com/IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" + token.Token;
|
||||
GetAuthSessionsForAccount_Response getsess;
|
||||
try
|
||||
{
|
||||
getsess = await Client.GetProto<GetAuthSessionsForAccount_Response>(uri, new EmptyMessage());
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new SessionExpiredException(string.Empty, ex);
|
||||
}
|
||||
|
||||
if (getsess.ClientIds.Count == 0)
|
||||
if (sessions.ClientIds.Count == 0)
|
||||
{
|
||||
return new LoginConfirmationResult
|
||||
{
|
||||
Error = LoginConfirmationError.NoRequests
|
||||
};
|
||||
}
|
||||
if (getsess.ClientIds.Count > 1)
|
||||
if (sessions.ClientIds.Count > 1)
|
||||
{
|
||||
return new LoginConfirmationResult
|
||||
{
|
||||
Error = LoginConfirmationError.MoreThanOneRequest
|
||||
};
|
||||
}
|
||||
var clientId = getsess.ClientIds.Single();
|
||||
var infoUri = "https://api.steampowered.com/IAuthenticationService/GetAuthSessionInfo/v1?access_token=" + token.Token;
|
||||
var infoReq = new GetAuthSessionInfo_Request
|
||||
{
|
||||
ClientId = clientId
|
||||
};
|
||||
var infoResp = await Client.PostProto<GetAuthSessionInfo_Response>(infoUri, infoReq);
|
||||
var updateUri = "https://api.steampowered.com/IAuthenticationService/UpdateAuthSessionWithMobileConfirmation/v1?access_token=" + token.Token;
|
||||
|
||||
var clientId = sessions.ClientIds.Single();
|
||||
var clientInfo = await SteamMobileAuthenticatorApi.GetAuthSessionInfo(Client, token.Token, clientId);
|
||||
var updateReq = new UpdateAuthSessionWithMobileConfirmation_Request
|
||||
{
|
||||
ClientId = clientId,
|
||||
@@ -224,12 +161,11 @@ public static class MaClient
|
||||
Steamid = mafile.SessionData.SteamId.Steam64.ToUlong(),
|
||||
Version = 1
|
||||
};
|
||||
updateReq.ComputeSignature(mafile.SharedSecret);
|
||||
await Client.PostProtoEnsureSuccess(updateUri, updateReq);
|
||||
await SteamMobileAuthenticatorApi.UpdateAuthSessionStatus(Client, token.Token, mafile.SharedSecret, updateReq);
|
||||
return new LoginConfirmationResult
|
||||
{
|
||||
Country = infoResp.Country,
|
||||
IP = infoResp.IP,
|
||||
Country = clientInfo.Country,
|
||||
IP = clientInfo.IP,
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using NebulaAuth.Model.Entities;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib;
|
||||
using SteamLib.Utility.MafileSerialization;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public static class NebulaSerializer
|
||||
{
|
||||
public static MafileSerializer Serializer { get; }
|
||||
|
||||
static NebulaSerializer()
|
||||
{
|
||||
Serializer = new MafileSerializer(new MafileSerializerSettings
|
||||
{
|
||||
DeserializationOptions =
|
||||
{
|
||||
AllowDeviceIdGeneration = true,
|
||||
AllowSessionIdGeneration = true,
|
||||
ThrowIfInvalidSteamId = false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static Mafile Deserialize(string cont)
|
||||
{
|
||||
var data = Serializer.Deserialize(cont);
|
||||
var mobileData = data.Data;
|
||||
var info = data.Info;
|
||||
if (info.IsExtended == false)
|
||||
throw new FormatException("Mafile is not extended data");
|
||||
|
||||
|
||||
var props = info.UnusedProperties ?? new Dictionary<string, JProperty>();
|
||||
var proxy = GetPropertyValue<MaProxy>("Proxy", props);
|
||||
var group = GetPropertyValue<string>("Group", props);
|
||||
var password = GetPropertyValue<string>("Password", props);
|
||||
var mafile = Mafile.FromMobileDataExtended((MobileDataExtended)mobileData, proxy, group, password);
|
||||
|
||||
|
||||
if (!info.SteamIdValid)
|
||||
throw new MafileNeedReloginException(mafile);
|
||||
|
||||
return mafile;
|
||||
}
|
||||
|
||||
private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out var prop) == false) return default;
|
||||
var value = prop.Value;
|
||||
try
|
||||
{
|
||||
return value.ToObject<T>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Can't deserialize property {name}", name);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static string SerializeMafile(Mafile data)
|
||||
{
|
||||
var props = new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(Mafile.Proxy), data.Proxy},
|
||||
{nameof(Mafile.Group), data.Group},
|
||||
{nameof(Mafile.Password), data.Password}
|
||||
};
|
||||
return SerializeMafile(data, props);
|
||||
}
|
||||
|
||||
|
||||
public static string SerializeMafile(MobileDataExtended data, Dictionary<string, object?>? properties)
|
||||
{
|
||||
if (Settings.Instance.LegacyMode)
|
||||
{
|
||||
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MafileSerializer.Serialize(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,28 @@ using System.Text;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public static class PHandler //RETHINK: Use SecureString?
|
||||
public static class PHandler
|
||||
{
|
||||
public static bool IsPasswordSet => _k.Length > 0;
|
||||
private static byte[] _k = Array.Empty<byte>();
|
||||
private static byte[] _k = [];
|
||||
|
||||
|
||||
public static void SetPassword(string? password)
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="password"></param>
|
||||
/// <returns><see langword="true"/> if password was set and not empty. Otherwise - <see langword="false"/></returns>
|
||||
public static bool SetPassword(string? password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
_k = Array.Empty<byte>();
|
||||
return;
|
||||
_k = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
var keyBytes = Encoding.UTF8.GetBytes(password);
|
||||
_k = SHA256.HashData(keyBytes);
|
||||
return _k.Length > 0;
|
||||
}
|
||||
|
||||
public static string Encrypt(string plainText)
|
||||
@@ -56,7 +62,6 @@ public static class PHandler //RETHINK: Use SecureString?
|
||||
{
|
||||
if (_k.Length == 0) throw new Exception("Password not set");
|
||||
var encryptedBytes = Convert.FromBase64String(encryptedText);
|
||||
string decryptedText = null;
|
||||
|
||||
using var aes = Aes.Create();
|
||||
var keyBytes = _k;
|
||||
@@ -70,7 +75,7 @@ public static class PHandler //RETHINK: Use SecureString?
|
||||
using var ms = new MemoryStream(encryptedBytes);
|
||||
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
|
||||
using var reader = new StreamReader(cs);
|
||||
decryptedText = reader.ReadToEnd();
|
||||
var decryptedText = reader.ReadToEnd();
|
||||
|
||||
return decryptedText;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AchiesUtilities.Collections;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using System.Linq;
|
||||
using AchiesUtilities.Web.Proxy.Parsing;
|
||||
using NebulaAuth.Core;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -10,20 +12,30 @@ namespace NebulaAuth.Model;
|
||||
|
||||
public static class ProxyStorage
|
||||
{
|
||||
|
||||
public const string FORMAT = ADDRESS_FORMAT + ":{USER}:{PASS}";
|
||||
public const string ADDRESS_FORMAT = "{IP}:{PORT}";
|
||||
|
||||
public static readonly ProxyParser DefaultScheme = new(
|
||||
ProxyDefaultFormats.UniversalColon, false, ProxyProtocol.HTTP,
|
||||
ProxyPatternProtocol.All,
|
||||
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4,
|
||||
PatternRequirement.Optional,
|
||||
PatternRequirement.Optional);
|
||||
|
||||
|
||||
public static ObservableDictionary<int, ProxyData> Proxies { get; } = new();
|
||||
|
||||
|
||||
static ProxyStorage()
|
||||
{
|
||||
|
||||
if(File.Exists("proxies.json") == false)
|
||||
if (File.Exists("proxies.json") == false)
|
||||
return;
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText("proxies.json");
|
||||
var proxies = JsonConvert.DeserializeObject<Proxies>(json) ?? throw new NullReferenceException();
|
||||
var proxies = JsonConvert.DeserializeObject<ProxiesSchema>(json) ?? throw new NullReferenceException();
|
||||
Proxies = proxies.ProxiesData;
|
||||
Proxies = new ObservableDictionary<int, ProxyData>(
|
||||
Proxies.OrderBy(p => p.Key)
|
||||
@@ -34,21 +46,18 @@ public static class ProxyStorage
|
||||
MaClient.DefaultProxy = Proxies[proxies.DefaultProxy.Value];
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
SnackbarController.SendSnackbar("Ошибка при загрузке прокси");
|
||||
SnackbarController.SendSnackbar(ex.Message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static void SetProxy(int? id, ProxyData proxyData)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
if (Proxies.Any() == false)
|
||||
if (Proxies.Count == 0)
|
||||
{
|
||||
id = 0;
|
||||
}
|
||||
@@ -59,18 +68,49 @@ public static class ProxyStorage
|
||||
}
|
||||
|
||||
Proxies[id] = proxyData;
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void SetProxies(IEnumerable<KeyValuePair<int?, ProxyData>> proxies)
|
||||
{
|
||||
foreach (var (key, proxyData) in proxies)
|
||||
{
|
||||
var id = key;
|
||||
if (id == null)
|
||||
{
|
||||
if (Proxies.Count == 0)
|
||||
{
|
||||
id = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
id = Proxies.Keys.Max() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
Proxies[id] = proxyData;
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void OrderCollection() //RETHINK: maybe there is a better way to handle it
|
||||
{
|
||||
var proxies = Proxies.OrderBy(p => p.Key)
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
Proxies.Clear();
|
||||
foreach (var kvp in proxies)
|
||||
{
|
||||
Proxies.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveProxy(int id)
|
||||
{
|
||||
Proxies.Remove(id);
|
||||
Save();
|
||||
}
|
||||
public static bool CompareProxy(ProxyData proxyData1, ProxyData proxyData2)
|
||||
{
|
||||
return proxyData1.ToString(FORMAT) == proxyData2.ToString(FORMAT);
|
||||
}
|
||||
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
@@ -79,29 +119,33 @@ public static class ProxyStorage
|
||||
File.WriteAllText("proxies.json", json);
|
||||
}
|
||||
|
||||
private static Proxies Create()
|
||||
public static string GetProxyString(ProxyData proxyData)
|
||||
{
|
||||
return proxyData.AuthEnabled ? proxyData.ToString(FORMAT) : proxyData.ToString(ADDRESS_FORMAT);
|
||||
}
|
||||
|
||||
private static ProxiesSchema Create()
|
||||
{
|
||||
int? def = null;
|
||||
if (MaClient.DefaultProxy != null)
|
||||
{
|
||||
var search = Proxies.FirstOrDefault(p => p.Value.Equals(MaClient.DefaultProxy));
|
||||
if (search.Value != null!)
|
||||
{
|
||||
def = search.Key;
|
||||
}
|
||||
var search = Proxies.FirstOrDefault(p => p.Value.Equals(MaClient.DefaultProxy));
|
||||
if (search.Value != null!)
|
||||
{
|
||||
def = search.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return new Proxies
|
||||
return new ProxiesSchema
|
||||
{
|
||||
ProxiesData = Proxies,
|
||||
DefaultProxy = def
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Proxies
|
||||
{
|
||||
public ObservableDictionary<int, ProxyData> ProxiesData { get; set; }
|
||||
public int? DefaultProxy { get; set; }
|
||||
private class ProxiesSchema
|
||||
{
|
||||
public ObservableDictionary<int, ProxyData> ProxiesData = [];
|
||||
public int? DefaultProxy;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,123 @@
|
||||
using NebulaAuth.Core;
|
||||
using AchiesUtilities.Web.Models;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Exceptions;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public static class SessionHandler
|
||||
public static partial class SessionHandler
|
||||
{
|
||||
public static event EventHandler? LoginStarted;
|
||||
public static event EventHandler? LoginCompleted;
|
||||
|
||||
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile)
|
||||
private static readonly SemaphoreSlim Semaphore = new(1, 1);
|
||||
|
||||
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile,
|
||||
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null)
|
||||
{
|
||||
string? password = null;
|
||||
chp ??= MaClient.Chp;
|
||||
await Semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(mafile.Password))
|
||||
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<T> HandleInternal<T>(Func<Task<T>> func, HttpClientHandlerPair chp, Mafile mafile,
|
||||
string? snackbarPrefix = null)
|
||||
{
|
||||
using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler");
|
||||
var mobileTokenExpired = MobileTokenExpired(mafile);
|
||||
var refreshTokenExpired = RefreshTokenExpired(mafile);
|
||||
var password = GetPassword(mafile);
|
||||
|
||||
if (!mobileTokenExpired)
|
||||
{
|
||||
try
|
||||
{
|
||||
password = PHandler.Decrypt(mafile.Password);
|
||||
return await func();
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
when (refreshTokenExpired == false || password != null)
|
||||
{
|
||||
if (ex is SessionPermanentlyExpiredException)
|
||||
{
|
||||
Shell.Logger.Debug(ex, "RefreshToken on mafile {name} {steamid} is expired", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
refreshTokenExpired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Shell.Logger.Debug(ex, "MobileToken on mafile {name} {steamid} is expired", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//State: mobileToken invalid/expired, refreshToken maybe not expired
|
||||
if (!refreshTokenExpired)
|
||||
{
|
||||
var refreshed = await RefreshInternal(chp, mafile);
|
||||
if (refreshed)
|
||||
{
|
||||
SnackbarController.SendSnackbar(snackbarPrefix + LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically", "SessionHandler", "SessionWasRefreshedAutomatically"));
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
{
|
||||
Shell.Logger.Info(ex, "MobileToken on {name} {steamid} was refreshed but after it, error occured", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
if (password == null)
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shell.Logger.Debug("Session on mafile {name} {steamid} is invalid/expired", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
|
||||
//State: mobileToken invalid/expired, refreshToken invalid/expired
|
||||
if (password != null)
|
||||
{
|
||||
var logged = await LoginAgainInternal(chp, mafile, password, true);
|
||||
if (logged)
|
||||
{
|
||||
Shell.Logger.Debug("Mafile {name} {steamid} was succesfully auto-relogined", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
return await func();
|
||||
}
|
||||
}
|
||||
|
||||
//Nothing to do more, everything is expired
|
||||
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static bool MobileTokenExpired(Mafile mafile)
|
||||
{
|
||||
var mobileToken = mafile.SessionData?.GetMobileToken();
|
||||
return mobileToken == null || mobileToken.Value.IsExpired;
|
||||
}
|
||||
|
||||
private static bool RefreshTokenExpired(Mafile mafile)
|
||||
{
|
||||
return mafile.SessionData?.RefreshToken.IsExpired != false;
|
||||
}
|
||||
|
||||
private static string? GetPassword(Mafile mafile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PHandler.IsPasswordSet && !string.IsNullOrWhiteSpace(mafile.Password))
|
||||
{
|
||||
return PHandler.Decrypt(mafile.Password);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -26,69 +125,74 @@ public static class SessionHandler
|
||||
// ignored
|
||||
}
|
||||
|
||||
var refreshed = false;
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (SessionExpiredException) when (mafile.SessionData is not { RefreshToken.IsExpired: true})
|
||||
{
|
||||
refreshed = await TryRefresh(mafile);
|
||||
}
|
||||
catch (SessionInvalidException)
|
||||
when (password != null)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
if (refreshed)
|
||||
{
|
||||
Shell.Logger.Debug("Token on mafile {name} {steamid} refreshed", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (Exception ex3)
|
||||
when (password != null && ex3 is SessionExpiredException or SessionInvalidException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (password == null)
|
||||
{
|
||||
throw new SessionInvalidException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LoginStarted?.Invoke(null, EventArgs.Empty);
|
||||
await MaClient.LoginAgain(mafile, password, savePassword: true, null);
|
||||
Shell.Logger.Debug("Mafile {name} {steamid} succesfully auto-relogined", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
LoginCompleted?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
return await func();
|
||||
return null;
|
||||
}
|
||||
|
||||
private static async Task<bool> TryRefresh(Mafile mafile)
|
||||
|
||||
private static async Task<bool> RefreshInternal(HttpClientHandlerPair chp, Mafile mafile)
|
||||
{
|
||||
try
|
||||
{
|
||||
await MaClient.RefreshSession(mafile);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically", "SessionHandler", "SessionWasRefreshedAutomatically"));
|
||||
await RefreshMobileToken(chp, mafile);
|
||||
return true;
|
||||
}
|
||||
catch (SessionInvalidException)
|
||||
catch(Exception ex)
|
||||
{
|
||||
Shell.Logger.Debug("Token on mafile {name} {steamid} not refreshed", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
Shell.Logger.Debug(ex, "Failed to refresh session on mafile {name} {steamid}", mafile.AccountName, mafile.SessionData?.SteamId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> LoginAgainInternal(HttpClientHandlerPair chp, Mafile mafile, string password,
|
||||
bool savePassword)
|
||||
{
|
||||
var t = Task.Run(OnLoginStarted);
|
||||
try
|
||||
{
|
||||
|
||||
await LoginAgain(chp, mafile, password, savePassword);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnLoginCompleted();
|
||||
await t;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task OnLoginStarted()
|
||||
{
|
||||
if (DialogHost.IsDialogOpen(null)) return;
|
||||
await Application.Current.Dispatcher.BeginInvoke(async () =>
|
||||
{
|
||||
await DialogHost.Show(new WaitLoginDialog());
|
||||
});
|
||||
}
|
||||
|
||||
private static void OnLoginCompleted()
|
||||
{
|
||||
var currentSession = DialogHost.GetDialogSession(null);
|
||||
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (currentSession is { Content: WaitLoginDialog, IsEnded: false })
|
||||
{
|
||||
try
|
||||
{
|
||||
currentSession.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using AchiesUtilities.Web.Models;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Account;
|
||||
using SteamLib.Api.Mobile;
|
||||
using SteamLib.Authentication;
|
||||
using SteamLib.Authentication.LoginV2;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public partial class SessionHandler //API
|
||||
{
|
||||
public static async Task RefreshMobileToken(HttpClientHandlerPair chp, Mafile mafile)
|
||||
{
|
||||
if (mafile.SessionData is not { RefreshToken.IsExpired: false })
|
||||
throw new SessionPermanentlyExpiredException(SessionInvalidException.SESSION_NULL_MSG);
|
||||
|
||||
var mobileToken = await SteamMobileApi.RefreshJwt(chp.Client, mafile.SessionData.RefreshToken.Token, mafile.SessionData.SteamId);
|
||||
Shell.Logger.Info("MobileToken on {name} {steamid} successfully refreshed", mafile.AccountName, mafile.SessionData.SteamId);
|
||||
|
||||
var newToken = SteamTokenHelper.Parse(mobileToken);
|
||||
mafile.SessionData.SetMobileToken(newToken);
|
||||
|
||||
//Trigger PropertyChanged event for PortableMaClient handling session updated from MaClient
|
||||
//RETHINK: it makes double operation when session handled from PortableMaClient (more often scenario) which is unwanted behaviour
|
||||
mafile.SetSessionData(mafile.SessionData);
|
||||
Storage.UpdateMafile(mafile);
|
||||
chp.Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
|
||||
}
|
||||
|
||||
public static async Task LoginAgain(HttpClientHandlerPair chp, Mafile mafile, string password, bool savePassword)
|
||||
{
|
||||
var sgGenerator = new SteamGuardCodeGenerator(mafile.SharedSecret);
|
||||
var options = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, chp.Client)
|
||||
{
|
||||
Logger = Shell.ExtensionsLogger,
|
||||
SteamGuardProvider = sgGenerator,
|
||||
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
|
||||
WebsiteId = "Mobile",
|
||||
};
|
||||
chp.Handler.CookieContainer.ClearMobileSessionCookies();
|
||||
var result = await LoginV2Executor.DoLogin(options, mafile.AccountName, password);
|
||||
Shell.Logger.Info("Logged in again on {name} {steamid}", mafile.AccountName, result.SteamId);
|
||||
AdmissionHelper.TransferCommunityCookies(chp.Handler.CookieContainer);
|
||||
|
||||
//Triggers PropertyChanged event for PortableMaClient handling session updated from MaClient
|
||||
//RETHINK: it makes double operation when session handled from PortableMaClient (more often scenario) which is unwanted behaviour
|
||||
mafile.SetSessionData((MobileSessionData)result);
|
||||
if (PHandler.IsPasswordSet)
|
||||
mafile.Password = savePassword ? PHandler.Encrypt(password) : null;
|
||||
Storage.UpdateMafile(mafile);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ public partial class Settings : ObservableObject
|
||||
{
|
||||
#region Properties
|
||||
|
||||
[ObservableProperty] private bool _disableTimersOnChange = true;
|
||||
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
|
||||
[ObservableProperty] private bool _hideToTray;
|
||||
[ObservableProperty] private int _timerSeconds = 60;
|
||||
@@ -22,7 +21,8 @@ public partial class Settings : ObservableObject
|
||||
[ObservableProperty] private LocalizationLanguage _language = LocalizationLanguage.English;
|
||||
[ObservableProperty] private bool _legacyMode = true;
|
||||
[ObservableProperty] private bool _allowAutoUpdate;
|
||||
|
||||
[ObservableProperty] private bool _useAccountNameAsMafileName;
|
||||
[ObservableProperty] private bool _ignorePatchTuesdayErrors;
|
||||
#endregion
|
||||
|
||||
public static Settings Instance { get; }
|
||||
|
||||
@@ -17,7 +17,7 @@ public static class Shell
|
||||
|
||||
var lp = new NLog.Extensions.Logging.NLogLoggerProvider();
|
||||
var logger = lp.CreateLogger("SteamLib");
|
||||
HealthMonitor.FatalLogger = logger;
|
||||
SteamLibErrorMonitor.MonitorLogger = logger;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
|
||||
|
||||
var loggerFactory = new NLog.Extensions.Logging.NLogLoggerFactory();
|
||||
|
||||
+109
-85
@@ -1,18 +1,20 @@
|
||||
using NebulaAuth.Model.Entities;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamLib;
|
||||
using SteamLib.Core.Models;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile;
|
||||
using SteamLib.Utility.MaFiles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Exceptions;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using NLog.Targets;
|
||||
using SteamLib;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
//RETHINK
|
||||
public static class Storage
|
||||
{
|
||||
public const string MAFILE_F = "maFiles";
|
||||
@@ -23,6 +25,7 @@ public static class Storage
|
||||
|
||||
public static ObservableCollection<Mafile> MaFiles { get; } = new();
|
||||
|
||||
public static readonly int DuplicateFound;
|
||||
static Storage()
|
||||
{
|
||||
if (Directory.Exists(MafileFolder) == false)
|
||||
@@ -36,12 +39,24 @@ public static class Storage
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(MafileFolder);
|
||||
foreach (var file in files)
|
||||
var hashNames = new HashSet<string>();
|
||||
var hashIds = new HashSet<SteamId>();
|
||||
var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
|
||||
var ordered = files.Order(comparer).ToList();
|
||||
foreach (var file in ordered.Where(file => Path.GetExtension(file).EqualsIgnoreCase(".mafile")))
|
||||
{
|
||||
if (Path.GetExtension(file).Equals(".mafile", StringComparison.InvariantCultureIgnoreCase) == false) continue;
|
||||
try
|
||||
{
|
||||
var data = ReadMafile(file);
|
||||
|
||||
if (hashNames.Contains(data.AccountName) || hashIds.Contains(data.SteamId))
|
||||
{
|
||||
DuplicateFound++;
|
||||
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
|
||||
continue;
|
||||
}
|
||||
hashNames.Add(data.AccountName);
|
||||
if (data.SessionData != null) hashIds.Add(data.SteamId);
|
||||
MaFiles.Add(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -53,7 +68,14 @@ public static class Storage
|
||||
MaFiles = new ObservableCollection<Mafile>(MaFiles.OrderBy(m => m.AccountName));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="overwrite"></param>
|
||||
/// <exception cref="FormatException"></exception>
|
||||
/// <exception cref="SessionInvalidException"></exception>
|
||||
/// <exception cref="IOException"></exception>
|
||||
public static void AddNewMafile(string path, bool overwrite)
|
||||
{
|
||||
Mafile data;
|
||||
@@ -62,37 +84,29 @@ public static class Storage
|
||||
data = ReadMafile(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
when(ex is not MafileNeedReloginException)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Can't load mafile");
|
||||
throw new FormatException("File data is not valid", ex);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(data.AccountName))
|
||||
throw new FormatException("File data is not valid. Missing AccountName");
|
||||
|
||||
try
|
||||
{
|
||||
var code = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
|
||||
_ = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new FormatException("Can't generate code on this mafile", ex);
|
||||
}
|
||||
|
||||
|
||||
if (data.SessionData == null)
|
||||
throw new SessionInvalidException("File data is not valid. Missing SessionData")
|
||||
{
|
||||
Data = { { "mafile", data } }
|
||||
};
|
||||
|
||||
|
||||
if (overwrite == false && File.Exists(GetMafilePath(data)))
|
||||
if (overwrite == false && File.Exists(CreatePathForMafile(data)))
|
||||
{
|
||||
throw new IOException("File already exist and overwrite is False");
|
||||
}
|
||||
|
||||
|
||||
|
||||
SaveMafile(data);
|
||||
}
|
||||
|
||||
@@ -100,58 +114,13 @@ public static class Storage
|
||||
public static Mafile ReadMafile(string path)
|
||||
{
|
||||
var str = File.ReadAllText(path);
|
||||
var mafile = MafileSerializer.Deserialize(str, out var mafileData);
|
||||
if (mafileData.IsExtended == false)
|
||||
throw new FormatException("Mafile is not extended data");
|
||||
|
||||
|
||||
var props = mafileData.UnusedProperties ?? new Dictionary<string, JProperty>();
|
||||
var proxy = GetPropertyValue<MaProxy>("Proxy", props);
|
||||
var group = GetPropertyValue<string>("Group", props);
|
||||
var password = GetPropertyValue<string>("Password", props);
|
||||
return Mafile.FromMobileDataExtended((MobileDataExtended)mafile, proxy, group, password);
|
||||
}
|
||||
|
||||
public static string SerializeMafile(Mafile data)
|
||||
{
|
||||
if (Settings.Instance.LegacyMode)
|
||||
{
|
||||
var props = new Dictionary<string, object?>
|
||||
{
|
||||
{nameof(Mafile.Proxy), data.Proxy},
|
||||
{nameof(Mafile.Group), data.Group},
|
||||
{nameof(Mafile.Password), data.Password}
|
||||
};
|
||||
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, props);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return MafileSerializer.Serialize(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out var prop) == false) return default;
|
||||
var value = prop.Value;
|
||||
try
|
||||
{
|
||||
return value.ToObject<T>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Warn(ex, "Can't deserialize property {name}", name);
|
||||
return default;
|
||||
}
|
||||
return NebulaSerializer.Deserialize(str);
|
||||
}
|
||||
|
||||
public static void SaveMafile(Mafile data)
|
||||
{
|
||||
|
||||
var path = GetMafilePath(data);
|
||||
var str = SerializeMafile(data);
|
||||
var path = CreatePathForMafile(data);
|
||||
var str = NebulaSerializer.SerializeMafile(data);
|
||||
File.WriteAllText(Path.GetFullPath(path), str);
|
||||
|
||||
var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
|
||||
@@ -168,24 +137,15 @@ public static class Storage
|
||||
|
||||
public static void UpdateMafile(Mafile data)
|
||||
{
|
||||
var path = GetMafilePath(data);
|
||||
var str = SerializeMafile(data);
|
||||
var path = CreatePathForMafile(data);
|
||||
var str = NebulaSerializer.SerializeMafile(data);
|
||||
File.WriteAllText(Path.GetFullPath(path), str);
|
||||
}
|
||||
|
||||
public static void RemoveMafile(Mafile data)
|
||||
{
|
||||
var path = GetMafilePath(data);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
MaFiles.Remove(data);
|
||||
}
|
||||
public static void MoveToRemoved(Mafile data)
|
||||
{
|
||||
var path = GetMafilePath(data);
|
||||
var copyPath = Path.Combine(REMOVED_F, data.SessionData!.SteamId + ".mafile");
|
||||
var path = CreatePathForMafile(data);
|
||||
var copyPath = Path.Combine(REMOVED_F, data.SteamId + ".mafile");
|
||||
var copyPathCompleted = copyPath;
|
||||
var i = 0;
|
||||
while (File.Exists(copyPathCompleted))
|
||||
@@ -197,14 +157,78 @@ public static class Storage
|
||||
File.Delete(path);
|
||||
MaFiles.Remove(data);
|
||||
}
|
||||
private static string GetMafilePath(Mafile data)
|
||||
|
||||
private static string CreatePathForMafile(Mafile data)
|
||||
{
|
||||
if (data.SessionData == null)
|
||||
throw new NullReferenceException("SessionData was null can't retrieve SteamId");
|
||||
var fileName = data.SessionData.SteamId + ".mafile";
|
||||
var fileName = Settings.Instance.UseAccountNameAsMafileName
|
||||
? CreateFileNameWithAccountName(data.AccountName)
|
||||
: CreateFileNameWithSteamId(data.SteamId);
|
||||
|
||||
return Path.Combine(MafileFolder, fileName);
|
||||
}
|
||||
|
||||
private static string CreateFileNameWithAccountName(string accountName) => accountName + ".mafile";
|
||||
private static string CreateFileNameWithSteamId(SteamId steamId) => steamId.Steam64.Id + ".mafile";
|
||||
|
||||
public static string? TryFindMafilePath(Mafile data)
|
||||
{
|
||||
var pathFileName = Path.Combine(MafileFolder, CreateFileNameWithAccountName(data.AccountName));
|
||||
string? pathSteamId = null;
|
||||
if (data.SessionData != null)
|
||||
{
|
||||
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SteamId));
|
||||
}
|
||||
|
||||
var steamIdExist = pathSteamId != null && File.Exists(pathSteamId);
|
||||
var accountNameExist = File.Exists(pathFileName);
|
||||
|
||||
if (steamIdExist && accountNameExist)
|
||||
{
|
||||
return Settings.Instance.UseAccountNameAsMafileName ? pathFileName : pathSteamId;
|
||||
}
|
||||
if (steamIdExist ^ accountNameExist)
|
||||
{
|
||||
return steamIdExist ? pathSteamId : pathFileName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Refactor
|
||||
internal class MafileNameComparer : IComparer<string>
|
||||
{
|
||||
public bool MafileNameMode { get; }
|
||||
private const string MAF_64_START = "765";
|
||||
private static readonly IComparer<string> DefaultComparer = Comparer<string>.Default;
|
||||
public MafileNameComparer(bool mafileNameMode)
|
||||
{
|
||||
MafileNameMode = mafileNameMode;
|
||||
}
|
||||
|
||||
|
||||
public int Compare(string? x, string? y)
|
||||
{
|
||||
if (x == null && y == null) return 0;
|
||||
if (x == null) return -1;
|
||||
if (y == null) return 1;
|
||||
|
||||
|
||||
bool xisSteamId = Path.GetFileName(x).StartsWith(MAF_64_START);
|
||||
bool yisSteamId = Path.GetFileName(y).StartsWith(MAF_64_START);
|
||||
|
||||
if (xisSteamId ^ yisSteamId)
|
||||
{
|
||||
if (MafileNameMode)
|
||||
{
|
||||
return xisSteamId ? 1 : -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return yisSteamId ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultComparer.Compare(x, y);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<SatelliteResourceLanguages>ru</SatelliteResourceLanguages>
|
||||
<ApplicationIcon>Theme\nebula lock.ico</ApplicationIcon>
|
||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||
<AssemblyVersion>1.4.0</AssemblyVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Theme\Background.jpg" />
|
||||
<None Remove="Theme\nebula lock.ico" />
|
||||
<None Remove="Theme\nebula.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
|
||||
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
|
||||
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
<PackageReference Include="NLog" Version="5.1.2" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Theme\Background.jpg">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Theme\nebula lock.ico" />
|
||||
<Resource Include="Theme\nebula.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Model\Utility\" />
|
||||
<Folder Include="Model\Exceptions\" />
|
||||
<Folder Include="Theme\Fonts\Новая папка\" />
|
||||
<Folder Include="ViewModel\Other\" />
|
||||
<Folder Include="Utility\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SteamLib\SteamLib\SteamLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="NLog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -8,49 +8,53 @@
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
|
||||
<ApplicationIcon>Theme\nebula lock.ico</ApplicationIcon>
|
||||
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
|
||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||
<AssemblyVersion>1.4.5</AssemblyVersion>
|
||||
<AssemblyVersion>1.5.4</AssemblyVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Theme\1140x641.jpg" />
|
||||
<None Remove="Theme\Background.jpg" />
|
||||
<None Remove="Theme\nebula lock.ico" />
|
||||
<None Remove="Theme\nebula.ico" />
|
||||
<None Remove="Theme\lock.ico" />
|
||||
<None Remove="Theme\SplashScreen.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.4" />
|
||||
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.0" />
|
||||
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.0" />
|
||||
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
|
||||
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.1" />
|
||||
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.3" />
|
||||
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.1" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
||||
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
|
||||
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.8" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Theme\Background.jpg">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Theme\nebula lock.ico" />
|
||||
<Resource Include="Theme\nebula.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Model\Exceptions\" />
|
||||
<Folder Include="ViewModel\Other\" />
|
||||
<Folder Include="Utility\" />
|
||||
<Resource Include="Theme\lock.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SplashScreen Include="Theme\SplashScreen.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="View\Dialogs\LoginAgainOnImportDialog.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="NLog.config">
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
• Есть люди которым нужна функция просмотра и завершения сессий аккаунта. Скорее всего в следующей версии это будет реализовано
|
||||
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
|
||||
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
|
||||
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
|
||||
• Ускорить появление подсказок в интерфейсе
|
||||
• Добавить запоминание пароля при привязке мафайла
|
||||
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
|
||||
• Добавить полное шифрование мафайлов по аналогии с SDA
|
||||
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
|
||||
@@ -2,8 +2,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!--Card-->
|
||||
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="#26292E"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="#26292E" />
|
||||
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1" />
|
||||
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5" />
|
||||
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1" />
|
||||
</ResourceDictionary>
|
||||
@@ -4,14 +4,14 @@ using System.Windows;
|
||||
|
||||
namespace NebulaAuth.Theme;
|
||||
|
||||
public partial class FontScaleWindow : Window
|
||||
public class FontScaleWindow : Window
|
||||
{
|
||||
|
||||
private readonly Func<double, double, double> _diagonal = (h, w) => Math.Sqrt(h * h + w * w);
|
||||
private double _currentDiagonal;
|
||||
private double _defaultDiagonal = 1;
|
||||
private readonly HashSet<FrameworkElement> _cachedObjects = new();
|
||||
private bool _loaded = false;
|
||||
private bool _loaded;
|
||||
|
||||
public FontScaleWindow()
|
||||
{
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
using System.Windows.Controls;
|
||||
namespace LolzFucker.Theme;
|
||||
|
||||
namespace LolzFucker.Theme
|
||||
public partial class Palette
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для Palette.xaml
|
||||
/// </summary>
|
||||
public partial class Palette : UserControl
|
||||
public Palette()
|
||||
{
|
||||
public Palette()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 365 KiB |
Binary file not shown.
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable IdentifierTypo
|
||||
|
||||
namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
public static class NativeMethods
|
||||
// ReSharper disable once PartialTypeWithSinglePart //Required
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
public const int WM_NCCALCSIZE = 0x83;
|
||||
public const int WM_NCPAINT = 0x85;
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable IdentifierTypo
|
||||
|
||||
namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
public static class WindowChromeHelper
|
||||
{
|
||||
public static Thickness LayoutOffsetThickness => new Thickness(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
|
||||
public static Thickness LayoutOffsetThickness => new(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the properly adjusted window resize border thickness from system parameters.
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
{
|
||||
private Window window;
|
||||
private Window? _window;
|
||||
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
@@ -23,13 +24,13 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
AssociatedObject.ContentRendered -= OnContentRendered;
|
||||
}
|
||||
|
||||
private void OnContentRendered(object sender, EventArgs e)
|
||||
private void OnContentRendered(object? sender, EventArgs e)
|
||||
{
|
||||
window = Window.GetWindow(AssociatedObject);
|
||||
_window = Window.GetWindow(AssociatedObject);
|
||||
|
||||
if (window == null) return;
|
||||
if (_window == null) return;
|
||||
|
||||
var oldWindowChrome = WindowChrome.GetWindowChrome(window);
|
||||
var oldWindowChrome = WindowChrome.GetWindowChrome(_window);
|
||||
|
||||
if (oldWindowChrome == null) return;
|
||||
|
||||
@@ -43,9 +44,9 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
UseAeroCaptionButtons = oldWindowChrome.UseAeroCaptionButtons
|
||||
};
|
||||
|
||||
WindowChrome.SetWindowChrome(window, newWindowChrome);
|
||||
WindowChrome.SetWindowChrome(_window, newWindowChrome);
|
||||
|
||||
var hWnd = new WindowInteropHelper(window).Handle;
|
||||
var hWnd = new WindowInteropHelper(_window).Handle;
|
||||
HwndSource.FromHwnd(hWnd)?.AddHook(WndProc);
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ public class WindowChromeRenderedBehavior : Behavior<Window>
|
||||
margins.rightWidth = 0;
|
||||
margins.topHeight = 0;
|
||||
|
||||
var helper = new WindowInteropHelper(window);
|
||||
var helper = new WindowInteropHelper(_window);
|
||||
|
||||
NativeMethods.DwmExtendFrameIntoClientArea(helper.Handle, ref margins);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace NebulaAuth.Theme.WindowStyle
|
||||
namespace NebulaAuth.Theme.WindowStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WindowStyle.xaml
|
||||
/// </summary>
|
||||
partial class WindowStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WindowStyle.xaml
|
||||
/// </summary>
|
||||
partial class WindowStyle
|
||||
public WindowStyle()
|
||||
{
|
||||
public WindowStyle()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnCloseClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void OnMaximizeRestoreClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = window.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;
|
||||
}
|
||||
|
||||
private void OnMinimizeClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private void OnMaximizeRestoreClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = window.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;
|
||||
}
|
||||
|
||||
private void OnMinimizeClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = (Window)((FrameworkElement)sender).TemplatedParent;
|
||||
|
||||
window.WindowState = WindowState.Minimized;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 490 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 906 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 213 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 126 KiB |
@@ -0,0 +1,60 @@
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
|
||||
namespace NebulaAuth.Utility;
|
||||
|
||||
public class ClipboardHelper
|
||||
{
|
||||
public static bool Set(string text)
|
||||
{
|
||||
var i = 0;
|
||||
while (i < 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 19)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
|
||||
}
|
||||
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool SetFiles(StringCollection files)
|
||||
{
|
||||
var i = 0;
|
||||
while (i < 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetFileDropList(files);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 19)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
|
||||
}
|
||||
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public static class ExceptionHandler
|
||||
Shell.Logger.Error(ex);
|
||||
switch (ex)
|
||||
{
|
||||
case SessionExpiredException:
|
||||
case SessionPermanentlyExpiredException:
|
||||
{
|
||||
msg = "SessionExpiredException".GetCodeBehindLocalization();
|
||||
break;
|
||||
@@ -60,7 +60,17 @@ public static class ExceptionHandler
|
||||
}
|
||||
case CantLoadConfirmationsException e:
|
||||
{
|
||||
msg = e.Message;
|
||||
msg = LocManager.GetCodeBehindOrDefault(nameof(CantLoadConfirmationsException), EXCEPTION_HANDLER_LOC_PATH, nameof(CantLoadConfirmationsException), "Common");
|
||||
if (e.Error == LoadConfirmationsError.Unknown)
|
||||
{
|
||||
msg += e.ErrorMessage;
|
||||
msg += ' ';
|
||||
msg += e.ErrorDetails;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += LocManager.GetCodeBehindOrDefault(e.Error.ToString(), EXCEPTION_HANDLER_LOC_PATH, nameof(CantLoadConfirmationsException), e.Error.ToString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EResultException e:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
xmlns:converters="clr-namespace:NebulaAuth.Converters"
|
||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
||||
>
|
||||
xmlns:vm="clr-namespace:NebulaAuth.ViewModel">
|
||||
|
||||
|
||||
<DataTemplate DataType="{x:Type confirmations:Confirmation}">
|
||||
@@ -18,15 +18,15 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="QuestionMark" Margin="0,0,10,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Binding TypeName}"></TextBlock>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Binding TypeName}" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -43,21 +43,21 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0"></materialDesign:PackIcon>
|
||||
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0"></Image>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0" />
|
||||
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" TextWrapping="WrapWithOverflow" >
|
||||
<Run Text="{Tr MainWindow.ConfirmationTemplates.TradeWith, IsDynamic=False}"/>
|
||||
<Run Text="{Binding UserName}" FontWeight="Bold"></Run>
|
||||
<Run Text="{Binding UserName}" FontWeight="Bold" />
|
||||
</TextBlock>
|
||||
<materialDesign:PackIcon Grid.Column="3" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Kind="Gift" Visibility="{Binding IsReceiveNothing, Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -76,12 +76,12 @@
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.Recovery}"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -98,14 +98,14 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0" />
|
||||
<Border Grid.Column="1" Background="{DynamicResource MaterialDesignPaper}" MaxHeight="36" HorizontalAlignment="Center" BorderBrush="{DynamicResource PrimaryHueMidBrush}" BorderThickness="0.6" Margin="0,0,10,0">
|
||||
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" ></Image>
|
||||
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" />
|
||||
</Border>
|
||||
<Grid Grid.Column="2">
|
||||
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True" TextWrapping="WrapWithOverflow" Text="{Binding ItemName}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Foreground="LightGray" Text="{Binding PriceString}">
|
||||
<TextBlock Foreground="LightGray" Text="{Binding PriceString}">
|
||||
<TextBlock.FontSize>
|
||||
<Binding ElementName="ItemName" Path="FontSize" ConverterParameter="0.7">
|
||||
<Binding.Converter>
|
||||
@@ -118,12 +118,12 @@
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -139,15 +139,15 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="KeyLink" Margin="0,0,10,0"/>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}"></TextBlock>
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
@@ -162,7 +162,7 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0" />
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="1">
|
||||
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market, IsDynamic=False}"/>
|
||||
<Run Text="{Binding Confirmations.Count, Mode=OneWay}"/>
|
||||
@@ -171,12 +171,12 @@
|
||||
|
||||
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
|
||||
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</Button>
|
||||
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
|
||||
CommandParameter="{Binding }">
|
||||
<materialDesign:PackIcon Kind="Close" />
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="100" d:DesignWidth="400"
|
||||
Height="Auto" Width="Auto" MaxWidth="500"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
<Grid Margin="15">
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ConfirmCancelDialog.xaml
|
||||
/// </summary>
|
||||
public partial class ConfirmCancelDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ConfirmCancelDialog.xaml
|
||||
/// </summary>
|
||||
public partial class ConfirmCancelDialog : UserControl
|
||||
public ConfirmCancelDialog()
|
||||
{
|
||||
public ConfirmCancelDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ConfirmCancelDialog(string msg)
|
||||
{
|
||||
InitializeComponent();
|
||||
ConfirmTextBlock.Text = msg;
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public ConfirmCancelDialog(string msg)
|
||||
{
|
||||
InitializeComponent();
|
||||
ConfirmTextBlock.Text = msg;
|
||||
}
|
||||
}
|
||||
@@ -8,32 +8,31 @@
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="250" d:DesignWidth="800"
|
||||
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
|
||||
Foreground="WhiteSmoke" Cursor="Hand"
|
||||
Foreground="WhiteSmoke"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
|
||||
<Grid MinHeight="100" MinWidth="300" Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left">
|
||||
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
|
||||
<Run FontWeight="Bold" Text="{Binding UserName}"/>
|
||||
</TextBlock>
|
||||
<TextBox Text="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
|
||||
<CheckBox IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Margin="10" Grid.Row="2" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
|
||||
<Grid Grid.Row="3">
|
||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
|
||||
<CheckBox Grid.Row="2" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
|
||||
<Grid Grid.Row="3" Margin="10,10,10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button IsDefault="True" Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
|
||||
<Button IsCancel="True" Grid.Column="1" Margin="5,5,10,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}"/>
|
||||
<Button IsDefault="True" IsEnabled="{Binding IsFormValid}" Margin="0,0,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
|
||||
<Button IsCancel="True" Grid.Column="1" Margin="5,0,0,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainDialog : UserControl
|
||||
public LoginAgainDialog()
|
||||
{
|
||||
public LoginAgainDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<UserControl x:Class="NebulaAuth.View.Dialogs.LoginAgainOnImportDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
||||
mc:Ignorable="d"
|
||||
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
|
||||
Foreground="WhiteSmoke"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainOnImportVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
|
||||
<Grid MinHeight="100" MinWidth="300" Margin="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left">
|
||||
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
|
||||
<Run FontWeight="Bold" Text="{Binding UserName}"/>
|
||||
</TextBlock>
|
||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
|
||||
<ComboBox ToolTip="{Tr LoginAgainDialog.ProxyToolTip}" Grid.Row="2" Margin="10,18,10,0" materialDesign:HintAssist.Hint="{Tr Common.Proxy}" ItemsSource="{Binding Proxies}" SelectedItem="{Binding SelectedProxy}" >
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type entities:MaProxy}">
|
||||
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<UIElement.InputBindings>
|
||||
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
|
||||
</UIElement.InputBindings>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Row="3" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
|
||||
<CheckBox Grid.Row="4" Margin="10,10,10,0" IsEnabled="{Binding MafileHasProxy}" Content="{Tr LoginAgainDialog.UseMafileProxy}" IsChecked="{Binding UseMafileProxy}"/>
|
||||
<Grid Grid.Row="5" Margin="10,10,10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button IsEnabled="{Binding IsFormValid}" IsDefault="True" Margin="0,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
|
||||
<Button IsCancel="True" Grid.Column="1" Margin="5,5,0,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainOnImportDialog
|
||||
{
|
||||
public LoginAgainOnImportDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,12 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NebulaAuth.View.Dialogs"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
Foreground="WhiteSmoke" Cursor="Hand"
|
||||
Foreground="WhiteSmoke"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
|
||||
@@ -21,13 +19,13 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal" Text="{Tr SetEncryptedPasswordDialog.DialogText}" theme:FontScaleWindow.Scale="0.8" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left"/>
|
||||
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}"></PasswordBox>
|
||||
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}" />
|
||||
<Grid Grid.Row="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr SetEncryptedPasswordDialog.Ok}"/>
|
||||
<Button IsDefault="True" Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr SetEncryptedPasswordDialog.Ok}"/>
|
||||
<Button IsCancel="True" Grid.Column="1" Margin="5,5,10,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr SetEncryptedPasswordDialog.Cancel}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
|
||||
/// </summary>
|
||||
public partial class SetCryptPasswordDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
|
||||
/// </summary>
|
||||
public partial class SetCryptPasswordDialog : UserControl
|
||||
public SetCryptPasswordDialog()
|
||||
{
|
||||
public SetCryptPasswordDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
theme:FontScaleWindow.ResizeFont="True" theme:FontScaleWindow.Scale="1"
|
||||
Foreground="WhiteSmoke"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
@@ -19,8 +18,8 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" IsIndeterminate="True"></ProgressBar>
|
||||
<TextBlock Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}" VerticalAlignment="Center"></TextBlock>
|
||||
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" IsIndeterminate="True" />
|
||||
<TextBlock Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}" VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<Grid x:Name="CaptchaGrid" Margin="0,25,0,0" Visibility="Collapsed" Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -28,7 +27,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image x:Name="CaptchaImage" Stretch="Uniform"></Image>
|
||||
<Image x:Name="CaptchaImage" Stretch="Uniform" />
|
||||
<TextBox Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" x:Name="CaptchaTB"/>
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
|
||||
@@ -7,66 +7,65 @@ using System.Windows.Media.Imaging;
|
||||
using SteamLib.Core.Interfaces;
|
||||
using SteamLib.Exceptions;
|
||||
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WaitLoginDialog.xaml
|
||||
/// </summary>
|
||||
public partial class WaitLoginDialog : ICaptchaResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для WaitLoginDialog.xaml
|
||||
/// </summary>
|
||||
public partial class WaitLoginDialog : ICaptchaResolver
|
||||
private TaskCompletionSource<string> _tcs = new();
|
||||
public WaitLoginDialog()
|
||||
{
|
||||
private TaskCompletionSource<string> _tcs = new();
|
||||
public WaitLoginDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public async Task<string> Resolve(Uri imageUrl, HttpClient client)
|
||||
{
|
||||
|
||||
CaptchaGrid.Visibility = Visibility.Visible;
|
||||
var stream = await client.GetStreamAsync(imageUrl);
|
||||
return await Application.Current.Dispatcher.Invoke(async () =>
|
||||
{
|
||||
var image = await LoadImage(stream);
|
||||
CaptchaImage.Source = image;
|
||||
try
|
||||
{
|
||||
return await _tcs.Task;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw new LoginException(LoginError.CaptchaRequired);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<BitmapImage> LoadImage(Stream stream)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
var image = new BitmapImage();
|
||||
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = ms;
|
||||
image.EndInit();
|
||||
await stream.DisposeAsync();
|
||||
return image;
|
||||
}
|
||||
|
||||
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_tcs.SetCanceled();
|
||||
}
|
||||
|
||||
private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return;
|
||||
var oldTcs = _tcs;
|
||||
_tcs = new TaskCompletionSource<string>();
|
||||
oldTcs.SetResult(CaptchaTB.Text);
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> Resolve(Uri imageUrl, HttpClient client)
|
||||
{
|
||||
|
||||
CaptchaGrid.Visibility = Visibility.Visible;
|
||||
var stream = await client.GetStreamAsync(imageUrl);
|
||||
return await Application.Current.Dispatcher.Invoke(async () =>
|
||||
{
|
||||
var image = await LoadImage(stream);
|
||||
CaptchaImage.Source = image;
|
||||
try
|
||||
{
|
||||
return await _tcs.Task;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw new LoginException(LoginError.CaptchaRequired);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<BitmapImage> LoadImage(Stream stream)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
var image = new BitmapImage();
|
||||
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
image.StreamSource = ms;
|
||||
image.EndInit();
|
||||
await stream.DisposeAsync();
|
||||
return image;
|
||||
}
|
||||
|
||||
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_tcs.SetCanceled();
|
||||
}
|
||||
|
||||
private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return;
|
||||
var oldTcs = _tcs;
|
||||
_tcs = new TaskCompletionSource<string>();
|
||||
oldTcs.SetResult(CaptchaTB.Text);
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,10 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NebulaAuth.View"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
MinHeight="640"
|
||||
@@ -63,12 +61,19 @@
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}"/>
|
||||
<TextBlock Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12" >
|
||||
<Hyperlink Command="{Binding OpenTroubleshootingCommand}">
|
||||
<Run Text="{Tr LinkerDialog.GotErrorHyperlinkText, IsDynamic=False}" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Background="DarkGray" Grid.Row="1"></Separator>
|
||||
<Separator Background="DarkGray" Grid.Row="1" />
|
||||
<Grid Grid.Row="2" Margin="10,20,10,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@@ -86,48 +91,58 @@
|
||||
</ComboBox>
|
||||
<Button Grid.Column="1" IsEnabled="{Binding IsPasswordFieldVisible}" Margin="5,0,0,0" Command="{Binding ResetProxyCommand}" Content="{materialDesign:PackIcon Trash}"/>
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="10" Tag="{Binding IsLogin}">
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="7" Tag="{Binding IsLogin}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.Authorization}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Authorization}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="10" Tag="{Binding IsEmailCode}">
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailCode}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.EmailCode}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailCode}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="10" Tag="{Binding IsPhoneNumber}">
|
||||
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="7" Tag="{Binding IsPhoneNumber}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.PhoneNumber}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.PhoneNumber}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="10" Tag="{Binding IsEmailConfirmation}">
|
||||
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailConfirmation}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.EmailLink}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailLink}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="7" Orientation="Horizontal" Margin="10" Tag="{Binding IsLinkCode}">
|
||||
<StackPanel Grid.Row="7" Orientation="Horizontal" Margin="7" Tag="{Binding IsLinkCode}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.SmsOrCode}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.SmsOrCode}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="8" Orientation="Horizontal" Margin="10" Tag="{Binding IsCompleted}">
|
||||
<StackPanel Grid.Row="8" Orientation="Horizontal" Margin="7" Tag="{Binding IsCompleted}">
|
||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
|
||||
<TextBlock Text="{Tr LinkerDialog.Completed}"/>
|
||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Completed}"/>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="9" Margin="10">
|
||||
<Grid Grid.Row="9" Margin="10,20,10,10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Padding="10" Text="{Binding FieldText}" Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10"></TextBox>
|
||||
<TextBox Grid.Row="1" Padding="10" Text="{Binding PassFieldText}" Visibility="{Binding IsPasswordFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16"/>
|
||||
<TextBox Padding="7" Text="{Binding FieldText}" Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10" />
|
||||
<TextBox Grid.Row="1" Padding="7" Text="{Binding PassFieldText}" Visibility="{Binding IsPasswordFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16"/>
|
||||
<GroupBox Grid.Row="2" MaxWidth="400" BorderBrush="{StaticResource PrimaryHueMidBrush}" VerticalAlignment="Bottom" BorderThickness="1" Margin="0,10,0,0" Padding="5">
|
||||
<GroupBox.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Tr LinkerDialog.Message}" FontSize="16" Foreground="GhostWhite"/>
|
||||
</DataTemplate>
|
||||
</GroupBox.HeaderTemplate>
|
||||
<TextBlock Foreground="GhostWhite" FontSize="16" Text="{Binding HintText}" HorizontalAlignment="Stretch" TextWrapping="Wrap"/>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Foreground="GhostWhite" FontSize="16" Text="{Binding HintText}" HorizontalAlignment="Stretch" TextWrapping="Wrap"/>
|
||||
<Button Command="{Binding CopyCodeCommand}" Visibility="{Binding IsCompleted, Converter={StaticResource BooleanToVisibilityConverter}}" Grid.Row="1">
|
||||
<materialDesign:PackIcon Kind="ContentCopy" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<Button Grid.Row="10" FontSize="18" Command="{Binding ProceedCommand}" Content="{Tr LinkerDialog.ProceedButton}"/>
|
||||
<Button Grid.Row="10" Height="35" FontSize="17" Command="{Binding ProceedCommand}" Content="{Tr LinkerDialog.ProceedButton}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LinkerView.xaml
|
||||
/// </summary>
|
||||
public partial class LinkerView
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LinkerView.xaml
|
||||
/// </summary>
|
||||
public partial class LinkerView : UserControl
|
||||
public LinkerView()
|
||||
{
|
||||
public LinkerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,11 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:NebulaAuth.View"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DesignHeight="500" d:DesignWidth="400"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{md:MaterialDesignFont}"
|
||||
MinHeight="500"
|
||||
@@ -15,13 +14,7 @@
|
||||
MaxHeight="550"
|
||||
d:DataContext="{d:DesignInstance other:ProxyManagerVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
<d:DesignerProperties.DesignStyle>
|
||||
<Style TargetType="UserControl">
|
||||
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
|
||||
</Style>
|
||||
</d:DesignerProperties.DesignStyle>
|
||||
|
||||
<Grid Margin="15,10,15,15">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -29,39 +22,39 @@
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}"/>
|
||||
<Button 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>
|
||||
</Button>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Margin="10" FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}"/>
|
||||
<Button Margin="0,0,10,0" IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static md:DialogHost.CloseDialogCommand}">
|
||||
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
<Separator Grid.Row="1"></Separator>
|
||||
<Separator Grid.Row="1" />
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Stretch"
|
||||
Margin="15" FontSize="16">
|
||||
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}"/>
|
||||
<Run Text="{Binding DefaultProxy.Key, StringFormat='
0:', FallbackValue='X', Mode=OneWay}"/>
|
||||
<Run Text="{Binding DefaultProxy.Value.Address, Mode=OneWay, FallbackValue=''}"/>
|
||||
<Run Text="{Binding DefaultProxy.Key, StringFormat='
0:', FallbackValue='-', Mode=OneWay}"/>
|
||||
<Run Text="{Binding DefaultProxy.Value, Converter='{StaticResource ProxyDataTextConverter}', Mode=OneWay, FallbackValue=''}"/>
|
||||
</TextBlock>
|
||||
<Button Grid.Column="1" Command="{Binding SetDefaultCommand}">
|
||||
<md:PackIcon Kind="HeartBoxOutline" Width="20" Height="20"></md:PackIcon>
|
||||
</Button>
|
||||
<Button Grid.Column="2" Command="{Binding RemoveDefaultCommand}" Cursor="Hand">
|
||||
<md:PackIcon Kind="ClearBox" Width="20" Height="20"></md:PackIcon>
|
||||
|
||||
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
|
||||
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<md:Card Grid.Row="3" Margin="10">
|
||||
<ListBox SelectedValue="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
|
||||
<ListBox VirtualizingStackPanel.VirtualizationMode="Recycling" FontSize="14" SelectedValue="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
|
||||
<ListBox.InputBindings>
|
||||
<KeyBinding Key="Delete" Command="{Binding DataContext.RemoveProxyCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
</ListBox.InputBindings>
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
|
||||
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DataContext}"/>
|
||||
@@ -77,33 +70,48 @@
|
||||
</ContextMenu>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
|
||||
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock>
|
||||
<Run Text="{Binding Key, Mode=OneWay}"/><Run Text=": "/>
|
||||
<Run Text="{Binding Value.Address, Mode=OneWay}"/>
|
||||
<Grid >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<Run Text="{Binding Key, Mode=OneWay}"/><Run Text=": "/>
|
||||
<Run Text="{Binding Value, Mode=OneWay, Converter={StaticResource ProxyDataTextConverter}}"/>
|
||||
|
||||
</TextBlock>
|
||||
</TextBlock>
|
||||
<Button Style="{StaticResource MaterialDesignIconButton}" Padding="0" Width="24" Height="24" md:RippleAssist.IsDisabled="True" Grid.Column="1" HorizontalAlignment="Right"
|
||||
Command="{Binding DataContext.SetDefaultCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<md:PackIcon Height="16" Width="16" Kind="Heart" />
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</md:Card>
|
||||
<Grid Grid.Row="4">
|
||||
<Grid Grid.Row="4" Margin="5,0,15,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True" AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}" Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}"></TextBox>
|
||||
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True" AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}" Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}" />
|
||||
<Button IsDefault="True" Grid.Column="1" Command="{Binding AddProxyCommand}">
|
||||
<md:PackIcon Kind="Add"></md:PackIcon>
|
||||
<md:PackIcon Kind="Add" />
|
||||
</Button>
|
||||
<Button Grid.Column="2" Command="{Binding RemoveProxyCommand}">
|
||||
<md:PackIcon Kind="Trash"></md:PackIcon>
|
||||
<md:PackIcon Kind="Trash" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ProxyManagerView.xaml
|
||||
/// </summary>
|
||||
public partial class ProxyManagerView
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ProxyManagerView.xaml
|
||||
/// </summary>
|
||||
public partial class ProxyManagerView : UserControl
|
||||
public ProxyManagerView()
|
||||
{
|
||||
public ProxyManagerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,20 +6,15 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignHeight="650"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
MinHeight="300"
|
||||
MinHeight="600"
|
||||
MinWidth="400"
|
||||
|
||||
MaxWidth="200"
|
||||
d:DataContext="{d:DesignInstance other:SettingsVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
<d:DesignerProperties.DesignStyle>
|
||||
<Style TargetType="UserControl">
|
||||
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
|
||||
</Style>
|
||||
</d:DesignerProperties.DesignStyle>
|
||||
<Grid>
|
||||
<Grid Margin="15,10,15,15">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -35,19 +30,18 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr SettingsDialog.Title}"/>
|
||||
<Button IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></materialDesign:PackIcon>
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Margin="0,10,0,0" />
|
||||
|
||||
<StackPanel Grid.Row="2" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" ></ComboBox>
|
||||
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" ></ComboBox>
|
||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding DisableTimersOnChange}" Content="{Tr SettingsDialog.DisableTimersOnSwitch}"/>
|
||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
|
||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}"/>
|
||||
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" />
|
||||
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" />
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}" ToolTip="{Tr SettingsDialog.UseIndicatorHint}"/>
|
||||
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
|
||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding UseBackground}" Content="{Tr SettingsDialog.UseCustomColor}"/>
|
||||
<CheckBox Margin="0,10,0,5" FontSize="16" IsChecked="{Binding UseBackground}" Content="{Tr SettingsDialog.UseCustomColor}"/>
|
||||
|
||||
<materialDesign:ColorPicker Height="100" IsEnabled="{Binding UseBackground}" Color="{Binding BackgroundColor, Delay=50}" />
|
||||
<Grid>
|
||||
@@ -58,13 +52,17 @@
|
||||
<PasswordBox MinWidth="250" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Height="Auto" VerticalAlignment="Center" FontSize="16" Margin="0,10,0,0"
|
||||
Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
|
||||
materialDesign:HintAssist.Hint="{Tr SettingsDialog.PasswordBox.CurrentCryptPassword}"
|
||||
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}"></PasswordBox>
|
||||
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" />
|
||||
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom" Grid.Column="1"> <materialDesign:PackIcon Kind="ContentSave"/></Button>
|
||||
</Grid>
|
||||
<CheckBox Margin="0,25,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 AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
|
||||
|
||||
|
||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding LegacyMode}" Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}"/>
|
||||
<!--<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>-->
|
||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseAccountNameAsMafileName}" >
|
||||
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding IgnorePatchTuesdayErrors}" Margin="0,10,0,0" FontSize="16" ToolTip="{Tr SettingsDialog.IgnorePatchTuesdayErrorsHint}">
|
||||
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.IgnorePatchTuesdayErrors}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class SettingsView
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class SettingsView : UserControl
|
||||
public SettingsView()
|
||||
{
|
||||
public SettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ColorPicker_OnColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
|
||||
{
|
||||
Debug.WriteLine(e.NewValue);
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<UserControl x:Class="NebulaAuth.View.UpdaterView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
mc:Ignorable="d"
|
||||
Foreground="WhiteSmoke"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
d:DataContext="{d:DesignInstance other:UpdaterVM}"
|
||||
Background="{DynamicResource WindowBackground}"
|
||||
Padding="10"
|
||||
MaxWidth="500">
|
||||
<d:DesignerProperties.DesignStyle>
|
||||
<Style TargetType="UserControl">
|
||||
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
|
||||
</Style>
|
||||
</d:DesignerProperties.DesignStyle>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock FontSize="24" Text="Обновления"/>
|
||||
<Separator Grid.Row="1" />
|
||||
|
||||
<TextBlock FontSize="16" Margin="5,10,0,10" TextWrapping="WrapWithOverflow" Grid.Row="2" >
|
||||
<Run Text="Доступна новая версия:"/>
|
||||
<Run FontWeight="Bold" Text="{Binding UpdateInfoEventArgs.CurrentVersion}"/>
|
||||
<Run Text="
Вы используете версию"/>
|
||||
<Run FontWeight="Bold" Text="{Binding UpdateInfoEventArgs.InstalledVersion}"/>
|
||||
<Run Text="Хотите обновить программу?"/>
|
||||
</TextBlock>
|
||||
<Expander Header="Что изменилось?" Grid.Row="3">
|
||||
<!--<wpf:WebView2 HorizontalAlignment="Stretch" Height="300"
|
||||
Source="{Binding UpdateInfoEventArgs.ChangelogURL}"
|
||||
/>-->
|
||||
</Expander>
|
||||
<Grid Grid.Row="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Content="OK" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" />
|
||||
<Button IsCancel="True" Grid.Column="1" Content="Cancel" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для UpdaterView.xaml
|
||||
/// </summary>
|
||||
public partial class UpdaterView
|
||||
{
|
||||
public UpdaterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using JetBrains.Annotations;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
using SteamLib.Exceptions;
|
||||
using SteamLib.SteamMobile;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -21,8 +21,11 @@ public partial class MainVM : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Mafile> _maFiles = Storage.MaFiles;
|
||||
|
||||
[UsedImplicitly]
|
||||
public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
|
||||
|
||||
|
||||
public Mafile? SelectedMafile
|
||||
{
|
||||
get => _selectedMafile;
|
||||
@@ -30,20 +33,25 @@ public partial class MainVM : ObservableObject
|
||||
}
|
||||
private Mafile? _selectedMafile;
|
||||
|
||||
public bool IsMafileSelected => SelectedMafile != null;
|
||||
|
||||
|
||||
public MainVM()
|
||||
{
|
||||
CreateCodeTimer();
|
||||
_confirmTimer = new Timer(ConfirmByTimer, null, TimeSpan.FromSeconds(_timerCheckSeconds), TimeSpan.FromSeconds(_timerCheckSeconds));
|
||||
Proxies = new ObservableCollection<MaProxy>(ProxyStorage.Proxies.Select(kvp =>
|
||||
new MaProxy(kvp.Key, kvp.Value)));
|
||||
Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged;
|
||||
QueryGroups();
|
||||
SessionHandler.LoginStarted += SessionHandlerOnLoginStarted;
|
||||
SessionHandler.LoginCompleted += SessionHandlerOnLoginCompleted;
|
||||
UpdateManager.CheckForUpdates();
|
||||
if (Storage.DuplicateFound > 0)
|
||||
{
|
||||
SnackbarController.SendSnackbar(
|
||||
GetLocalization("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
|
||||
TimeSpan.FromSeconds(4));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void SetMafile(Mafile? mafile)
|
||||
{
|
||||
@@ -51,12 +59,14 @@ public partial class MainVM : ObservableObject
|
||||
{
|
||||
_selectedMafile = mafile;
|
||||
OnPropertyChanged(nameof(SelectedMafile));
|
||||
OnPropertyChanged(nameof(TradeTimerEnabled));
|
||||
OnPropertyChanged(nameof(MarketTimerEnabled));
|
||||
MaClient.SetAccount(mafile);
|
||||
OnPropertyChanged(nameof(ConfirmationsVisible));
|
||||
if (Settings.DisableTimersOnChange) OffTimer(dispatcher: false);
|
||||
SetCurrentProxy();
|
||||
OnPropertyChanged(nameof(IsDefaultProxy));
|
||||
if (mafile != null) Code = SteamGuardCodeGenerator.GenerateCode(mafile.SharedSecret);
|
||||
OnPropertyChanged(nameof(IsMafileSelected));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +83,14 @@ public partial class MainVM : ObservableObject
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var password = loginAgainVm.Password;
|
||||
var waitDialog = new WaitLoginDialog();
|
||||
var wait = DialogHost.Show(waitDialog);
|
||||
try
|
||||
{
|
||||
await MaClient.LoginAgain(SelectedMafile, password, loginAgainVm.SavePassword, waitDialog);
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
|
||||
}
|
||||
catch (LoginException ex)
|
||||
{
|
||||
@@ -109,7 +120,7 @@ public partial class MainVM : ObservableObject
|
||||
try
|
||||
{
|
||||
await MaClient.RefreshSession(SelectedMafile);
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SessionRefreshed"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("SessionRefreshed"));
|
||||
}
|
||||
catch (Exception ex) when (ExceptionHandler.Handle(ex))
|
||||
{
|
||||
@@ -120,7 +131,6 @@ public partial class MainVM : ObservableObject
|
||||
[RelayCommand]
|
||||
public async Task LinkAccount()
|
||||
{
|
||||
OffTimer(false);
|
||||
await DialogsController.ShowLinkerDialog();
|
||||
}
|
||||
|
||||
@@ -131,16 +141,16 @@ public partial class MainVM : ObservableObject
|
||||
if (selectedMafile == null) return;
|
||||
if (string.IsNullOrWhiteSpace(selectedMafile.RevocationCode))
|
||||
{
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalizationOrDefault("MissingRCode"));
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalization("MissingRCode"));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmRemovingAuthenticator")))
|
||||
if (await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmRemovingAuthenticator")))
|
||||
{
|
||||
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(SelectedMafile), SelectedMafile);
|
||||
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(selectedMafile), selectedMafile);
|
||||
SnackbarController.SendSnackbar(
|
||||
result.Success ? GetLocalizationOrDefault("AuthenticatorRemoved") : GetLocalizationOrDefault("AuthenticatorNotRemoved"));
|
||||
result.Success ? GetLocalization("AuthenticatorRemoved") : GetLocalization("AuthenticatorNotRemoved"));
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
@@ -167,17 +177,17 @@ public partial class MainVM : ObservableObject
|
||||
|
||||
try
|
||||
{
|
||||
LoginConfirmationResult res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
|
||||
var res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
|
||||
if (res.Success)
|
||||
{
|
||||
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
|
||||
SnackbarController.SendSnackbar($"{GetLocalization("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
|
||||
}
|
||||
else
|
||||
{
|
||||
string msg = res.Error switch
|
||||
{
|
||||
LoginConfirmationError.NoRequests => GetLocalizationOrDefault("ConfirmLoginFailedNoRequests"),
|
||||
LoginConfirmationError.MoreThanOneRequest => GetLocalizationOrDefault("ConfirmLoginFailedMoreThanOneRequest"), //TODO
|
||||
LoginConfirmationError.NoRequests => GetLocalization("ConfirmLoginFailedNoRequests"),
|
||||
LoginConfirmationError.MoreThanOneRequest => GetLocalization("ConfirmLoginFailedMoreThanOneRequest"), //TODO
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
SnackbarController.SendSnackbar(msg);
|
||||
@@ -189,4 +199,11 @@ public partial class MainVM : ObservableObject
|
||||
Shell.Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string GetLocalization(string key)
|
||||
{
|
||||
const string locPath = "MainVM";
|
||||
return LocManager.GetCodeBehindOrDefault(key, locPath, key);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using SteamLib.SteamMobile;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -10,34 +16,60 @@ public partial class MainVM
|
||||
{
|
||||
private Timer _codeTimer;
|
||||
[ObservableProperty] private double _codeProgress;
|
||||
[ObservableProperty] private string _code;
|
||||
[ObservableProperty] private string _code = "Code";
|
||||
|
||||
|
||||
[MemberNotNull(nameof(_codeTimer))]
|
||||
[MemberNotNull(nameof(_code))]
|
||||
private void CreateCodeTimer()
|
||||
{
|
||||
var currentTime = TimeAligner.GetSteamTime();
|
||||
_codeTimer = new Timer(UpdateCode, null, 0, 1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void UpdateCode(object? state = null)
|
||||
{
|
||||
var currentTime = TimeAligner.GetSteamTime();
|
||||
var untilChange = currentTime - currentTime / 30L * 30L;
|
||||
var untilChange = currentTime % 30;
|
||||
var codeProgress = untilChange / 30D * 100;
|
||||
|
||||
|
||||
string? code = null;
|
||||
if (untilChange == 0 && SelectedMafile != null)
|
||||
{
|
||||
code = SteamGuardCodeGenerator.GenerateCode(SelectedMafile!.SharedSecret);
|
||||
}
|
||||
|
||||
if(Application.Current == null) return;
|
||||
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
|
||||
if (Application.Current == null) return;
|
||||
Application.Current.Dispatcher.BeginInvoke((string? c) =>
|
||||
{
|
||||
if (Application.Current.MainWindow?.WindowState == WindowState.Minimized) return;
|
||||
CodeProgress = codeProgress;
|
||||
if (code != null) Code = code;
|
||||
});
|
||||
if (c != null) Code = c;
|
||||
}, DispatcherPriority.DataBind, code);
|
||||
}
|
||||
|
||||
[RelayCommand(AllowConcurrentExecutions = false)]
|
||||
private async Task CopyCode()
|
||||
{
|
||||
var selectedMafile = SelectedMafile;
|
||||
if (selectedMafile == null) return;
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(Code);
|
||||
Code = LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(200);
|
||||
selectedMafile = SelectedMafile;
|
||||
if (selectedMafile != null)
|
||||
Code = SteamGuardCodeGenerator.GenerateCode(selectedMafile.SharedSecret);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public partial class MainVM //Confirmations
|
||||
public bool ConfirmationsVisible => SelectedMafile == _confirmationsLoadedForMafile;
|
||||
|
||||
[RelayCommand]
|
||||
public async Task GetConfirmations()
|
||||
private async Task GetConfirmations()
|
||||
{
|
||||
if (SelectedMafile == null) return;
|
||||
var maf = SelectedMafile;
|
||||
@@ -40,7 +40,10 @@ public partial class MainVM //Confirmations
|
||||
_confirmationsLoadedForMafile = maf;
|
||||
OnPropertyChanged(nameof(ConfirmationsVisible));
|
||||
Confirmations.Clear();
|
||||
var marketConfirmations = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction).ToList();
|
||||
var marketConfirmations = conf
|
||||
.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction)
|
||||
.Cast<MarketConfirmation>()
|
||||
.ToList();
|
||||
|
||||
if (marketConfirmations.Count > 1)
|
||||
{
|
||||
@@ -50,7 +53,7 @@ public partial class MainVM //Confirmations
|
||||
conf.Remove(mCon);
|
||||
}
|
||||
|
||||
var mConf = new MarketMultiConfirmation(marketConfirmations.Cast<MarketConfirmation>());
|
||||
var mConf = new MarketMultiConfirmation(marketConfirmations);
|
||||
conf.Insert(indexOfLast, mConf);
|
||||
}
|
||||
|
||||
@@ -60,12 +63,25 @@ public partial class MainVM //Confirmations
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task Confirm(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, true);
|
||||
}
|
||||
[RelayCommand]
|
||||
private Task Cancel(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, false);
|
||||
}
|
||||
|
||||
private async Task SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
|
||||
{
|
||||
bool result;
|
||||
try
|
||||
{
|
||||
if (confirmation is MarketMultiConfirmation multi)
|
||||
if (confirmation is MarketMultiConfirmation multi)
|
||||
{
|
||||
result = await MaClient.SendMultipleConfirmation(mafile, multi.Confirmations, confirm);
|
||||
}
|
||||
@@ -86,20 +102,7 @@ public partial class MainVM //Confirmations
|
||||
}
|
||||
else
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("ConfirmationError"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("ConfirmationError"));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task Confirm(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, true);
|
||||
}
|
||||
[RelayCommand]
|
||||
private Task Cancel(Confirmation? confirmation)
|
||||
{
|
||||
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
|
||||
return SendConfirmation(SelectedMafile, confirmation, false);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,10 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using SteamLib.Exceptions;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -25,19 +28,29 @@ public partial class MainVM //File //TODO: Refactor
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenMafileFolder()
|
||||
{ var mafile = SelectedMafile;
|
||||
|
||||
{
|
||||
var mafile = SelectedMafile;
|
||||
|
||||
var path = Storage.MafileFolder;
|
||||
if (mafile?.SessionData != null)
|
||||
string? mafilePath = null;
|
||||
if (mafile != null)
|
||||
{
|
||||
var mafPath = Path.Combine(Storage.MafileFolder, mafile.SessionData.SteamId.Steam64 + ".maFile");
|
||||
if (File.Exists(mafPath))
|
||||
{
|
||||
path = $"/e, /select, \"{mafPath}\""; ;
|
||||
}
|
||||
mafilePath = Storage.TryFindMafilePath(mafile);
|
||||
}
|
||||
if (mafilePath != null)
|
||||
{
|
||||
path = $"/select, \"{mafilePath}\"";
|
||||
}
|
||||
|
||||
Process.Start("explorer", path);
|
||||
try
|
||||
{
|
||||
var processStartInfo = new ProcessStartInfo("explorer.exe", path);
|
||||
Process.Start(processStartInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SnackbarController.SendSnackbar(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +66,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
var fs = openFileDialog.ShowDialog();
|
||||
if (fs != true) return Task.CompletedTask;
|
||||
var path = openFileDialog.FileName;
|
||||
return AddMafile(new[] { path });
|
||||
return AddMafile([path]);
|
||||
|
||||
}
|
||||
public async Task AddMafile(string[] path)
|
||||
@@ -75,7 +88,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmMafileOverwrite"));
|
||||
confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmMafileOverwrite"));
|
||||
|
||||
if (confirmOverwrite == true)
|
||||
{
|
||||
@@ -87,11 +100,11 @@ public partial class MainVM //File //TODO: Refactor
|
||||
notAdded++;
|
||||
}
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
catch (MafileNeedReloginException ex)
|
||||
{
|
||||
if (path.Length == 1 && ex.Data.Contains("mafile"))
|
||||
if (path.Length == 1 && ex.Mafile != null)
|
||||
{
|
||||
var mafile = (Mafile)ex.Data["mafile"]!;
|
||||
var mafile = ex.Mafile;
|
||||
if (await HandleAddMafileWithoutSession(mafile))
|
||||
{
|
||||
added++;
|
||||
@@ -103,48 +116,70 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
else
|
||||
{
|
||||
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("MafileImportError")} {Path.GetFileName(str)}{GetLocalizationOrDefault("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
|
||||
SnackbarController.SendSnackbar($"{GetLocalization("MafileImportError")} {Path.GetFileName(str)}{GetLocalization("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var msg = GetLocalizationOrDefault("Import");
|
||||
var msg = GetLocalization("Import");
|
||||
if (added > 0)
|
||||
{
|
||||
msg += $" {GetLocalizationOrDefault("ImportAdded")} {added}.";
|
||||
msg += $" {GetLocalization("ImportAdded")} {added}.";
|
||||
}
|
||||
if (notAdded > 0)
|
||||
{
|
||||
msg += $" {GetLocalizationOrDefault("ImportSkipped")} {notAdded}.";
|
||||
msg += $" {GetLocalization("ImportSkipped")} {notAdded}.";
|
||||
}
|
||||
|
||||
if (errors > 0)
|
||||
{
|
||||
msg += $" {GetLocalizationOrDefault("ImportErrors")} {errors}.";
|
||||
msg += $" {GetLocalization("ImportErrors")} {errors}.";
|
||||
}
|
||||
SnackbarController.SendSnackbar(msg, TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
private async Task<bool> HandleAddMafileWithoutSession(Mafile data)
|
||||
{
|
||||
var oldMafile = SelectedMafile;
|
||||
SelectedMafile = data;
|
||||
await LoginAgain();
|
||||
var loginAgainVm = await DialogsController.ShowLoginAgainOnImportDialog(data, Proxies);
|
||||
if (loginAgainVm == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var password = loginAgainVm.Password;
|
||||
if (!loginAgainVm.UseMafileProxy)
|
||||
{
|
||||
data.Proxy = loginAgainVm.SelectedProxy;
|
||||
}
|
||||
var waitDialog = new WaitLoginDialog();
|
||||
var wait = DialogHost.Show(waitDialog);
|
||||
try
|
||||
{
|
||||
await MaClient.LoginAgain(data, password, loginAgainVm.SavePassword, waitDialog);
|
||||
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
|
||||
}
|
||||
catch (LoginException ex)
|
||||
{
|
||||
SnackbarController.SendSnackbar(ErrorTranslatorHelper.TranslateLoginError(ex.Error), TimeSpan.FromSeconds(1.5));
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ExceptionHandler.Handle(ex))
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DialogsController.CloseDialog();
|
||||
await wait;
|
||||
}
|
||||
var result = data.SessionData != null;
|
||||
if (result)
|
||||
if (!result) return result;
|
||||
Storage.SaveMafile(data);
|
||||
{
|
||||
var existed = MaFiles.FirstOrDefault(m => m.AccountName == data.AccountName); //TODO: more elegant way to handle overwrite
|
||||
if (existed != null)
|
||||
{
|
||||
MaFiles.Remove(existed);
|
||||
}
|
||||
MaFiles.Add(data);
|
||||
ResetQuery();
|
||||
SearchText = data.AccountName ?? string.Empty;
|
||||
SelectedMafile = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedMafile = oldMafile;
|
||||
}
|
||||
} //As this operation used only for 1 mafile at time, we can safely assume that we can select it for convenience
|
||||
return result;
|
||||
|
||||
}
|
||||
@@ -154,7 +189,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
{
|
||||
if (SelectedMafile == null) return;
|
||||
var confirm =
|
||||
await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("RemoveMafileConfirmation"));
|
||||
await DialogsController.ShowConfirmCancelDialog(GetLocalization("RemoveMafileConfirmation"));
|
||||
|
||||
if (!confirm) return;
|
||||
try
|
||||
@@ -163,12 +198,12 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRemoveAlreadyExist"));
|
||||
SnackbarController.SendSnackbar(GetLocalization("CantRemoveAlreadyExist"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SnackbarController.SendSnackbar(
|
||||
$"{GetLocalizationOrDefault("CantRemoveMafile")} {ex.Message}");
|
||||
$"{GetLocalization("CantRemoveMafile")} {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +219,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CopyMafileFromBuffer()
|
||||
private async Task PasteMafilesFromClipboard()
|
||||
{
|
||||
StringCollection files;
|
||||
try
|
||||
@@ -204,4 +239,30 @@ public partial class MainVM //File //TODO: Refactor
|
||||
|
||||
await AddMafile(arr);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyLogin(object? mafile)
|
||||
{
|
||||
if(mafile is not Mafile maf) return;
|
||||
if(ClipboardHelper.Set(maf.AccountName))
|
||||
SnackbarController.SendSnackbar(GetLocalization("LoginCopied"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopySteamId(object? mafile)
|
||||
{
|
||||
if (mafile is not Mafile maf) return;
|
||||
|
||||
if(ClipboardHelper.Set(maf.SteamId.ToString()))
|
||||
SnackbarController.SendSnackbar(GetLocalization("SteamIdCopied"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyMafile(object? mafile)
|
||||
{
|
||||
if (mafile is not Mafile maf) return;
|
||||
var path = Storage.TryFindMafilePath(maf);
|
||||
if(ClipboardHelper.SetFiles([path]))
|
||||
SnackbarController.SendSnackbar(GetLocalization("MafileCopied"));
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,16 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using AchiesUtilities.Extensions;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM //Groups
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<string> _groups = new();
|
||||
private ObservableCollection<string> _groups = [];
|
||||
|
||||
|
||||
public string? SelectedGroup
|
||||
@@ -20,7 +20,7 @@ public partial class MainVM //Groups
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedGroup, value))
|
||||
PerformSearch();
|
||||
PerformQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public partial class MainVM //Groups
|
||||
set
|
||||
{
|
||||
if(SetProperty(ref _searchText, value))
|
||||
PerformSearch();
|
||||
PerformQuery();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,6 +47,7 @@ public partial class MainVM //Groups
|
||||
var mafile = SelectedMafile;
|
||||
if (mafile == null) return;
|
||||
if (string.IsNullOrEmpty(value)) return;
|
||||
|
||||
mafile.Group = value;
|
||||
Storage.UpdateMafile(mafile);
|
||||
QueryGroups();
|
||||
@@ -60,6 +61,7 @@ public partial class MainVM //Groups
|
||||
{
|
||||
if (value == null) return;
|
||||
if (value.Length < 2) return;
|
||||
|
||||
var group = (string?)value[0];
|
||||
var mafile = (Mafile?)value[1];
|
||||
|
||||
@@ -67,7 +69,7 @@ public partial class MainVM //Groups
|
||||
mafile.Group = group;
|
||||
Storage.UpdateMafile(mafile);
|
||||
QueryGroups();
|
||||
PerformSearch();
|
||||
PerformQuery();
|
||||
OnPropertyChanged(nameof(SelectedMafile));
|
||||
}
|
||||
|
||||
@@ -84,7 +86,7 @@ public partial class MainVM //Groups
|
||||
{
|
||||
SelectedGroup = null;
|
||||
}
|
||||
PerformSearch();
|
||||
PerformQuery();
|
||||
}
|
||||
|
||||
|
||||
@@ -99,8 +101,12 @@ public partial class MainVM //Groups
|
||||
|
||||
Groups = new ObservableCollection<string>(groups!);
|
||||
}
|
||||
private void PerformSearch()
|
||||
|
||||
|
||||
|
||||
private void PerformQuery()
|
||||
{
|
||||
MaacDisplay = false;
|
||||
if (string.IsNullOrWhiteSpace(SelectedGroup) && string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
MaFiles = Storage.MaFiles;
|
||||
@@ -122,18 +128,19 @@ public partial class MainVM //Groups
|
||||
}
|
||||
var perform = query.ToList();
|
||||
MaFiles = new ObservableCollection<Mafile>(perform);
|
||||
if (SelectedMafile != null && !MaFiles.Contains(SelectedMafile))
|
||||
{
|
||||
SelectedMafile = MaFiles.FirstOrDefault();
|
||||
}
|
||||
SelectedMafile = MaFiles.FirstOrDefault();
|
||||
return;
|
||||
|
||||
bool SearchPredicate(Mafile mafile)
|
||||
{
|
||||
if (!mafile.AccountName.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return mafile.SessionData != null && mafile.SessionData.SteamId.Steam64.Id.Equals(searchSteamId);
|
||||
}
|
||||
return true;
|
||||
return mafile.AccountName.ContainsIgnoreCase(SearchText) || mafile.SteamId.Steam64.Id.Equals(searchSteamId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ResetQuery()
|
||||
{
|
||||
_selectedGroup = null;
|
||||
_searchText = string.Empty;
|
||||
PerformQuery();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using NebulaAuth.Core;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM
|
||||
{
|
||||
private const string LOC_PATH = "MainVM";
|
||||
private static string? GetLocalization(string key)
|
||||
{
|
||||
return LocManager.GetCodeBehind(LOC_PATH, key);
|
||||
}
|
||||
|
||||
private static string GetLocalizationOrDefault(string key)
|
||||
{
|
||||
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Model.MAAC;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM //MAAC
|
||||
{
|
||||
public bool MaacDisplay
|
||||
{
|
||||
get => _maacDisplay;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _maacDisplay, value))
|
||||
{
|
||||
SwitchMAACDisplay(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool _maacDisplay;
|
||||
|
||||
public bool MarketTimerEnabled
|
||||
{
|
||||
get => SelectedMafile?.LinkedClient?.AutoConfirmMarket ?? false;
|
||||
set => SetMarketTimer(value);
|
||||
}
|
||||
public bool TradeTimerEnabled
|
||||
{
|
||||
get => SelectedMafile?.LinkedClient?.AutoConfirmTrades ?? false;
|
||||
set => SetTradeTimer(value);
|
||||
}
|
||||
|
||||
public int TimerCheckSeconds
|
||||
{
|
||||
get => Settings.Instance.TimerSeconds;
|
||||
set => SetTimer(value);
|
||||
}
|
||||
|
||||
private void SetMarketTimer(bool value)
|
||||
{
|
||||
var selectedMafile = SelectedMafile;
|
||||
if (selectedMafile == null) return;
|
||||
if (value && selectedMafile.LinkedClient == null)
|
||||
{
|
||||
MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile);
|
||||
}
|
||||
if (!value && selectedMafile is { LinkedClient.AutoConfirmTrades: false })
|
||||
{
|
||||
MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile);
|
||||
}
|
||||
|
||||
if (selectedMafile.LinkedClient != null)
|
||||
{
|
||||
selectedMafile.LinkedClient.AutoConfirmMarket = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTradeTimer(bool value)
|
||||
{
|
||||
var selectedMafile = SelectedMafile;
|
||||
if (selectedMafile == null) return;
|
||||
if (value && selectedMafile.LinkedClient == null)
|
||||
{
|
||||
MultiAccountAutoConfirmer.TryAddToConfirm(selectedMafile);
|
||||
}
|
||||
|
||||
if (!value && selectedMafile is { LinkedClient.AutoConfirmMarket: false })
|
||||
{
|
||||
MultiAccountAutoConfirmer.RemoveFromConfirm(selectedMafile);
|
||||
}
|
||||
|
||||
if (selectedMafile.LinkedClient != null)
|
||||
{
|
||||
selectedMafile.LinkedClient.AutoConfirmTrades = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTimer(int value)
|
||||
{
|
||||
var timerCheckSeconds = Settings.TimerSeconds;
|
||||
if (timerCheckSeconds == value) return;
|
||||
if (timerCheckSeconds < 10)
|
||||
{
|
||||
timerCheckSeconds = 10; //Guard
|
||||
}
|
||||
if (value < 10)
|
||||
{
|
||||
value = timerCheckSeconds;
|
||||
SnackbarController.SendSnackbar(GetLocalization("TimerTooFast"));
|
||||
}
|
||||
Settings.TimerSeconds = value;
|
||||
OnPropertyChanged(nameof(TimerCheckSeconds));
|
||||
if (timerCheckSeconds != TimerCheckSeconds)
|
||||
SnackbarController.SendSnackbar(GetLocalization("TimerChanged"));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchMAAC(bool market)
|
||||
{
|
||||
var maf = SelectedMafile;
|
||||
if (maf == null) return;
|
||||
SwitchMAACOn([maf], market);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchMAACOnGroup(bool market)
|
||||
{
|
||||
var group = SelectedGroup;
|
||||
if(group == null) return;
|
||||
var mafilesInGroup = MaFiles.Where(m => group.Equals(m.Group));
|
||||
SwitchMAACOn(mafilesInGroup, market);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SwitchMAACOnAll(bool market)
|
||||
{
|
||||
SwitchMAACOn(MaFiles, market);
|
||||
}
|
||||
|
||||
private void SwitchMAACOn(IEnumerable<Mafile> mafiles, bool market)
|
||||
{
|
||||
mafiles = mafiles.ToArray();
|
||||
|
||||
var turnOn = mafiles.All(m => m.LinkedClient == null || GetCurrentMode(m.LinkedClient) == false);
|
||||
if (turnOn)
|
||||
{
|
||||
foreach (var mafile in mafiles)
|
||||
{
|
||||
MultiAccountAutoConfirmer.TryAddToConfirm(mafile);
|
||||
SetCurrentMode(mafile.LinkedClient, turnOn);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var mafile in mafiles)
|
||||
{
|
||||
SetCurrentMode(mafile.LinkedClient, turnOn);
|
||||
if(PretendsToRemove(mafile))
|
||||
MultiAccountAutoConfirmer.RemoveFromConfirm(mafile);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool PretendsToRemove(Mafile mafile) => mafile.LinkedClient is {AutoConfirmMarket: false, AutoConfirmTrades: false};
|
||||
bool GetCurrentMode(PortableMaClient linkedClient) => market ? linkedClient.AutoConfirmMarket : linkedClient.AutoConfirmTrades;
|
||||
void SetCurrentMode(PortableMaClient? linkedClient, bool value)
|
||||
{
|
||||
if (linkedClient == null) return;
|
||||
if (market)
|
||||
{
|
||||
linkedClient.AutoConfirmMarket = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
linkedClient.AutoConfirmTrades = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchMAACDisplay(bool newValue)
|
||||
{
|
||||
if (newValue)
|
||||
{
|
||||
MaFiles = MultiAccountAutoConfirmer.Clients;
|
||||
}
|
||||
else
|
||||
{
|
||||
PerformQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM //Other
|
||||
{
|
||||
private void SessionHandlerOnLoginCompleted(object? sender, EventArgs e)
|
||||
{
|
||||
var currentSession = DialogHost.GetDialogSession(null);
|
||||
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
if (currentSession is { Content: WaitLoginDialog, IsEnded: false })
|
||||
{
|
||||
try
|
||||
{
|
||||
currentSession.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void SessionHandlerOnLoginStarted(object? sender, EventArgs e)
|
||||
{
|
||||
if (DialogHost.IsDialogOpen(null)) return;
|
||||
await Application.Current.Dispatcher.BeginInvoke(async () =>
|
||||
{
|
||||
await DialogHost.Show(new WaitLoginDialog());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
@@ -6,7 +7,6 @@ using NebulaAuth.Model.Entities;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NebulaAuth.Model.Comparers;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -23,16 +23,17 @@ public partial class MainVM
|
||||
{
|
||||
OnPropertyChanged(nameof(IsDefaultProxy));
|
||||
OnProxyChanged();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private MaProxy? _selectedProxy;
|
||||
|
||||
[ObservableProperty] private bool _proxyExist = true;
|
||||
public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null;
|
||||
public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null && SelectedMafile != null;
|
||||
private bool _handleProxyChange;
|
||||
|
||||
//TODO: Refactor this method
|
||||
private void SetCurrentProxy()
|
||||
{
|
||||
if (ReferenceEquals(_selectedProxy, SelectedMafile?.Proxy) == false && _selectedProxy?.Equals(SelectedMafile?.Proxy) == false)
|
||||
@@ -41,6 +42,7 @@ public partial class MainVM
|
||||
if (SelectedMafile == null)
|
||||
{
|
||||
SelectedProxy = null;
|
||||
ProxyExist = true;
|
||||
return;
|
||||
}
|
||||
if (SelectedMafile.Proxy == null)
|
||||
@@ -52,7 +54,7 @@ public partial class MainVM
|
||||
var existed = Proxies.FirstOrDefault(p => p.Id == SelectedMafile.Proxy.Id);
|
||||
|
||||
|
||||
if (existed == null || ProxyDataComparer.Equal(existed.Data, SelectedMafile.Proxy.Data) == false)
|
||||
if (existed == null || existed.Data.Equals(SelectedMafile.Proxy.Data) == false)
|
||||
{
|
||||
SelectedProxy = SelectedMafile.Proxy;
|
||||
}
|
||||
@@ -76,7 +78,7 @@ public partial class MainVM
|
||||
|
||||
var selectedId = SelectedProxy.Id;
|
||||
ProxyExist = ProxyStorage.Proxies.TryGetValue(selectedId, out var existedProxy)
|
||||
&& ProxyStorage.CompareProxy(SelectedProxy.Data, existedProxy);
|
||||
&& SelectedProxy.Data.Equals(existedProxy); //Id is not important in 'Equals()' as we extract it from the dictionary
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -98,6 +100,7 @@ public partial class MainVM
|
||||
[RelayCommand]
|
||||
private void RemoveProxy()
|
||||
{
|
||||
if (SelectedProxy == null) return;
|
||||
if (SelectedMafile == null) return;
|
||||
SelectedMafile.Proxy = null;
|
||||
SelectedProxy = null;
|
||||
@@ -118,4 +121,5 @@ public partial class MainVM
|
||||
SelectedMafile.Proxy = SelectedProxy;
|
||||
Storage.UpdateMafile(SelectedMafile);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,140 +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);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ using AchiesUtilities.Web.Proxy;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using SteamLib;
|
||||
using SteamLib.Account;
|
||||
@@ -20,12 +20,12 @@ using SteamLib.SteamMobile.AuthenticatorLinker;
|
||||
using SteamLib.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using NebulaAuth.Core;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
@@ -61,7 +61,9 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
private TaskCompletionSource _emailConfTcs = new();
|
||||
private TaskCompletionSource<string> _linkCodeTcs = new();
|
||||
|
||||
private bool isLinkStarted;
|
||||
private bool _isLinkStarted;
|
||||
private string _rCode = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ProceedCommand))]
|
||||
@@ -136,6 +138,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
IsLogin = true;
|
||||
var userName = FieldText;
|
||||
var pass = PassFieldText;
|
||||
_password = pass;
|
||||
FieldText = string.Empty;
|
||||
IsFieldVisible = false;
|
||||
HintText = string.Empty;
|
||||
@@ -179,12 +182,12 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
|
||||
#endregion
|
||||
|
||||
if (isLinkStarted)
|
||||
if (_isLinkStarted)
|
||||
goto linkStarted;
|
||||
|
||||
try
|
||||
{
|
||||
isLinkStarted = true;
|
||||
_isLinkStarted = true;
|
||||
var linkOptions = new LinkOptions(Client, LoginV2Executor.NullConsumer, this,
|
||||
null, this, this, backupHandler: Backup, Logger2);
|
||||
_linker = new SteamAuthenticatorLinker(linkOptions);
|
||||
@@ -192,13 +195,26 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
IsLinkCode = true;
|
||||
IsCompleted = true;
|
||||
var mafile = Mafile.FromMobileDataExtended(result);
|
||||
try
|
||||
{
|
||||
if (SelectedProxy.HasValue)
|
||||
mafile.Proxy = new MaProxy(SelectedProxy.Value.Key, SelectedProxy.Value.Value);
|
||||
if (Settings.Instance.IsPasswordSet)
|
||||
mafile.Password = PHandler.Encrypt(_password);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error during saving Nebula data to mafile");
|
||||
}
|
||||
|
||||
Storage.SaveMafile(mafile);
|
||||
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
|
||||
HintText =
|
||||
string.Format(GetLocalizationOrDefault("MafileLinked"),
|
||||
string.Format(GetLocalizationOrDefault("MafileLinked"),
|
||||
mafile.RevocationCode,
|
||||
mafile.SessionData?.SteamId.Steam64);
|
||||
|
||||
_rCode = mafile.RevocationCode ?? string.Empty;
|
||||
CanProceed = true;
|
||||
return;
|
||||
}
|
||||
@@ -313,11 +329,13 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
IsLogin = false;
|
||||
IsFieldVisible = true;
|
||||
IsEmailCode = false;
|
||||
isLinkStarted = false;
|
||||
_isLinkStarted = false;
|
||||
IsPhoneNumber = false;
|
||||
IsEmailConfirmation = false;
|
||||
CanProceed = true;
|
||||
_emailCodeTcs = new TaskCompletionSource<string>();
|
||||
_rCode = string.Empty;
|
||||
_password = string.Empty;
|
||||
}
|
||||
|
||||
private void Backup(MobileDataExtended data)
|
||||
@@ -326,9 +344,8 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
{
|
||||
Directory.CreateDirectory("mafiles_backup");
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
|
||||
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"), json);
|
||||
var json = NebulaSerializer.SerializeMafile(data, null);
|
||||
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"), json); //TODO: Move logic to Storage
|
||||
}
|
||||
|
||||
#region Providers
|
||||
@@ -403,6 +420,33 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
|
||||
|
||||
#endregion
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenTroubleshooting()
|
||||
{
|
||||
const string troubleshootingURI =
|
||||
"https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/docs/{0}/LinkingTroubleshooting";
|
||||
|
||||
var localized = string.Format(troubleshootingURI, LocManager.GetCurrentLanguageCode());
|
||||
Process.Start(new ProcessStartInfo(new Uri(localized).ToString())
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyCode()
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(_rCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex, "Error whily copying RCode");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string GetLocalizationOrDefault(string key)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public partial class LoginAgainOnImportVM : ObservableObject
|
||||
{
|
||||
public ObservableCollection<MaProxy> Proxies { get; } = new();
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsFormValid))]
|
||||
private string _password = null!;
|
||||
[ObservableProperty] private bool _savePassword;
|
||||
[ObservableProperty] private string _userName = null!;
|
||||
[ObservableProperty] private bool _mafileHasProxy;
|
||||
|
||||
public MaProxy? SelectedProxy
|
||||
{
|
||||
get => _selectedProxy;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedProxy, value) && value != null)
|
||||
{
|
||||
UseMafileProxy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool UseMafileProxy
|
||||
{
|
||||
get => _useMafileProxy;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _useMafileProxy, value) && value)
|
||||
{
|
||||
SelectedProxy = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFormValid => !string.IsNullOrWhiteSpace(Password);
|
||||
|
||||
|
||||
private MaProxy? _selectedProxy;
|
||||
private bool _useMafileProxy;
|
||||
public LoginAgainOnImportVM(Mafile mafile, IEnumerable<MaProxy> proxies)
|
||||
{
|
||||
UserName = mafile.AccountName;
|
||||
MafileHasProxy = mafile.Proxy != null;
|
||||
UseMafileProxy = MafileHasProxy;
|
||||
Proxies = new(proxies);
|
||||
}
|
||||
|
||||
public LoginAgainOnImportVM()
|
||||
{ }
|
||||
|
||||
[RelayCommand]
|
||||
private void RemoveProxy()
|
||||
{
|
||||
SelectedProxy = null;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,16 @@ namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public partial class LoginAgainVM : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _password = null!;
|
||||
[ObservableProperty] private bool _savePassword;
|
||||
[ObservableProperty] private string _userName = null!;
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsFormValid))]
|
||||
private string _password = null!;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _savePassword;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _userName = null!;
|
||||
|
||||
|
||||
public bool IsFormValid => !string.IsNullOrWhiteSpace(Password);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public partial class ProxyManagerVM : ObservableObject
|
||||
[ObservableProperty] private KeyValuePair<int, ProxyData>? _defaultProxy;
|
||||
public ObservableDictionary<int, ProxyData> Proxies => ProxyStorage.Proxies;
|
||||
|
||||
private static readonly Regex IdRegex = new(@"(?:\{(\d+)\})");
|
||||
private static readonly Regex IdRegex = new(@"\{(\d+)\}$", RegexOptions.Compiled);
|
||||
|
||||
|
||||
public ProxyManagerVM()
|
||||
@@ -33,80 +33,71 @@ public partial class ProxyManagerVM : ObservableObject
|
||||
[RelayCommand]
|
||||
private void AddProxy()
|
||||
{
|
||||
if (string.IsNullOrEmpty(AddProxyField)) return;
|
||||
if (AddProxyField.Contains(Environment.NewLine))
|
||||
{
|
||||
var split = AddProxyField.Split(Environment.NewLine);
|
||||
var idPresent = (bool?)null;
|
||||
var proxies = new List<KeyValuePair<int?, ProxyData>>();
|
||||
var i = 0;
|
||||
foreach (var str in split)
|
||||
{
|
||||
i++;
|
||||
int? id = null;
|
||||
var match = IdRegex.Match(str);
|
||||
if (match.Success) id = int.Parse(match.Groups[1].Value);
|
||||
idPresent ??= match.Success;
|
||||
|
||||
if (idPresent.Value != match.Success)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var proxy = ProxyData.Parse(str, ProxyStorage.FORMAT);
|
||||
if (id != null && proxies.Any(kvp => kvp.Key == id))
|
||||
{
|
||||
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
|
||||
return;
|
||||
}
|
||||
proxies.Add(new KeyValuePair<int?, ProxyData>(id, proxy));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in proxies)
|
||||
{
|
||||
ProxyStorage.SetProxy(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
var input = AddProxyField;
|
||||
if (string.IsNullOrEmpty(input)) return;
|
||||
|
||||
|
||||
var split = input
|
||||
.Split(Environment.NewLine)
|
||||
.Where(s => string.IsNullOrWhiteSpace(s) == false)
|
||||
.ToArray();
|
||||
|
||||
if (split.Length == 0) return;
|
||||
|
||||
bool? idPresent = null;
|
||||
var proxies = new List<KeyValuePair<int?, ProxyData>>();
|
||||
var i = 0;
|
||||
|
||||
|
||||
foreach (var s in split.Where(s => string.IsNullOrWhiteSpace(s) == false))
|
||||
{
|
||||
i++;
|
||||
var str = s;
|
||||
int? id = null;
|
||||
var input = AddProxyField;
|
||||
if (IdRegex.IsMatch(AddProxyField))
|
||||
var idMatch = IdRegex.Match(str);
|
||||
if (idMatch.Success)
|
||||
{
|
||||
id = int.Parse(IdRegex.Match(AddProxyField).Groups[1].Value);
|
||||
input = IdRegex.Replace(input, "");
|
||||
id = int.Parse(idMatch.Groups[1].Value);
|
||||
str = IdRegex.Replace(str, "");
|
||||
}
|
||||
|
||||
ProxyData data;
|
||||
try
|
||||
idPresent ??= idMatch.Success;
|
||||
if (idPresent.Value != idMatch.Success)
|
||||
{
|
||||
data = ProxyData.Parse(input, ProxyStorage.FORMAT);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
|
||||
return;
|
||||
}
|
||||
|
||||
ProxyStorage.SetProxy(id, data);
|
||||
|
||||
|
||||
if (ProxyStorage.DefaultScheme.TryParse(str, out var proxy))
|
||||
{
|
||||
if (id != null && proxies.Any(kvp => kvp.Key == id))
|
||||
{
|
||||
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
|
||||
return;
|
||||
}
|
||||
proxies.Add(KeyValuePair.Create(id, proxy));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (split.Length == 1)
|
||||
{
|
||||
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
|
||||
return;
|
||||
}
|
||||
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ProxyStorage.SetProxies(proxies);
|
||||
ProxyStorage.OrderCollection();
|
||||
AddProxyField = string.Empty;
|
||||
CheckIfDefaultProxyStay();
|
||||
}
|
||||
|
||||
|
||||
private void CheckIfDefaultProxyStay()
|
||||
{
|
||||
if (!DefaultProxy.HasValue || Proxies.Any(kvp => kvp.Equals(DefaultProxy.Value))) return;
|
||||
@@ -117,17 +108,37 @@ public partial class ProxyManagerVM : ObservableObject
|
||||
[RelayCommand]
|
||||
private void RemoveProxy()
|
||||
{
|
||||
if (SelectedProxy == null) return;
|
||||
ProxyStorage.RemoveProxy(SelectedProxy.Value.Key);
|
||||
var selected = SelectedProxy;
|
||||
if (selected == null) return;
|
||||
var s = selected.Value;
|
||||
|
||||
|
||||
KeyValuePair<int, ProxyData>? nextNeighbor = null;
|
||||
KeyValuePair<int, ProxyData>? prevNeighbor = null;
|
||||
foreach (var id in Proxies.Keys.Order())
|
||||
{
|
||||
if (id < s.Key)
|
||||
{
|
||||
prevNeighbor = KeyValuePair.Create(id, Proxies[id]);
|
||||
}
|
||||
else if (id > s.Key)
|
||||
{
|
||||
nextNeighbor = KeyValuePair.Create(id, Proxies[id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ProxyStorage.RemoveProxy(s.Key);
|
||||
SelectedProxy = nextNeighbor ?? prevNeighbor;
|
||||
CheckIfDefaultProxyStay();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void SetDefault()
|
||||
private void SetDefault(object? arg)
|
||||
{
|
||||
if (SelectedProxy == null) return;
|
||||
DefaultProxy = SelectedProxy;
|
||||
MaClient.DefaultProxy = SelectedProxy.Value.Value;
|
||||
if (arg is not KeyValuePair<int, ProxyData> proxy) return;
|
||||
DefaultProxy = proxy;
|
||||
MaClient.DefaultProxy = proxy.Value;
|
||||
ProxyStorage.Save();
|
||||
}
|
||||
|
||||
@@ -140,10 +151,18 @@ public partial class ProxyManagerVM : ObservableObject
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void CopyProxy(ProxyData? obj)
|
||||
private void CopyProxy(ProxyData? data)
|
||||
{
|
||||
if (obj == null) return;
|
||||
Clipboard.SetText(obj.ToString(ProxyStorage.FORMAT));
|
||||
if (data == null) return;
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(ProxyStorage.GetProxyString(data));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -11,11 +11,6 @@ public partial class SettingsVM : ObservableObject
|
||||
{
|
||||
public Settings Settings => Settings.Instance;
|
||||
|
||||
public bool DisableTimersOnChange
|
||||
{
|
||||
get => Settings.DisableTimersOnChange;
|
||||
set => Settings.DisableTimersOnChange = value;
|
||||
}
|
||||
public BackgroundMode BackgroundMode
|
||||
{
|
||||
get => Settings.BackgroundMode;
|
||||
@@ -109,14 +104,25 @@ public partial class SettingsVM : ObservableObject
|
||||
set => Settings.AllowAutoUpdate = value;
|
||||
}
|
||||
|
||||
[ObservableProperty] private string _password;
|
||||
public bool UseAccountNameAsMafileName
|
||||
{
|
||||
get => Settings.UseAccountNameAsMafileName;
|
||||
set => Settings.UseAccountNameAsMafileName = value;
|
||||
}
|
||||
|
||||
public bool IgnorePatchTuesdayErrors
|
||||
{
|
||||
get => Settings.IgnorePatchTuesdayErrors;
|
||||
set => Settings.IgnorePatchTuesdayErrors = value;
|
||||
}
|
||||
|
||||
[ObservableProperty] private string? _password;
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
public void SetPassword()
|
||||
private void SetPassword()
|
||||
{
|
||||
PHandler.SetPassword(Password);
|
||||
Settings.IsPasswordSet = PHandler.IsPasswordSet;
|
||||
Settings.IsPasswordSet = PHandler.SetPassword(Password);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using AutoUpdaterDotNET;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public class UpdaterVM : ObservableObject
|
||||
{
|
||||
|
||||
public UpdateInfoEventArgs UpdateInfoEventArgs { get; }
|
||||
public UpdaterVM(UpdateInfoEventArgs args)
|
||||
{
|
||||
UpdateInfoEventArgs = args;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,11 @@
|
||||
"ru": "Отменено",
|
||||
"ua": "Скасовано"
|
||||
},
|
||||
"Proxy": {
|
||||
"en": "Proxy",
|
||||
"ru": "Прокси",
|
||||
"ua": "Проксі"
|
||||
},
|
||||
"Abbreviations": {
|
||||
"Time": {
|
||||
"Seconds": {
|
||||
@@ -53,6 +58,23 @@
|
||||
}
|
||||
},
|
||||
"MainWindow": {
|
||||
"Global": {
|
||||
"DragNDropHint": {
|
||||
"en": "Release to import mafiles",
|
||||
"ru": "Отпустите для импорта мафайлов",
|
||||
"ua": "Відпустіть для імпорту мафайлів"
|
||||
},
|
||||
"LoadingHint": {
|
||||
"en": "Loading...",
|
||||
"ru": "Загрузка...",
|
||||
"ua": "Завантаження..."
|
||||
},
|
||||
"StartTip": {
|
||||
"ru": "Чтобы начать пользоваться программой вы можете привязать аккаунт через меню \"Аккаунт\", либо импортировать существующие мафайлы одним из способов:\n1. Скопировать их в папку mafiles и перезапустить приложение\n2. Перетянуть файлы прямо в окно программы\n3. Скопировать файлы и нажать CTRL+V в окне программы\n4. Через меню \"Файл\" - \"Импорт\"",
|
||||
"en": "To start using the program, you can link an account through the \"Account\" menu, or import existing mafiles in one of the following ways:\n1. Copy them to the mafiles folder and restart the application\n2. Drag files directly into the program window\n3. Copy files and press CTRL+V in the program window\n4. Through the \"File\" - \"Import\" menu",
|
||||
"ua": "Щоб почати користуватися програмою, ви можете прив'язати акаунт через меню \"Акаунт\", або імпортувати існуючі мафайли одним із способів:\n1. Скопіювати їх у папку mafiles та перезапустити програму\n2. Перетягнути файли безпосередньо в вікно програми\n3. Скопіювати файли та натиснути CTRL+V в вікні програми\n4. Через меню \"Файл\" - \"Імпорт\""
|
||||
}
|
||||
},
|
||||
"Menu": {
|
||||
"File": {
|
||||
"Caption": {
|
||||
@@ -85,7 +107,7 @@
|
||||
"Caption": {
|
||||
"en": "Account",
|
||||
"ru": "Аккаунт",
|
||||
"ua": "Аккаунт"
|
||||
"ua": "Акаунт"
|
||||
},
|
||||
"Link": {
|
||||
"en": "Link",
|
||||
@@ -115,6 +137,11 @@
|
||||
"ru": "Группы",
|
||||
"ua": "Групи"
|
||||
},
|
||||
"GroupToolTip": {
|
||||
"ru": "Введите новую группу и нажмите Enter. Управление группой - ПКМ на аккаунте",
|
||||
"en": "Enter new group and press Enter. Group management - RMB on account",
|
||||
"ua": "Введіть нову групу і натисніть Enter. Управління групою - ПКМ на акаунті"
|
||||
},
|
||||
"Proxy": {
|
||||
"ProxyHint": {
|
||||
"en": "Proxy",
|
||||
@@ -126,6 +153,11 @@
|
||||
"ru": "Открыть менеджер прокси",
|
||||
"ua": "Відкрити менеджер проксі"
|
||||
},
|
||||
"ProxyManipulateToolTip": {
|
||||
"en": "Right-click to open menu. DEL to remove",
|
||||
"ru": "Правый клик для открытия меню. DEL для удаления",
|
||||
"ua": "Правий клік для відкриття меню. DEL для видалення"
|
||||
},
|
||||
"ProxyAlert": {
|
||||
"DefaultInUse": {
|
||||
"en": "Default proxy is in use",
|
||||
@@ -139,16 +171,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"TradeTimerHint": {
|
||||
"en": "Trade",
|
||||
"ru": "Трейд",
|
||||
"ua": "Трейд"
|
||||
"en": "Auto-confirm trades. RMC for mass control",
|
||||
"ru": "Автоподтверждение трейдов. ПКМ для массового управления",
|
||||
"ua": "Автопідтвердження трейдів. ПКМ для масового управління"
|
||||
},
|
||||
"MarketTimerHint": {
|
||||
"en": "Market",
|
||||
"ru": "Маркет",
|
||||
"ua": "Маркет"
|
||||
"en": "Auto-confirm market listings. RMC for mass control",
|
||||
"ru": "Автоподтверждение лотов на маркете. ПКМ для массового управления",
|
||||
"ua": "Автопідтвердження лотів на маркеті. ПКМ для масового управління"
|
||||
},
|
||||
"TimersContextMenu": {
|
||||
"SwitchMAACGroup": {
|
||||
"en": "Switch in group",
|
||||
"ru": "Переключить в группе",
|
||||
"ua": "Перемкнути в групі"
|
||||
},
|
||||
"SwitchMAACAll": {
|
||||
"en": "Switch all",
|
||||
"ru": "Переключить все",
|
||||
"ua": "Перемкнути всі"
|
||||
}
|
||||
},
|
||||
"ShowAutoConfirmAccountsHint": {
|
||||
"en": "Show accounts with auto-confirmation enabled.",
|
||||
"ru": "Показать аккаунты с включенным автоподтверждением.",
|
||||
"ua": "Показати акаунти з включеним автопідтвердженням."
|
||||
}
|
||||
},
|
||||
"LeftPart": {
|
||||
@@ -174,7 +222,7 @@
|
||||
"Account": {
|
||||
"en": "Account: ",
|
||||
"ru": "Аккаунт: ",
|
||||
"ua": "Аккаунт: "
|
||||
"ua": "Акаунт: "
|
||||
},
|
||||
"Group": {
|
||||
"en": "Group: ",
|
||||
@@ -211,6 +259,21 @@
|
||||
},
|
||||
"ContextMenus": {
|
||||
"Mafile": {
|
||||
"CopyLogin": {
|
||||
"en": "Copy login",
|
||||
"ru": "Скопировать логин",
|
||||
"ua": "Скопіювати логін"
|
||||
},
|
||||
"CopyMafile": {
|
||||
"en": "Copy mafile",
|
||||
"ru": "Скопировать мафайл",
|
||||
"ua": "Скопіювати мафайл"
|
||||
},
|
||||
"CopySteamId": {
|
||||
"en": "Copy SteamID",
|
||||
"ru": "Скопировать SteamID",
|
||||
"ua": "Скопіювати SteamID"
|
||||
},
|
||||
"AddToGroup": {
|
||||
"en": "Add to group",
|
||||
"ru": "Добавить в группу",
|
||||
@@ -225,13 +288,13 @@
|
||||
"Proxy": {
|
||||
"Copy": {
|
||||
"en": "Copy",
|
||||
"ru": "Копировать",
|
||||
"ua": "Копіювати"
|
||||
"ru": "Скопировать",
|
||||
"ua": "Скопіювати"
|
||||
},
|
||||
"CopyAddress": {
|
||||
"en": "Copy address",
|
||||
"ru": "Копировать адрес",
|
||||
"ua": "Копіювати адресу"
|
||||
"ru": "Скопировать адрес",
|
||||
"ua": "Скопіювати адресу"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,11 +327,6 @@
|
||||
"ua": "Без фону"
|
||||
}
|
||||
},
|
||||
"DisableTimersOnSwitch": {
|
||||
"en": "Disable timers on account switch",
|
||||
"ru": "Отключать таймеры при смене аккаунта",
|
||||
"ua": "Вимикати таймери при зміні аккаунта"
|
||||
},
|
||||
"MinimizeToTray": {
|
||||
"en": "Minimize to tray",
|
||||
"ru": "Сворачивать в трей",
|
||||
@@ -279,6 +337,11 @@
|
||||
"ru": "Использовать индикатор",
|
||||
"ua": "Використовувати індикатор"
|
||||
},
|
||||
"UseIndicatorHint": {
|
||||
"en": "Small color indicator in windows toolbar to identify program instance",
|
||||
"ru": "Маленький цветной индикатор в панели задач для идентификации экземпляра программы",
|
||||
"ua": "Малий кольоровий індикатор в панелі завдань для ідентифікації екземпляра програми"
|
||||
},
|
||||
"UseCustomColor": {
|
||||
"en": "Custom window color",
|
||||
"ru": "Свой цвет окна",
|
||||
@@ -292,8 +355,8 @@
|
||||
},
|
||||
"Hint": {
|
||||
"en": "Doesn't saved on disk",
|
||||
"ru": "Не сохраняется в файл",
|
||||
"ua": "Не зберігається у файлах"
|
||||
"ru": "Не сохраняется в системе",
|
||||
"ua": "Не зберігається у системі"
|
||||
}
|
||||
},
|
||||
"LegacyMafileMode": {
|
||||
@@ -310,7 +373,24 @@
|
||||
"en": "Allow auto update",
|
||||
"ru": "Разрешить автообновление",
|
||||
"ua": "Дозволити автооновлення"
|
||||
},
|
||||
"UseAccountName": {
|
||||
"en": "Use account name on mafiles",
|
||||
"ru": "Использовать имя аккаунта на мафайлах",
|
||||
"ua": "Використовувати ім'я акаунта на мафайлах"
|
||||
},
|
||||
"IgnorePatchTuesdayErrors": {
|
||||
"en": "Ignore Patch Tuesday errors in timer (exp.)",
|
||||
"ru": "Игнорировать ошибки Patch Tuesday в таймере (эксп.)",
|
||||
"ua": "Ігнорувати помилки Patch Tuesday у таймері (експ.)"
|
||||
},
|
||||
"IgnorePatchTuesdayErrorsHint": {
|
||||
"en": "Ignore errors that occur every Tuesday (Wednesday night in GMT) when Steam doing technical works and auto-confirm timers turns off unnecessarily",
|
||||
"ru": "Игнорировать ошибки, которые возникают каждый вторник (ночь среды по GMT) когда Steam проводит технические работы и автоподтверждения отключаются без надобности",
|
||||
"ua": "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби"
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
"LoginAgainDialog": {
|
||||
"Title": {
|
||||
@@ -342,6 +422,16 @@
|
||||
"en": "Save encrypted password to mafile",
|
||||
"ru": "Сохранить зашифрованный пароль в мафайл",
|
||||
"ua": "Зберегти зашифрований пароль в мафайлі"
|
||||
},
|
||||
"UseMafileProxy": {
|
||||
"en": "Use mafile proxy",
|
||||
"ru": "Использовать прокси из мафайла",
|
||||
"ua": "Використовувати проксі з мафайла"
|
||||
},
|
||||
"ProxyToolTip": {
|
||||
"en": "Press 'DEL' to remove",
|
||||
"ru": "Нажмите 'DEL' для удаления",
|
||||
"ua": "Натисніть 'DEL' для видалення"
|
||||
}
|
||||
},
|
||||
"ProxyManagerDialog": {
|
||||
@@ -354,8 +444,12 @@
|
||||
"en": "Default proxy:",
|
||||
"ru": "Прокси по умолчанию:",
|
||||
"ua": "Проксі за замовчуванням:"
|
||||
},
|
||||
"UseRandomDefaultProxy": {
|
||||
"en": "Use random default proxy",
|
||||
"ru": "Cлучайный прокси по умолчанию",
|
||||
"ua": "Випадковий проксі за замовчуванням"
|
||||
}
|
||||
|
||||
},
|
||||
"WaitLoginDialog": {
|
||||
"Text": {
|
||||
@@ -370,6 +464,11 @@
|
||||
"ru": "Привязка",
|
||||
"ua": "Прив'язка"
|
||||
},
|
||||
"GotErrorHyperlinkText": {
|
||||
"en": "(getting error?)",
|
||||
"ru": "(возникает ошибка?)",
|
||||
"ua": "(виникає помилка?)"
|
||||
},
|
||||
"Proxy": {
|
||||
"en": "Proxy",
|
||||
"ru": "Прокси",
|
||||
@@ -391,14 +490,14 @@
|
||||
"ua": "Номер телефону"
|
||||
},
|
||||
"EmailLink": {
|
||||
"en": "Email link",
|
||||
"ru": "Ссылка из письма",
|
||||
"ua": "Посилання з листа"
|
||||
"en": "EMail link",
|
||||
"ru": "Ссылка из email",
|
||||
"ua": "Посилання з email"
|
||||
},
|
||||
"SmsOrCode": {
|
||||
"en": "Sms or code",
|
||||
"ru": "Смс или код",
|
||||
"ua": "Смс або код"
|
||||
"en": "SMS or code",
|
||||
"ru": "СМС или код",
|
||||
"ua": "СМС або код"
|
||||
},
|
||||
"Completed": {
|
||||
"en": "Completed",
|
||||
@@ -526,9 +625,9 @@
|
||||
"ua": "помилки:"
|
||||
},
|
||||
"RemoveMafileConfirmation": {
|
||||
"ru": "Удалить мафайл? Это действие переместит мафайл в mafiles_removed",
|
||||
"en": "Remove mafile? This action will move mafile to mafiles_removed",
|
||||
"ua": "Видалити мафайл? Ця дія перемістить мафайл в mafiles_removed"
|
||||
"ru": "Удалить мафайл? Мафайл будет перемещен в папку 'mafiles_removed' (Это действие не удаляет Steam Guard с аккаунта)",
|
||||
"en": "Remove mafile? Mafile will be moved to 'mafiles_removed' directory (This action does not remove the Steam Guard from the account)",
|
||||
"ua": "Видалити мафайл? Мафайл буде переміщено у каталог 'mafiles_removed' (Ця дія не видаляє автентифікатор з облікового запису)"
|
||||
},
|
||||
"CantRemoveAlreadyExist": {
|
||||
"ru": "Не удалось переместить мафайл, возможно в папке уже существует мафайл с таким именем.",
|
||||
@@ -540,30 +639,45 @@
|
||||
"en": "Can't remove mafile:",
|
||||
"ua": "Не вдалося видалити (перемістити) мафайл:"
|
||||
},
|
||||
"TimerConfirmed": {
|
||||
"ru": "Авто: подтверждено ",
|
||||
"en": "Auto: confirmed ",
|
||||
"ua": "Авто: підтверджено "
|
||||
},
|
||||
"TimerNotConfirmed": {
|
||||
"ru": "Авто: подтверждение не сработало",
|
||||
"en": "Auto: confirmation was unsuccessful",
|
||||
"ua": "Авто: підтвердження не спрацювало"
|
||||
},
|
||||
"TimerSessionError": {
|
||||
"ru": "Авто: необходимо обновить сессию или перелогиниться",
|
||||
"en": "Auto: need to refresh session or relogin",
|
||||
"ua": "Авто: необхідно оновити сесію або перелогінитися"
|
||||
},
|
||||
"TimerPrefix": {
|
||||
"ru": "Авто: ",
|
||||
"en": "Auto: ",
|
||||
"ua": "Авто: "
|
||||
},
|
||||
"TimerTooFast": {
|
||||
"ru": "Слишком быстрый таймер.",
|
||||
"en": "Too fast timer.",
|
||||
"ua": "Занадто швидкий таймер."
|
||||
},
|
||||
"DuplicateMafilesFound": {
|
||||
"en": "Duplicate mafile(s) found and were not loaded. Check log to get lists of duplicate files.",
|
||||
"ru": "Найдены дубликаты мафайлов, они не были загружены. Проверьте лог чтобы получить списки дубликатов.",
|
||||
"ua": "Знайдено дублікати мафайлів, вони не були завантажені. Перевірте лог щоб отримати списки дублікатів."
|
||||
},
|
||||
"TimerChanged": {
|
||||
"en": "Timer changed",
|
||||
"ru": "Таймер изменен",
|
||||
"ua": "Таймер змінено"
|
||||
},
|
||||
"CantRetrieveSteamIDToUpdate": {
|
||||
"ru": "Не удалось получить SteamID из мафайла и сохранить изменения. Необходимо сделать релогин. Отменено",
|
||||
"en": "Can't retrieve SteamID from mafile to save changes. Need to relogin. Canceled",
|
||||
"ua": "Не вдалося отримати SteamID з мафайла та зберегти зміни. Потрібно зробити релогін. Скасовано"
|
||||
},
|
||||
"LoginCopied": {
|
||||
"en": "Login copied",
|
||||
"ru": "Логин скопирован",
|
||||
"ua": "Логін скопійовано"
|
||||
},
|
||||
"SteamIdCopied": {
|
||||
"en": "SteamID copied",
|
||||
"ru": "SteamID скопирован",
|
||||
"ua": "SteamID скопійовано"
|
||||
},
|
||||
"MafileCopied": {
|
||||
"en": "Mafile copied",
|
||||
"ru": "Мафайл скопирован",
|
||||
"ua": "Мафайл скопійовано"
|
||||
},
|
||||
"MafileNotCopied": {
|
||||
"en": "Mafile not copied",
|
||||
"ru": "Мафайл не скопирован",
|
||||
"ua": "Мафайл не скопійовано"
|
||||
}
|
||||
},
|
||||
"ErrorTranslator": {
|
||||
@@ -626,7 +740,7 @@
|
||||
"AccountNotFound": {
|
||||
"ru": "Аккаунт не найден (18)",
|
||||
"en": "Account not found (18)",
|
||||
"ua": "Аккаунт не знайдено (18)"
|
||||
"ua": "Акаунт не знайдено (18)"
|
||||
},
|
||||
"InvalidSteamID": {
|
||||
"ru": "Неправильный SteamID (19)",
|
||||
@@ -661,7 +775,7 @@
|
||||
"AccountDisabled": {
|
||||
"ru": "Аккаунт отключен (43)",
|
||||
"en": "Account disabled (43)",
|
||||
"ua": "Аккаунт відключено (43)"
|
||||
"ua": "Акаунт відключено (43)"
|
||||
|
||||
},
|
||||
"Suspended": {
|
||||
@@ -684,7 +798,7 @@
|
||||
"AccountLockedDown": {
|
||||
"ru": "Аккаунт заблокирован (КТ 73)",
|
||||
"en": "Account locked down (73)",
|
||||
"ua": "Аккаунт заблоковано (КТ 73)"
|
||||
"ua": "Акаунт заблоковано (КТ 73)"
|
||||
|
||||
},
|
||||
"UnexpectedError": {
|
||||
@@ -732,7 +846,7 @@
|
||||
"AccountLimitExceeded": {
|
||||
"ru": "Лимит аккаунта превышен (95)",
|
||||
"en": "Account limit exceeded (95)",
|
||||
"ua": "Ліміт аккаунта перевищено (95)"
|
||||
"ua": "Ліміт акаунта перевищено (95)"
|
||||
|
||||
},
|
||||
"EmailSendFailure": {
|
||||
@@ -756,12 +870,12 @@
|
||||
"LimitedUserAccount": {
|
||||
"ru": "Аккаунт с лимитом (112)",
|
||||
"en": "Limited user account (112)",
|
||||
"ua": "Аккаунт з лімітом (112)"
|
||||
"ua": "Акаунт з лімітом (112)"
|
||||
},
|
||||
"AccountDeleted": {
|
||||
"ru": "Аккаунт удален (114)",
|
||||
"en": "Account deleted (114)",
|
||||
"ua": "Аккаунт видалено (114)"
|
||||
"ua": "Акаунт видалено (114)"
|
||||
|
||||
},
|
||||
"PhoneNumberIsVOIP": {
|
||||
@@ -812,9 +926,9 @@
|
||||
"ua": "Не вдається згенерувати коди"
|
||||
},
|
||||
"InvalidStateWithStatus2": {
|
||||
"ru": "Неверное состояние (InvalidState) со статусом 2. Попробуйте привязку с помощью СМС. Если СМС придет, но ошибка повторится. Нужно попробовать еще раз.",
|
||||
"en": "Invalid state (InvalidState) with status 2. Try to attach with SMS. If SMS will come, but error will repeat. You need to try again.",
|
||||
"ua": "Невірний стан (InvalidState) зі статусом 2. Спробуйте прив'язати з допомогою СМС. Якщо СМС прийде, але помилка повториться. Потрібно спробувати ще раз."
|
||||
"ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.",
|
||||
"en": "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
|
||||
"ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна."
|
||||
},
|
||||
"GeneralFailure": {
|
||||
"ru": "General Failure",
|
||||
@@ -824,15 +938,15 @@
|
||||
}
|
||||
},
|
||||
"ExceptionHandler": {
|
||||
"SessionExpiredException": {
|
||||
"ru": "Сессия истекла. Попробуйте обновить ее через меню",
|
||||
"en": "Session expired. Try to refresh it through menu",
|
||||
"ua": "Сесія закінчилася. Спробуйте оновити її через меню"
|
||||
},
|
||||
"SessionInvalidException": {
|
||||
"ru": "Сессия невалидна. Нужно залогиниться заново",
|
||||
"en": "Session invalid. Need to login again",
|
||||
"ua": "Сесія невалідна. Потрібно залогінитися знову"
|
||||
"ru": "Сессия истекла. Попробуйте обновить ее через меню или залогиниться заново",
|
||||
"en": "Session expired. Try to refresh it through menu or login again",
|
||||
"ua": "Сесія прострочена. Спробуйте оновити її через меню або залогінитися знову"
|
||||
},
|
||||
"SessionExpiredException": {
|
||||
"ru": "Сессия полностью истекла. Нужно залогиниться заново",
|
||||
"en": "Session fully expired. Need to login again",
|
||||
"ua": "Сесія повніст'ю прострочена'. Потрібно залогінитися знову"
|
||||
},
|
||||
"TaskCanceledException": {
|
||||
"ru": "Произошла отмена операции",
|
||||
@@ -858,6 +972,23 @@
|
||||
"ru": "Неизвестная ошибка: ",
|
||||
"en": "Unknown error: ",
|
||||
"ua": "Невідома помилка: "
|
||||
},
|
||||
"CantLoadConfirmationsException": {
|
||||
"Common": {
|
||||
"ru": "Не удалось загрузить потверждения из-за ошибки Steam: ",
|
||||
"en": "Can't load confirmations due to Steam error: ",
|
||||
"ua": "Не вдалося завантажити підтвердження через помилку Steam: "
|
||||
},
|
||||
"TryAgainLater": {
|
||||
"ru": "Попробуйте позже",
|
||||
"en": "Try again later",
|
||||
"ua": "Спробуйте пізніше"
|
||||
},
|
||||
"NotSetupToReceiveConfirmations": {
|
||||
"ru": "Аккаунт не настроен на получение подтверждений",
|
||||
"en": "Account is not set up to receive confirmations",
|
||||
"ua": "Акаунт не налаштований на отримання підтверджень"
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
@@ -942,9 +1073,31 @@
|
||||
"ua": "На телефон {0} було відправлено СМС"
|
||||
},
|
||||
"EnterPhoneNumber": {
|
||||
"ru": "Введите номер телефона (без знака '+' и др. символов)",
|
||||
"en": "Enter phone number (without '+' and other symbols)",
|
||||
"ua": "Введіть номер телефону (без знака '+' та ін. символів)"
|
||||
"ru": "Введите номер телефона (без знака '+' и др. символов). По-желанию.",
|
||||
"en": "Enter phone number (without '+' and other symbols). Optional",
|
||||
"ua": "Введіть номер телефону (без знака '+' та ін. символів). За бажанням"
|
||||
}
|
||||
},
|
||||
"MAAC": {
|
||||
"TimerConfirmed": {
|
||||
"ru": "Авто: подтверждено ",
|
||||
"en": "Auto: confirmed ",
|
||||
"ua": "Авто: підтверджено "
|
||||
},
|
||||
"TimerNotConfirmed": {
|
||||
"ru": "Авто: подтверждение не сработало",
|
||||
"en": "Auto: confirmation was unsuccessful",
|
||||
"ua": "Авто: підтвердження не спрацювало"
|
||||
},
|
||||
"TimerSessionError": {
|
||||
"ru": "необходимо обновить сессию или перелогиниться",
|
||||
"en": "need to refresh session or relogin",
|
||||
"ua": "необхідно оновити сесію або перелогінитися"
|
||||
},
|
||||
"TimerPrefix": {
|
||||
"ru": "Авто ",
|
||||
"en": "Auto ",
|
||||
"ua": "Авто "
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>1.4.5.0</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.4.5/NebulaAuth.1.4.5.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.4.5.html</changelog>
|
||||
<version>1.5.4.0</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.4/NebulaAuth.1.5.4.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.4.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
</item>
|
||||
@@ -0,0 +1,74 @@
|
||||
# NebulaAuth
|
||||
|
||||
## Описание
|
||||
|
||||
<h3 align="center" style="margin-bottom:0">
|
||||
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Скачать последнюю версию</a>
|
||||
</h3>
|
||||
|
||||
<h3 align="center">NebulaAuth — это приложение для эмуляции действий из мобильного приложения Steam. Которая заменяет ваш смартфон при работе в Steam.</h3>
|
||||
|
||||
|
||||
|
||||
## Основные преимущества
|
||||
|
||||
- **Локализация на трёх языках**: английском, русском и украинском.
|
||||
- **Полная функциональность Steam Desktop Authenticator**, переосмысление [старого приложения](https://github.com/Jessecar96/SteamDesktopAuthenticator)
|
||||
- **Поддержка прокси** во всех процессах работы с аккаунтом.
|
||||
- **Группировка мафайлов** для продвинутого контроля.
|
||||
- **Автоматическое подтверждение трейдов/действий на ТП** для экономии времени.
|
||||
- **Массовый импорт мафайлов** с помощью Drag'n'Drop или CTRL+V для удобства.
|
||||
- **Настройка внешнего вида** для персонализации интерфейса.
|
||||
- **Возможность подтвердить вход в учетную запись без ввода кода** для облегчения доступа.
|
||||
- **Автообновление** программы для использования новейших функций.
|
||||
- **Автоматический повторный вход в случае проблем с сессией** для непрерывной работы.
|
||||
- **Интуитивно понятный интерфейс** с подсказками и удобствами
|
||||
- **Постоянная поддержка** кода приложения и других функций.
|
||||
|
||||
## Установка
|
||||
|
||||
1. Если приложение не запускается, необходимо установить [.net Desktop Runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.1-windows-x64-installer)
|
||||
2. [Скачать программу из релизов этого репозитория на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
|
||||
* *Для сохранности ваших данных скачивайте приложение только отсюда*
|
||||
4. Распакуйте ZIP-файл в любую папку.
|
||||
5. Запустите файл **NebulaAuth.exe**.
|
||||
|
||||
## Использование
|
||||
|
||||

|
||||
|
||||
|
||||
1. Панель управления.
|
||||
- управление файлами и настройки
|
||||
- управление аккаунтом (вход, привязка, отвязка)
|
||||
- группировка
|
||||
- выбор прокси
|
||||
- индикатор с подсказкой об используемом прокси (горит либо желтым, либо красным, при наведении отобразит дополнительную информацию)
|
||||
- таймеры для автоматического подтверждения трейдов/продаж на торговой площадке
|
||||
- интервал таймера подтверждений (в секундах)
|
||||
2. Список ваших аккаунтов
|
||||
3. Код подтверждения входа (нажмите, чтобы скопировать)
|
||||
4. Главное окно подтверждений
|
||||
5. Поиск по логину или SteamID (7xxxxxxxxxxxxx)
|
||||
6. Подтвердить вход на другогом устройстве.
|
||||
7. Гиперссылка на официальную страницу приложения с указанием авторства.
|
||||
|
||||
## Настройки
|
||||

|
||||
|
||||
|
||||
1. Режим фона. Используйте, если вы хотите отключить фон или установить собственный (поместите файл «Background.png» в папку приложения)
|
||||
2. Язык приложения
|
||||
3. Отключить таймеры при переключении между аккаунтами
|
||||
4. Скрывать в трей при сворачивании
|
||||
5. Индикатор с цветом. Маленький кружочек на значке панели задач с произвольным цветом. Полезно при использовании нескольких окон.
|
||||
6. Пользовательский цвет приложения.
|
||||
7. Текущий пароль шифрования. Если установлено, вы можете сохранять зашифрованные пароли в mafile, чтобы облегчить повторный вход в систему при проблемах с сессией. (Не рекомендуется)
|
||||
8. Режим устаревших файлов. Режим совместимости Mafile с другими клиентами (SDA и т.д.). Если установлено, приложение будет сохранять файлы в старом стандартном формате (по умолчанию: включено).
|
||||
9. Разрешить автообновление без подтверждения
|
||||
|
||||
|
||||
|
||||
## [Лицензия](/LICENSE.md)
|
||||
|
||||
Коммерческое использование запрещено. При распространении измененного кода необходимо указывать оригинальное авторство.
|
||||
@@ -0,0 +1,71 @@
|
||||
# NebulaAuth
|
||||
|
||||
## Опис
|
||||
|
||||
<h3 align="center" style="margin-bottom:0">
|
||||
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Завантажити останню версію</a>
|
||||
</h3>
|
||||
|
||||
<h3 align="center">NebulaAuth - це програма для емуляції дій з мобільного додатку Steam. Яка замінює ваш смартфон під час роботи в Steam.</h3>
|
||||
|
||||
|
||||
## Основні переваги
|
||||
|
||||
- **Локалізація трьома мовами**: англійською, російською та українською.
|
||||
- **Повна функціональність Steam Desktop Authenticator** переосмислення [старої програми](https://github.com/Jessecar96/SteamDesktopAuthenticator)
|
||||
- **Підтримка проксі** у всіх процесах роботи з обліковим записом.
|
||||
- **Угруповання мафайлів** для просунутого контролю.
|
||||
- **Автоматичне підтвердження трейдів/дій на маркеті** для економії часу.
|
||||
- **Масовий імпорт мафайлів** за допомогою Drag'n'Drop або CTRL+V для зручності.
|
||||
- **Налаштування зовнішнього вигляду** для персоналізації інтерфейсу.
|
||||
- **Можливість підтвердити вхід до облікового запису без введення коду** для полегшення доступу.
|
||||
- **Автооновлення** програми для використання новітніх функцій.
|
||||
- **Автоматичний повторний вхід у разі проблем із сесією** для безперервної роботи.
|
||||
- **Інтуїтивно зрозумілий інтерфейс** з підказками та зручностями
|
||||
- **Постійна підтримка** коду програми та інших функцій.
|
||||
|
||||
## Монтаж
|
||||
|
||||
1. Якщо програма не запускається, необхідно встановити [.NET Desktop Runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.1-windows-x64-installer)
|
||||
2. [Завантажити програму з релізів цього репозиторію на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
|
||||
* *Для збереження ваших даних завантажуйте програму тільки звідси*
|
||||
4. Розпакуйте ZIP-файл у будь-яку папку.
|
||||
5. Запустіть файл **NebulaAuth.exe**.
|
||||
|
||||
## Використання
|
||||
|
||||

|
||||
|
||||
1. Панель керування.
|
||||
- керування файлами та налаштування
|
||||
- керування акаунтом (вхід, прив'язка, відв'язування)
|
||||
- угруповання
|
||||
- вибір проксі
|
||||
- індикатор з підказкою про проксі (світиться або жовтим, або червоним, при наведенні відобразить додаткову інформацію)
|
||||
- таймери для автоматичного підтвердження трейдів/продажів на маркеті
|
||||
- інтервал таймера підтверджень (у секундах)
|
||||
2. Список ваших облікових записів
|
||||
3. Код підтвердження входу (натисніть, щоб скопіювати)
|
||||
4. Головне вікно підтвердження
|
||||
5. Пошук за логіном або SteamID (7xxxxxxxxxxxxx)
|
||||
6. Підтвердити вхід на іншому пристрою.
|
||||
7. Гіперпосилання на офіційну сторінку додатку із зазначенням авторства.
|
||||
|
||||
## Налаштування
|
||||

|
||||
|
||||
1. Режим фону. Використовуйте, якщо ви хочете вимкнути фон або встановити власний (помістіть файл "Background.png" у папку програми)
|
||||
2. Мова локалізації
|
||||
3. Вимкнути таймери під час перемикання між обліковими записами
|
||||
4. Приховувати у трей при згортанні
|
||||
5. Індикатор із кольором. Маленький кружечок на піктограмі панелі завдань з довільним кольором. Корисно при використанні кількох вікон.
|
||||
6. Власний колір програми.
|
||||
7. Поточний пароль шифрування. Якщо встановлено, ви можете зберігати зашифровані паролі в mafile, щоб полегшити повторний вхід до системи у разі проблеми з сесією. (Не рекомендується)
|
||||
8. Режим застарілих файлів. Режим сумісності Mafile з іншими клієнтами (SDA тощо). Якщо встановлено, програма зберігатиме файли у старому стандартному форматі (за замовчуванням: увімкнено).
|
||||
9. Дозволити автооновлення без підтвердження
|
||||
|
||||
|
||||
|
||||
## [Ліцензія](/LICENSE.md)
|
||||
|
||||
Комерційне використання заборонено. У разі поширення зміненого коду необхідно вказувати оригінальне авторство.
|
||||
@@ -1,34 +1,46 @@
|
||||
# NebulaAuth
|
||||
|
||||
* [Русский](README-RU.md)
|
||||
* [Українська](README-UA.md)
|
||||
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
NebulaAuth is an application for emulating actions from the Steam Mobile App. Which replaces your smartphone when operating on Steam.
|
||||
<h3 align="center" style="margin-bottom:0">
|
||||
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Download latest version</a>
|
||||
</h3>
|
||||
|
||||
<h3 align="center">NebulaAuth is an application for emulating actions from the Steam Mobile App. Which replaces your smartphone when operating on Steam. </h3>
|
||||
<h4 align="center"><a href="https://t.me/nebulaauth">Official Telegram Group</a></h4>
|
||||
|
||||
|
||||
## Main advantages
|
||||
|
||||
- **Localization in three languages**: English, Russian and Ukrainian.
|
||||
- **Full functionality of Steam Desktop Authenticator** reimagining [old app](https://github.com/Jessecar96/SteamDesktopAuthenticator)
|
||||
- **Using a proxy**
|
||||
- **Proxy support** in all account work processes.
|
||||
- **Mafile grouping** for improved management.
|
||||
- **Automatic confirmation of trades/trading platform** to save time.
|
||||
- **Bulk import of map files** via Drag'n'Drop or CTRL+V for convenience.
|
||||
- **Automatic confirmations of trades/market actions** to save time.
|
||||
- **Bulk import of .mafiles** via Drag'n'Drop or CTRL+V for convenience.
|
||||
- **Design customization** to personalize the interface.
|
||||
- **Ability to confirm account login without entering a code** for easier access.
|
||||
- **Auto-update** program to use the latest features.
|
||||
- **Automatic relogin in case of problems with the session** for continuous operation.
|
||||
- **Intuitive interface** with tips and conveniences
|
||||
- **Continious support** of application code and other features.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
1. If the application does not start, you need to install [.net desktop runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-aspnetcore-8.0.1-windows- x64-installer)
|
||||
1. If the application does not start, you need to install [.net desktop runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.1-windows-x64-installer)
|
||||
2. [Download the program from the releases of this repository on Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
|
||||
* *For the safety of your data, download the application only from here*
|
||||
4. Unpack the .zip file to any folder
|
||||
5. Run the file **NebulaAuth.exe**
|
||||
|
||||
## Usage
|
||||
|
||||
- *Main window*
|
||||
|
||||
|
||||

|
||||
|
||||
1. Control panel.
|
||||
@@ -38,13 +50,27 @@ NebulaAuth is an application for emulating actions from the Steam Mobile App. Wh
|
||||
- proxy selection
|
||||
- an indicator with a hint about the proxy used (lit either yellow or red, when hovered it will display additional information)
|
||||
- timers for automatic confirmation of trade offers/sale offers on the marketplace
|
||||
- how often to check confirmations when timers are enabled (in seconds)
|
||||
- confirmation timer interval (in seconds)
|
||||
2. List of your accounts
|
||||
3. Login confirmation code (click to copy)
|
||||
4. Main confirmation window
|
||||
5. Search by login or SteamID (7xxxxxxxxxxxxx)
|
||||
6. Confirm login from another device
|
||||
6. Confirm login on another device
|
||||
7. Hyperlink to the official application page with attribution
|
||||
|
||||
## Settings
|
||||

|
||||
|
||||
1. Background mode. Use it if you want to disable default or set custom background of application (put file 'Background.png' to your application folder)
|
||||
2. Localization language
|
||||
3. Disable timers when switching between accounts
|
||||
4. Hide to tray on minimize
|
||||
5. Indicator with color. Small ellipse on your task-bar icon with custom color. Useful when using multiple windows
|
||||
6. Custom background color of application
|
||||
7. Current encryption password. If set you can save encrypted passwords to mafile to help re-login on session troubles. (Not recommended)
|
||||
8. Legacy mafile mode. Mafile compability mode for another applications (SDA and etc). If checked application will save mafiles with old standart format (Default: checked)
|
||||
9. Allow auto-update without confirmation
|
||||
|
||||
|
||||
|
||||
## [License](/LICENSE.md)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user