Compare commits

..

38 Commits

Author SHA1 Message Date
Achies a093840b35 Merge pull request #6 from achiez/pre-release
1.5.4 Release
2024-11-10 14:44:19 +02:00
Давид Чернопятов c705caac8f 1.5.4 prepared for release
- Compatibility update is completed
- Added 'Copy SteamId' menu item in context menu of mafile
- Proxy and password automatically saved in mafile after linking
- SetCryptPasswordDialog now supports "Enter" hot-key
- Loc updates
- Added PlannedChanges.txt
- Added ClipboardHelper.cs to improved code readability
2024-11-10 14:42:49 +02:00
Давид Чернопятов ef8e12e20d 1.5.4 progress
- Mafile version was updated (3), new property SteamId added
- MafileSerializer was refactored to adjust compability and code readability.
- Serialization work moved from Storage to new class NebulaSerializer
2024-11-10 02:29:40 +02:00
Давид Чернопятов 317d8b9754 1.5.3 hot-fix: fixed loading market confirmations caused crash 2024-10-28 18:25:56 +02:00
Давид Чернопятов a6c5e01a9d 1.5.3 hot-fix: IgnorePatchTuesdayErrors checkbox in Settings bound to ViewModel 2024-10-28 17:27:38 +02:00
Achies e77a04e447 Update README.md
Added link to Telegram Group
2024-10-28 12:34:04 +02:00
Achies cddc6ea2d3 Merge pull request #5 from achiez/pre-release
Pre release 1.5.3 merge to master
2024-10-28 11:30:18 +02:00
Давид Чернопятов f00015b00f 1.5.3 update.xml 2024-10-28 11:24:47 +02:00
Давид Чернопятов 9016f96b6a Prepared for 1.5.3 Release.
- Code clean-ups
- Added "Copy File" support in mafile context menu
- Added hotkeys support to "Copy Login" and "Copy File" commands
- Added experimental feature "Ignore Patch Tuesday errors"
- Added MAAC batch control
- Added SessionID client-side generation in MafileSerializer to support mafiles without SessionID (especially from SDA)
- SplashScreen.png was slightly enhanced
2024-10-28 00:14:44 +02:00
Давид Чернопятов 06748d6f83 1.5.3 progress
- SessionHandler refactored. Now it used as global authorization API
- Added semaphore to SessionHandler
- WaitLogin dialog flow moved to SessionHandler
- MaClient and PortableMaClient authorization logic now separeted and works correctly
- Fixed App startup error was not localized and closed immediatly
- Fixed code progress bar was freezing due to low Dispatcher priority
- Fixed legacy serializer typo error in AccessToken field on write operation
2024-10-22 19:25:27 +03:00
Давид Чернопятов acf187fc6b 1.5.3 progress.
- Fixed CantLoadConfirmationsException was intercepted by error monitor
- Added localization to CantLoadConfirmationsException in ErrorHandler
- Added Copy RCode button to the Link dialog
- Fixed tooltips were messed up on Trade/Market timer's buttons
2024-10-21 13:32:45 +03:00
Давид Чернопятов 1e65cd4a06 1.5.3 progress. Added multi-confirmation and code refactorings
NebulaAuth:
- Added MAAC and PortableMaClient for multi-confirmations
- Code clean-ups, removed unused classes, refactoring of old files
- SteamLib updated
- Localization and UI updates
- Dependcies updated to the last version

LegacyConverter:
 - Added mode to decrypt mafiles
2024-10-16 16:31:53 +03:00
Achies c185533bb5 Update LinkingTroubleshooting.md 2024-10-09 22:52:12 +03:00
Achies 56ae190695 Update LinkingTroubleshooting.md 2024-10-09 22:43:14 +03:00
Achies 44382b369b Update LinkingTroubleshooting.md 2024-10-09 22:39:29 +03:00
Achies fb18d4c5f3 Update LinkingTroubleshooting.md 2024-10-09 22:25:16 +03:00
Achies 2f3055cb44 Update README-UA.md 2024-09-22 16:55:28 +03:00
Achies e3f1202eba Update README-RU.md 2024-09-22 16:55:24 +03:00
Achies a98e10f001 Update README.md 2024-09-22 16:49:23 +03:00
Achies 3385036dbe Update README-RU.md 2024-09-22 16:47:35 +03:00
Achies f618accd80 Update README-RU.md 2024-09-22 16:46:35 +03:00
Achies 787326f355 Update README.md 2024-09-22 16:41:33 +03:00
Давид Чернопятов 99be9d64c6 1.5.3 pre-release
- Added hyperlink to troubleshooting guide in Linking window
- Minor localization and design updates
2024-09-22 16:28:06 +03:00
Achies a286403ef4 Create LinkingTroubleshooting.md 2024-09-22 15:26:13 +03:00
Achies a0d1cf8ba3 Create LinkingTroubleshooting.md 2024-09-22 15:25:08 +03:00
Achies 7eab9fddd1 Create LinkingTroubleshooting.md 2024-09-22 15:23:52 +03:00
Achies 769f102d16 Merge pull request #2 from achiez/pre-release
Pre release -> Release master
2024-09-18 14:22:43 +03:00
Давид Чернопятов 80d7c09b0d Prepared for 1.5.2 release
HOTFIX: Resolved an issue introduced by the September 18, 2024, Steam update, where an error occurred during session refresh due to changes in Steam's response model.
2024-09-18 14:16:32 +03:00
Давид Чернопятов e6ae762d5a - Added splashscreen
- Added Copy Login to Context Menu
- UI and localization improvements
2024-09-15 16:45:20 +03:00
Давид Чернопятов 89ca08f6cf Pre-release performance update.
- Potentially fixed memory leak in code progress bar
- Added using statement in SteamGuardCodeGenerator on disposable HMACSHA1
- Most of packages updated to the last version
2024-09-01 02:51:35 +03:00
Давид Чернопятов e401bcccfd 1.5.1
Commit just for executing actions (see previous commit with merge for real changes)
2024-07-08 16:35:56 +03:00
Achies 195ac95b36 Merge pull request #1 from achiez/pre-release
Release 1.5.1
2024-07-08 16:30:15 +03:00
Achies d1f660381e Update build-and-release.yml 2024-07-08 16:24:38 +03:00
Давид Чернопятов c8aa7ba8e7 1.5.1 2024-07-08 16:20:28 +03:00
Achies 50a7d21d52 Update build-and-release.yml 2024-07-08 16:10:15 +03:00
Achies aa092bdd67 Create build-and-release.yml
Action to build, publish and validate release
2024-07-08 15:41:15 +03:00
Давид Чернопятов 6a1b03163c 1.5.1 progress
fixed new 407 error caused by parsing {id} as password part
2024-07-08 12:58:26 +03:00
Давид Чернопятов 9a277d07db AchiesUtilities.Web updated to 1.0.11. This version contains proxy credentials fix (407 error) 2024-05-13 18:05:57 +03:00
164 changed files with 4238 additions and 2394 deletions
+108
View File
@@ -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; }
}
+168 -36
View File
@@ -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();
}
+203
View File
@@ -0,0 +1,203 @@
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
namespace NebulaAuth.LegacyConverter;
using System;
using System.IO;
using System.Security.Cryptography;
#pragma warning disable all
#pragma warning disable SYSLIB0023
#pragma warning disable CS8603 // Possible null reference return.
#pragma warning disable SYSLIB0022
#pragma warning disable SYSLIB0041
//Resharper disable all
//Pragma is disabled because it's legacy code from SDA
/// <summary>
/// This class provides the controls that will encrypt and decrypt the *.maFile files
///
/// Passwords entered will be passed into 100k rounds of PBKDF2 (RFC2898) with a cryptographically random salt.
/// The generated key will then be passed into AES-256 (RijndalManaged) which will encrypt the data
/// in cypher block chaining (CBC) mode, and then write both the PBKDF2 salt and encrypted data onto the disk.
/// </summary>
public static class SDAEncryptor
{
private const int PBKDF2_ITERATIONS = 50000; //Set to 50k to make program not unbearably slow. May increase in future.
private const int SALT_LENGTH = 8;
private const int KEY_SIZE_BYTES = 32;
private const int IV_LENGTH = 16;
/// <summary>
/// Returns an 8-byte cryptographically random salt in base64 encoding
/// </summary>
/// <returns></returns>
public static string GetRandomSalt()
{
byte[] salt = new byte[SALT_LENGTH];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(salt);
}
return Convert.ToBase64String(salt);
}
/// <summary>
/// Returns a 16-byte cryptographically random initialization vector (IV) in base64 encoding
/// </summary>
/// <returns></returns>
public static string GetInitializationVector()
{
byte[] IV = new byte[IV_LENGTH];
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(IV);
}
return Convert.ToBase64String(IV);
}
/// <summary>
/// Generates an encryption key derived using a password, a random salt, and specified number of rounds of PBKDF2
///
/// TODO: pass in password via SecureString?
/// </summary>
/// <param name="password"></param>
/// <param name="salt"></param>
/// <returns></returns>
private static byte[] GetEncryptionKey(string password, string salt)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Password is empty");
}
if (string.IsNullOrEmpty(salt))
{
throw new ArgumentException("Salt is empty");
}
using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(salt), PBKDF2_ITERATIONS))
{
return pbkdf2.GetBytes(KEY_SIZE_BYTES);
}
}
/// <summary>
/// Tries to decrypt and return data given an encrypted base64 encoded string. Must use the same
/// password, salt, IV, and ciphertext that was used during the original encryption of the data.
/// </summary>
/// <param name="password"></param>
/// <param name="passwordSalt"></param>
/// <param name="IV">Initialization Vector</param>
/// <param name="encryptedData"></param>
/// <returns></returns>
public static string DecryptData(string password, string passwordSalt, string IV, string encryptedData)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Password is empty");
}
if (string.IsNullOrEmpty(passwordSalt))
{
throw new ArgumentException("Salt is empty");
}
if (string.IsNullOrEmpty(IV))
{
throw new ArgumentException("Initialization Vector is empty");
}
if (string.IsNullOrEmpty(encryptedData))
{
throw new ArgumentException("Encrypted data is empty");
}
byte[] cipherText = Convert.FromBase64String(encryptedData);
byte[] key = GetEncryptionKey(password, passwordSalt);
string plaintext = null;
using (RijndaelManaged aes256 = new RijndaelManaged())
{
aes256.IV = Convert.FromBase64String(IV);
aes256.Key = key;
aes256.Padding = PaddingMode.PKCS7;
aes256.Mode = CipherMode.CBC;
//create decryptor to perform the stream transform
ICryptoTransform decryptor = aes256.CreateDecryptor(aes256.Key, aes256.IV);
//wrap in a try since a bad password yields a bad key, which would throw an exception on decrypt
try
{
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
catch (CryptographicException)
{
plaintext = null;
}
}
return plaintext;
}
/// <summary>
/// Encrypts a string given a password, salt, and initialization vector, then returns result in base64 encoded string.
///
/// To retrieve this data, you must decrypt with the same password, salt, IV, and cyphertext that was used during encryption
/// </summary>
/// <param name="password"></param>
/// <param name="passwordSalt"></param>
/// <param name="IV"></param>
/// <param name="plaintext"></param>
/// <returns></returns>
public static string EncryptData(string password, string passwordSalt, string IV, string plaintext)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentException("Password is empty");
}
if (string.IsNullOrEmpty(passwordSalt))
{
throw new ArgumentException("Salt is empty");
}
if (string.IsNullOrEmpty(IV))
{
throw new ArgumentException("Initialization Vector is empty");
}
if (string.IsNullOrEmpty(plaintext))
{
throw new ArgumentException("Plaintext data is empty");
}
byte[] key = GetEncryptionKey(password, passwordSalt);
byte[] ciphertext;
using (RijndaelManaged aes256 = new RijndaelManaged())
{
aes256.Key = key;
aes256.IV = Convert.FromBase64String(IV);
aes256.Padding = PaddingMode.PKCS7;
aes256.Mode = CipherMode.CBC;
ICryptoTransform encryptor = aes256.CreateEncryptor(aes256.Key, aes256.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncypt = new StreamWriter(csEncrypt))
{
swEncypt.Write(plaintext);
}
ciphertext = msEncrypt.ToArray();
}
}
}
return Convert.ToBase64String(ciphertext);
}
}
+5
View File
@@ -21,6 +21,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
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
+2 -1
View File
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=MAAC/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+1 -5
View File
@@ -1,9 +1,7 @@
<Application x:Class="NebulaAuth.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NebulaAuth"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:background="clr-namespace:NebulaAuth.Converters.Background"
@@ -15,13 +13,12 @@
<FontFamily x:Key="RobotoSymbols">pack://application:,,,/Fonts/Roboto/#Roboto Symbols</FontFamily>
<converters:CoefficientConverter x:Key="CoefficientConverter"/>
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter"/>
<converters:OnOffBoolTextConverter x:Key="OnOffBoolTextConverter"/>
<converters:SelectedProxyTextConverter x:Key="SelectedProxyTextConverter"/>
<converters:ProxyTextConverter x:Key="ProxyTextConverter"/>
<converters:ProxyDataTextConverter x:Key="ProxyDataTextConverter"/>
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter"/>
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter"/>
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter"/>
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter"/>
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter"/>
@@ -34,7 +31,6 @@
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="LightGreen" SecondaryColor="Purple" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignExtensions;component/Themes/Generic.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.snackbar.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.Dark.xaml" />
+5 -5
View File
@@ -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;
}
}
}
@@ -7,10 +7,10 @@ namespace NebulaAuth.Converters;
public class AnyMafilesToVisibilityConverter : IValueConverter
{
private static bool EverAnyMafiles;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
private static bool _everAnyMafiles;
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (EverAnyMafiles)
if (_everAnyMafiles)
{
return Visibility.Collapsed;
}
@@ -19,12 +19,12 @@ public class AnyMafilesToVisibilityConverter : IValueConverter
return Visibility.Visible;
}
EverAnyMafiles = true;
_everAnyMafiles = true;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
@@ -8,13 +8,13 @@ namespace NebulaAuth.Converters.Background;
public class BackgroundImageVisibleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value is not BackgroundMode.Color ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
@@ -9,7 +9,7 @@ namespace NebulaAuth.Converters.Background;
public class BackgroundSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is BackgroundMode.Custom)
{
@@ -21,8 +21,8 @@ public class BackgroundSourceConverter : IValueConverter
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg"));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
@@ -7,13 +7,16 @@ namespace NebulaAuth.Converters;
[ValueConversion(typeof(double), typeof(double))]
public class CoefficientConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return System.Convert.ToDouble(value) * System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return (double)value / (double)parameter;
var first = value as double? ?? 0;
var second = parameter as double? ?? 0;
if (second == 0) second = 1;
return first / second;
}
}
@@ -13,6 +13,6 @@ public class MultiCommandParameterConverter : IMultiValueConverter
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
@@ -1,23 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class OnOffBoolTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not bool b)
{
throw new InvalidCastException($"Can't cast value {value} to 'bool'");
}
return b ? "вкл" : "выкл";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,24 @@
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using System;
namespace NebulaAuth.Converters;
public class PortableMaClientStatusToColorConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is false)
{
return new SolidColorBrush(Color.FromRgb(187, 224, 139));
}
return new SolidColorBrush(Color.FromRgb(224, 139, 139));
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
+6 -6
View File
@@ -8,7 +8,7 @@ namespace NebulaAuth.Converters;
public class ProxyTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not MaProxy p)
{
@@ -18,15 +18,15 @@ public class ProxyTextConverter : IValueConverter
return $"{p.Id}: {p.Data.Address}:{p.Data.Port}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
public class ProxyDataTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not ProxyData p)
{
@@ -36,8 +36,8 @@ public class ProxyDataTextConverter : IValueConverter
return $"{p.Address}:{p.Port}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
throw new NotSupportedException();
}
}
@@ -6,13 +6,23 @@ namespace NebulaAuth.Converters;
public class ReverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return !(bool)value;
if (value is bool boolValue)
{
return !boolValue;
}
throw new ArgumentException("Value must be of type bool", nameof(value));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return !(bool)value;
if (value is bool boolValue)
{
return !boolValue;
}
throw new ArgumentException("Value must be of type bool", nameof(value));
}
}
}
@@ -1,26 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.Converters;
public class SelectedProxyTextConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is not MaProxy proxy)
{
return string.Empty;
}
var proxyExist = (bool)values[1];
return proxyExist ? $"{proxy.Id}: {proxy.Data.Address}" : "";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
+3 -3
View File
@@ -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
View File
@@ -5,7 +5,6 @@ using NebulaAuth.Model.Entities;
using NebulaAuth.View;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel.Other;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
namespace NebulaAuth.Core;
+4
View File
@@ -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)
{
+2
View File
@@ -24,7 +24,9 @@ public class SnackbarController
///
/// </summary>
/// <param name="text"></param>
/// <param name="action"></param>
/// <param name="duration">Default duration is 1 second</param>
/// <param name="actionText">Default: 'OK'</param>
public static void SendSnackbarWithButton(string text, string actionText = "OK", Action? action = null, TimeSpan? duration = null)
{
duration ??= GetSnackbarTime(text);
+14 -8
View File
@@ -1,28 +1,30 @@
using System;
using NebulaAuth.Model;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Forms;
using NebulaAuth.Model;
using Application = System.Windows.Application;
namespace NebulaAuth.Core;
public static class TrayManager
{
private static NotifyIcon _notifyIcon;
private static NotifyIcon? _notifyIcon;
private static readonly Window MainWindow = Application.Current.MainWindow!;
public static bool IsEnabled => Settings.Instance.HideToTray;
private static bool IsEnabled => Settings.Instance.HideToTray;
[MemberNotNullWhen(true, nameof(_notifyIcon))]
private static bool Init { get; set; }
public static void InitializeTray()
{
_notifyIcon = new NotifyIcon();
_notifyIcon.Text = "NebulaAuth";
Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
var iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
_notifyIcon.Icon = new Icon(iconStream);
_notifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick;
var contextMenu = new ContextMenuStrip();
@@ -32,6 +34,7 @@ public static class TrayManager
_notifyIcon.ContextMenuStrip = contextMenu;
MainWindow.StateChanged += MainWindow_StateChanged;
Init = true;
}
private static void OnExitClick(object? sender, EventArgs e)
@@ -57,6 +60,7 @@ public static class TrayManager
private static void ShowMainWindow()
{
if (!Init) return;
MainWindow.Show();
MainWindow.WindowState = WindowState.Normal;
_notifyIcon.Visible = false;
@@ -64,13 +68,15 @@ public static class TrayManager
private static void HideMainWindow()
{
if(IsEnabled == false) return;
if (!Init) return;
if (IsEnabled == false) return;
_notifyIcon.Visible = true;
MainWindow.Hide();
}
private static void ExitApplication()
{
if (!Init) return;
_notifyIcon.Dispose();
Application.Current.Shutdown();
}
+42 -49
View File
@@ -1,14 +1,7 @@
using AutoUpdaterDotNET;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model;
using NebulaAuth.View;
using NebulaAuth.ViewModel.Other;
using System;
using System.IO;
using System.Net;
using System.Windows.Forms;
using System.Windows.Threading;
using Application = System.Windows.Application;
namespace NebulaAuth.Core;
@@ -29,55 +22,55 @@ public static class UpdateManager
}
static UpdateManager()
{
//AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
//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)
};
//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();
// await DialogHost.Show(dialog);
// Application.Current.Shutdown();
}
else
{
// }
// else
// {
}
}
else
{
if (args.Error is WebException)
{
// }
// }
// else
// {
// if (args.Error is WebException)
// {
}
else
{
// }
// else
// {
}
}
// }
// }
}
//}
private static void RunUpdate(UpdateInfoEventArgs args)
{
Application.Current.Dispatcher.Invoke(() =>
{
if (AutoUpdater.DownloadUpdate(args))
{
Application.Current.Shutdown();
}
}, DispatcherPriority.ContextIdle);
}
//private static void RunUpdate(UpdateInfoEventArgs args)
//{
// Application.Current.Dispatcher.Invoke(() =>
// {
// if (AutoUpdater.DownloadUpdate(args))
// {
// Application.Current.Shutdown();
// }
// }, DispatcherPriority.ContextIdle);
//}
}
+97 -31
View File
@@ -25,7 +25,7 @@
</b:Interaction.Behaviors>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=CopyMafileFromBufferCommand}"
<KeyBinding Command="{Binding Path=PasteMafilesFromClipboardCommand}"
Key="V"
Modifiers="Control"/>
</Window.InputBindings>
@@ -44,27 +44,27 @@
<TextBlock FontWeight="Bold" Foreground="#9a65b8" Text="{Tr MainWindow.Global.DragNDropHint}"/>
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36" Kind="FileReplaceOutline" />
</StackPanel>
<ToolBarTray IsLocked="True" Grid.Row="0">
<ToolBarTray IsLocked="True" Grid.Row="0" >
<ToolBarTray.Background>
<SolidColorBrush Opacity="0.6" Color="#FF1A1C25" />
</ToolBarTray.Background>
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.75" w:FontScaleWindow.ResizeFont="True">
<Menu>
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.File.Import}" Command="{Binding AddMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}" Command="{Binding OpenMafileFolderCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}" Command="{Binding OpenSettingsDialogCommand}" />
</MenuItem>
</Menu>
<Menu>
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}" Command="{Binding LinkAccountCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}" Command="{Binding RemoveAuthenticatorCommand}"/>
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}" Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
</MenuItem>
</Menu>
<Separator />
@@ -85,7 +85,15 @@
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}" CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
</UIElement.InputBindings>
</ComboBox>
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}" MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}" md:ComboBoxAssist.ShowSelectedItem="False" SelectedItem="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}"
MinWidth="80"
Margin="8,0,8,0"
VerticalAlignment="Center"
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
md:ComboBoxAssist.ShowSelectedItem="False"
SelectedItem="{Binding SelectedProxy}"
ItemsSource="{Binding Proxies}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
@@ -118,10 +126,29 @@
</Binding>
</UIElement.Visibility>
</md:PackIcon>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" ></md:PackIcon>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" IsChecked="{Binding TradeTimerEnabled}" Content="{Tr MainWindow.AppBar.TradeTimerHint}"/>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" IsChecked="{Binding MarketTimerEnabled}" Content="{Tr MainWindow.AppBar.MarketTimerHint}"/>
<TextBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding MarketTimerEnabled}" >
<md:PackIcon Kind="ShoppingCart"/>
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}" Command="{Binding Path=SwitchMAACOnGroupCommand}" CommandParameter="{StaticResource True}"/>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}" Command="{Binding Path=SwitchMAACOnAllCommand}" CommandParameter="{StaticResource True}"/>
</ContextMenu>
</FrameworkElement.ContextMenu>
</ToggleButton>
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}" Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}" IsEnabled="{Binding IsMafileSelected}" Margin="2" Style="{StaticResource MaterialDesignFlatToggleButton}" IsChecked="{Binding TradeTimerEnabled}" >
<md:PackIcon Kind="AccountArrowRight" />
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}" Command="{Binding Path=SwitchMAACOnGroupCommand}" CommandParameter="{StaticResource False}"/>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}" Command="{Binding Path=SwitchMAACOnAllCommand}" CommandParameter="{StaticResource False}"/>
</ContextMenu>
</FrameworkElement.ContextMenu>
</ToggleButton>
<TextBox md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}" HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}" Foreground="WhiteSmoke" VerticalAlignment="Center" Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
<md:PackIcon Kind="Accounts" />
</ToggleButton>
</ToolBar>
</ToolBarTray>
<Grid Row="1">
@@ -134,14 +161,53 @@
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="10,15,10,15" DisplayMemberPath="AccountName" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Grid.Row="0" Margin="10,15,0,0" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch" Text="{Binding AccountName}">
<TextBlock.Foreground>
<Binding Mode="OneWay" Path="LinkedClient.IsError" Converter="{StaticResource PortableMaClientStatusToColorConverter}">
<Binding.FallbackValue>
<SolidColorBrush Color="WhiteSmoke"/>
</Binding.FallbackValue>
</Binding>
</TextBlock.Foreground>
</TextBlock>
<StackPanel Visibility="{Binding LinkedClient, Converter={StaticResource NullableToVisibilityConverter}}" Grid.Column="1" Orientation="Horizontal">
<md:PackIcon Kind="ShoppingCart" Visibility="{Binding LinkedClient.AutoConfirmMarket, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"/>
<md:PackIcon Kind="AccountArrowRight" Visibility="{Binding LinkedClient.AutoConfirmTrades, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.InputBindings>
<KeyBinding Key="C" Modifiers="Control" Command="{Binding DataContext.CopyLoginCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
<KeyBinding Key="X" Modifiers="Control" Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
</ListBox.InputBindings>
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem 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 />
@@ -155,13 +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>
<TextBlock Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}, Mode=OneWay}" Margin="5" FontSize="16" Grid.Row="0" TextWrapping="WrapWithOverflow" Text="{Tr MainWindow.Global.StartTip}">
<TextBlock.Background>
<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"/>
</TextBlock.Background>
</TextBlock>
<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.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>
@@ -179,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 />
@@ -188,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>
@@ -215,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>
+4 -36
View File
@@ -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; }
+25 -4
View File
@@ -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);
}
}
+199
View File
@@ -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();
}
}
+27 -91
View File
@@ -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
};
}
+90
View File
@@ -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);
}
}
}
+12 -7
View File
@@ -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;
}
+51 -23
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
@@ -11,13 +12,15 @@ 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 ProxyScheme DefaultScheme = new ProxyScheme(
ProxyDefaultFormats.UniversalHostFirstColonDelimiter, false, ProxyProtocol.HTTP,
ProxyPatternProtocol.HTTP | ProxyPatternProtocol.HTTPs,
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4, PatternRequirement.Optional,
public static readonly ProxyParser DefaultScheme = new(
ProxyDefaultFormats.UniversalColon, false, ProxyProtocol.HTTP,
ProxyPatternProtocol.All,
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4,
PatternRequirement.Optional,
PatternRequirement.Optional);
@@ -32,7 +35,7 @@ public static class ProxyStorage
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)
@@ -48,16 +51,13 @@ public static class ProxyStorage
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;
}
@@ -71,16 +71,46 @@ public static class ProxyStorage
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.Address == proxyData2.Address && proxyData1.Port == proxyData2.Port;
}
public static void Save()
{
@@ -94,7 +124,7 @@ public static class ProxyStorage
return proxyData.AuthEnabled ? proxyData.ToString(FORMAT) : proxyData.ToString(ADDRESS_FORMAT);
}
private static Proxies Create()
private static ProxiesSchema Create()
{
int? def = null;
if (MaClient.DefaultProxy != null)
@@ -106,18 +136,16 @@ public static class ProxyStorage
}
}
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;
}
}
+166 -62
View File
@@ -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
}
}
});
}
}
+55
View File
@@ -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);
}
}
+1 -1
View File
@@ -12,7 +12,6 @@ public partial class Settings : ObservableObject
{
#region Properties
[ObservableProperty] private bool _disableTimersOnChange = true;
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
[ObservableProperty] private bool _hideToTray;
[ObservableProperty] private int _timerSeconds = 60;
@@ -23,6 +22,7 @@ public partial class Settings : ObservableObject
[ObservableProperty] private bool _legacyMode = true;
[ObservableProperty] private bool _allowAutoUpdate;
[ObservableProperty] private bool _useAccountNameAsMafileName;
[ObservableProperty] private bool _ignorePatchTuesdayErrors;
#endregion
public static Settings Instance { get; }
+1 -1
View File
@@ -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();
+30 -103
View File
@@ -1,20 +1,20 @@
using NebulaAuth.Model.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SteamLib;
using SteamLib.Account;
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.DirectoryServices.ActiveDirectory;
using System.IO;
using System.Linq;
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";
@@ -43,21 +43,20 @@ public static class Storage
var hashIds = new HashSet<SteamId>();
var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
var ordered = files.Order(comparer).ToList();
foreach (var file in ordered)
foreach (var file in ordered.Where(file => Path.GetExtension(file).EqualsIgnoreCase(".mafile")))
{
if (Path.GetExtension(file).Equals(".mafile", StringComparison.OrdinalIgnoreCase) == false) continue;
try
{
var data = ReadMafile(file);
if (hashNames.Contains(data.AccountName) || (data.SessionData != null && hashIds.Contains(data.SessionData.SteamId)))
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.SessionData.SteamId);
if (data.SessionData != null) hashIds.Add(data.SteamId);
MaFiles.Add(data);
}
catch (Exception ex)
@@ -69,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;
@@ -78,31 +84,23 @@ 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(CreatePathForMafile(data)))
{
throw new IOException("File already exist and overwrite is False");
@@ -116,61 +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)
{
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);
}
}
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 = CreatePathForMafile(data);
var str = SerializeMafile(data);
var str = NebulaSerializer.SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
@@ -188,23 +138,14 @@ public static class Storage
public static void UpdateMafile(Mafile data)
{
var path = CreatePathForMafile(data);
var str = SerializeMafile(data);
var str = NebulaSerializer.SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
}
public static void RemoveMafile(Mafile data)
{
var path = CreatePathForMafile(data);
if (File.Exists(path))
{
File.Delete(path);
}
MaFiles.Remove(data);
}
public static void MoveToRemoved(Mafile data)
{
var path = CreatePathForMafile(data);
var copyPath = Path.Combine(REMOVED_F, data.SessionData!.SteamId + ".mafile");
var copyPath = Path.Combine(REMOVED_F, data.SteamId + ".mafile");
var copyPathCompleted = copyPath;
var i = 0;
while (File.Exists(copyPathCompleted))
@@ -219,18 +160,9 @@ public static class Storage
private static string CreatePathForMafile(Mafile data)
{
string fileName;
if (Settings.Instance.UseAccountNameAsMafileName)
{
fileName = CreateFileNameWithAccountName(data.AccountName);
}
else
{
if(data.SessionData == null)
throw new NullReferenceException("SessionData was null can't retrieve SteamId"); //FIXME: think about better way to handle
fileName = CreateFileNameWithSteamId(data.SessionData.SteamId);
}
var fileName = Settings.Instance.UseAccountNameAsMafileName
? CreateFileNameWithAccountName(data.AccountName)
: CreateFileNameWithSteamId(data.SteamId);
return Path.Combine(MafileFolder, fileName);
}
@@ -239,13 +171,12 @@ public static class Storage
private static string CreateFileNameWithSteamId(SteamId steamId) => steamId.Steam64.Id + ".mafile";
public static string? TryFindMafilePath(Mafile data)
//FIXME: write mode to mafile instead of searching it. Search sometimes represents not actual mafile (in case of duplicate steamId + accountName)
{
var pathFileName = Path.Combine(MafileFolder, CreateFileNameWithAccountName(data.AccountName));
string? pathSteamId = null;
if (data.SessionData != null)
{
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SessionData.SteamId));
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SteamId));
}
var steamIdExist = pathSteamId != null && File.Exists(pathSteamId);
@@ -261,18 +192,14 @@ public static class Storage
}
return null;
}
public static bool ValidateCanSave(Mafile data)
{
return Settings.Instance.UseAccountNameAsMafileName || data.SessionData != 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;
private static readonly IComparer<string> DefaultComparer = Comparer<string>.Default;
public MafileNameComparer(bool mafileNameMode)
{
MafileNameMode = mafileNameMode;
@@ -301,7 +228,7 @@ internal class MafileNameComparer : IComparer<string>
}
}
return _defaultComparer.Compare(x, y);
return DefaultComparer.Compare(x, y);
}
}
-58
View File
@@ -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>
+15 -14
View File
@@ -10,26 +10,28 @@
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.4.9</AssemblyVersion>
<AssemblyVersion>1.5.4</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Remove="Theme\1140x641.jpg" />
<None Remove="Theme\Background.jpg" />
<None Remove="Theme\lock.ico" />
<None Remove="Theme\SplashScreen.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.5" />
<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="4.0.0-a02" />
<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>
@@ -40,12 +42,11 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Model\Exceptions\" />
<Folder Include="Utility\" />
</ItemGroup>
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
<SplashScreen Include="Theme\SplashScreen.png" />
</ItemGroup>
<ItemGroup>
+9
View File
@@ -0,0 +1,9 @@
• Есть люди которым нужна функция просмотра и завершения сессий аккаунта. Скорее всего в следующей версии это будет реализовано
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
• Ускорить появление подсказок в интерфейсе
• Добавить запоминание пароля при привязке мафайла
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
• Добавить полное шифрование мафайлов по аналогии с SDA
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
+4 -4
View File
@@ -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>
+2 -2
View File
@@ -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()
{
+5 -11
View File
@@ -1,15 +1,9 @@
using System.Windows.Controls;
namespace LolzFucker.Theme;
namespace LolzFucker.Theme
public partial class Palette
{
/// <summary>
/// Логика взаимодействия для Palette.xaml
/// </summary>
public partial class Palette : UserControl
public Palette()
{
public Palette()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
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.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

+60
View File
@@ -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;
}
}
+12 -2
View File
@@ -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:
+28 -28
View File
@@ -5,7 +5,7 @@
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
>
xmlns:vm="clr-namespace:NebulaAuth.ViewModel">
<DataTemplate DataType="{x:Type confirmations:Confirmation}">
@@ -18,15 +18,15 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="QuestionMark" Margin="0,0,10,0"/>
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Binding TypeName}"></TextBlock>
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Binding TypeName}" />
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -43,21 +43,21 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0"></materialDesign:PackIcon>
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0"></Image>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0" />
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0" />
<TextBlock VerticalAlignment="Center" Grid.Column="2" TextWrapping="WrapWithOverflow" >
<Run Text="{Tr MainWindow.ConfirmationTemplates.TradeWith, IsDynamic=False}"/>
<Run Text="{Binding UserName}" FontWeight="Bold"></Run>
<Run Text="{Binding UserName}" FontWeight="Bold" />
</TextBlock>
<materialDesign:PackIcon Grid.Column="3" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Kind="Gift" Visibility="{Binding IsReceiveNothing, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -76,12 +76,12 @@
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.Recovery}"/>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -98,14 +98,14 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0"></materialDesign:PackIcon>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0" />
<Border Grid.Column="1" Background="{DynamicResource MaterialDesignPaper}" MaxHeight="36" HorizontalAlignment="Center" BorderBrush="{DynamicResource PrimaryHueMidBrush}" BorderThickness="0.6" Margin="0,0,10,0">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" ></Image>
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" />
</Border>
<Grid Grid.Column="2">
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True" TextWrapping="WrapWithOverflow" Text="{Binding ItemName}"/>
<TextBlock Grid.Row="1" Foreground="LightGray" Text="{Binding PriceString}">
<TextBlock Foreground="LightGray" Text="{Binding PriceString}">
<TextBlock.FontSize>
<Binding ElementName="ItemName" Path="FontSize" ConverterParameter="0.7">
<Binding.Converter>
@@ -118,12 +118,12 @@
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -139,15 +139,15 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="KeyLink" Margin="0,0,10,0"/>
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}"></TextBlock>
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}" />
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -162,7 +162,7 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0"></materialDesign:PackIcon>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0" />
<TextBlock VerticalAlignment="Center" Grid.Column="1">
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market, IsDynamic=False}"/>
<Run Text="{Binding Confirmations.Count, Mode=OneWay}"/>
@@ -171,12 +171,12 @@
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.ConfirmCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Check" />
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.(vm:MainVM.CancelCommand)}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
@@ -6,7 +6,6 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="400"
Height="Auto" Width="Auto" MaxWidth="500"
Background="{DynamicResource WindowBackground}">
<Grid Margin="15">
@@ -1,21 +1,18 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs;
namespace NebulaAuth.View.Dialogs
/// <summary>
/// Логика взаимодействия для ConfirmCancelDialog.xaml
/// </summary>
public partial class ConfirmCancelDialog
{
/// <summary>
/// Логика взаимодействия для ConfirmCancelDialog.xaml
/// </summary>
public partial class ConfirmCancelDialog : UserControl
public ConfirmCancelDialog()
{
public ConfirmCancelDialog()
{
InitializeComponent();
}
public ConfirmCancelDialog(string msg)
{
InitializeComponent();
ConfirmTextBlock.Text = msg;
}
InitializeComponent();
}
}
public ConfirmCancelDialog(string msg)
{
InitializeComponent();
ConfirmTextBlock.Text = msg;
}
}
@@ -7,11 +7,9 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:model="clr-namespace:NebulaAuth.Model"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
mc:Ignorable="d"
d:DesignHeight="250" d:DesignWidth="800"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
Foreground="WhiteSmoke" Cursor="Hand"
Foreground="WhiteSmoke"
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
Background="{DynamicResource WindowBackground}">
@@ -26,7 +24,7 @@
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
<CheckBox Grid.Row="2" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
<Grid Grid.Row="3" Margin="10,10,10,0">
<Grid.ColumnDefinitions>
@@ -1,15 +1,12 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs;
namespace NebulaAuth.View.Dialogs
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainDialog
{
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainDialog : UserControl
public LoginAgainDialog()
{
public LoginAgainDialog()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
@@ -9,9 +9,8 @@
xmlns:model="clr-namespace:NebulaAuth.Model"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
mc:Ignorable="d"
d:DesignHeight="250" d:DesignWidth="800"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
Foreground="WhiteSmoke" Cursor="Hand"
Foreground="WhiteSmoke"
d:DataContext="{d:DesignInstance other:LoginAgainOnImportVM}"
Background="{DynamicResource WindowBackground}">
@@ -28,7 +27,7 @@
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
<ComboBox ToolTip="{Tr LoginAgainDialog.ProxyToolTip}" Grid.Row="2" Margin="10,18,10,0" materialDesign:HintAssist.Hint="{Tr Common.Proxy}" ItemsSource="{Binding Proxies}" SelectedItem="{Binding SelectedProxy}" >
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
@@ -1,15 +1,12 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs;
namespace NebulaAuth.View.Dialogs
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainOnImportDialog
{
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainOnImportDialog
public LoginAgainOnImportDialog()
{
public LoginAgainOnImportDialog()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
@@ -2,14 +2,12 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NebulaAuth.View.Dialogs"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:model="clr-namespace:NebulaAuth.Model"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Foreground="WhiteSmoke" Cursor="Hand"
Foreground="WhiteSmoke"
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
Background="{DynamicResource WindowBackground}">
@@ -21,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();
}
}
}
+3 -4
View File
@@ -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>
+59 -60
View File
@@ -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);
}
}
+38 -23
View File
@@ -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>
+8 -11
View File
@@ -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();
}
}
}
+10 -17
View File
@@ -2,12 +2,11 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NebulaAuth.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DesignHeight="500" d:DesignWidth="400"
Foreground="WhiteSmoke"
FontFamily="{md:MaterialDesignFont}"
MinHeight="500"
@@ -15,12 +14,6 @@
MaxHeight="550"
d:DataContext="{d:DesignInstance other:ProxyManagerVM}"
Background="{DynamicResource WindowBackground}">
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@@ -36,11 +29,11 @@
</Grid.ColumnDefinitions>
<TextBlock Margin="10" FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}"/>
<Button Margin="0,0,10,0" IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static md:DialogHost.CloseDialogCommand}">
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></md:PackIcon>
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1"></Separator>
<Separator Grid.Row="1" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -53,8 +46,8 @@
<Run Text="{Binding DefaultProxy.Value, Converter='{StaticResource ProxyDataTextConverter}', Mode=OneWay, FallbackValue=''}"/>
</TextBlock>
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}" Cursor="Hand">
<md:PackIcon Kind="ClearBox" Width="20" Height="20"></md:PackIcon>
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
</Button>
</Grid>
<md:Card Grid.Row="3" Margin="10">
@@ -98,7 +91,7 @@
<Button Style="{StaticResource MaterialDesignIconButton}" Padding="0" Width="24" Height="24" md:RippleAssist.IsDisabled="True" Grid.Column="1" HorizontalAlignment="Right"
Command="{Binding DataContext.SetDefaultCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}">
<md:PackIcon Height="16" Width="16" Kind="Heart"></md:PackIcon>
<md:PackIcon Height="16" Width="16" Kind="Heart" />
</Button>
</Grid>
@@ -113,12 +106,12 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True" AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}" Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}"></TextBox>
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True" AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}" Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}" />
<Button IsDefault="True" Grid.Column="1" Command="{Binding AddProxyCommand}">
<md:PackIcon Kind="Add"></md:PackIcon>
<md:PackIcon Kind="Add" />
</Button>
<Button Grid.Column="2" Command="{Binding RemoveProxyCommand}">
<md:PackIcon Kind="Trash"></md:PackIcon>
<md:PackIcon Kind="Trash" />
</Button>
</Grid>
+8 -11
View File
@@ -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();
}
}
}
+11 -16
View File
@@ -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,17 +30,16 @@
</Grid.ColumnDefinitions>
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr SettingsDialog.Title}"/>
<Button IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></materialDesign:PackIcon>
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1" Margin="0,10,0,0" />
<StackPanel Grid.Row="2" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" ></ComboBox>
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" ></ComboBox>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding DisableTimersOnChange}" Content="{Tr SettingsDialog.DisableTimersOnSwitch}"/>
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" />
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" />
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}"/>
<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,10,0,5" FontSize="16" IsChecked="{Binding UseBackground}" Content="{Tr SettingsDialog.UseCustomColor}"/>
@@ -58,16 +52,17 @@
<PasswordBox MinWidth="250" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Height="Auto" VerticalAlignment="Center" FontSize="16" Margin="0,10,0,0"
Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
materialDesign:HintAssist.Hint="{Tr SettingsDialog.PasswordBox.CurrentCryptPassword}"
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}"></PasswordBox>
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" />
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom" Grid.Column="1"> <materialDesign:PackIcon Kind="ContentSave"/></Button>
</Grid>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding LegacyMode}" Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
<!--<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>-->
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseAccountNameAsMafileName}" >
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
</CheckBox>
<CheckBox IsChecked="{Binding IgnorePatchTuesdayErrors}" Margin="0,10,0,0" FontSize="16" ToolTip="{Tr SettingsDialog.IgnorePatchTuesdayErrorsHint}">
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.IgnorePatchTuesdayErrors}" />
</CheckBox>
</StackPanel>
+8 -19
View File
@@ -1,23 +1,12 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace NebulaAuth.View;
namespace NebulaAuth.View
/// <summary>
/// Логика взаимодействия для SettingsView.xaml
/// </summary>
public partial class SettingsView
{
/// <summary>
/// Логика взаимодействия для SettingsView.xaml
/// </summary>
public partial class SettingsView : UserControl
public SettingsView()
{
public SettingsView()
{
InitializeComponent();
}
private void ColorPicker_OnColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
{
Debug.WriteLine(e.NewValue);
}
InitializeComponent();
}
}
}
+2 -6
View File
@@ -2,14 +2,10 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NebulaAuth.View"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
d:DesignHeight="700"
d:DesignWidth="500"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
d:DataContext="{d:DesignInstance other:UpdaterVM}"
@@ -31,7 +27,7 @@
</Grid.RowDefinitions>
<TextBlock FontSize="24" Text="Обновления"/>
<Separator Grid.Row="1"></Separator>
<Separator Grid.Row="1" />
<TextBlock FontSize="16" Margin="5,10,0,10" TextWrapping="WrapWithOverflow" Grid.Row="2" >
<Run Text="Доступна новая версия:"/>
+8 -24
View File
@@ -1,28 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace NebulaAuth.View;
namespace NebulaAuth.View
/// <summary>
/// Логика взаимодействия для UpdaterView.xaml
/// </summary>
public partial class UpdaterView
{
/// <summary>
/// Логика взаимодействия для UpdaterView.xaml
/// </summary>
public partial class UpdaterView : UserControl
public UpdaterView()
{
public UpdaterView()
{
InitializeComponent();
}
InitializeComponent();
}
}
}
+28 -20
View File
@@ -1,19 +1,19 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using JetBrains.Annotations;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using NebulaAuth.View.Dialogs;
using SteamLib.Exceptions;
using SteamLib.SteamMobile;
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NebulaAuth.View.Dialogs;
namespace NebulaAuth.ViewModel;
@@ -21,8 +21,9 @@ public partial class MainVM : ObservableObject
{
[ObservableProperty]
private ObservableCollection<Mafile> _maFiles = Storage.MaFiles;
public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
[UsedImplicitly]
public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
public Mafile? SelectedMafile
@@ -32,39 +33,40 @@ public partial class MainVM : ObservableObject
}
private Mafile? _selectedMafile;
public bool IsMafileSelected => SelectedMafile != null;
public MainVM()
{
CreateCodeTimer();
_confirmTimer = new Timer(ConfirmByTimer, null, TimeSpan.FromSeconds(_timerCheckSeconds), TimeSpan.FromSeconds(_timerCheckSeconds));
Proxies = new ObservableCollection<MaProxy>(ProxyStorage.Proxies.Select(kvp =>
new MaProxy(kvp.Key, kvp.Value)));
Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged;
QueryGroups();
SessionHandler.LoginStarted += SessionHandlerOnLoginStarted;
SessionHandler.LoginCompleted += SessionHandlerOnLoginCompleted;
UpdateManager.CheckForUpdates();
if (Storage.DuplicateFound > 0)
{
SnackbarController.SendSnackbar(
GetLocalizationOrDefault("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
GetLocalization("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
TimeSpan.FromSeconds(4));
}
}
private void SetMafile(Mafile? mafile)
{
if (mafile != SelectedMafile)
{
_selectedMafile = mafile;
OnPropertyChanged(nameof(SelectedMafile));
OnPropertyChanged(nameof(TradeTimerEnabled));
OnPropertyChanged(nameof(MarketTimerEnabled));
MaClient.SetAccount(mafile);
OnPropertyChanged(nameof(ConfirmationsVisible));
if (Settings.DisableTimersOnChange) OffTimer(dispatcher: false);
SetCurrentProxy();
OnPropertyChanged(nameof(IsDefaultProxy));
if (mafile != null) Code = SteamGuardCodeGenerator.GenerateCode(mafile.SharedSecret);
OnPropertyChanged(nameof(IsMafileSelected));
}
}
@@ -81,14 +83,14 @@ public partial class MainVM : ObservableObject
{
return;
}
var password = loginAgainVm.Password;
var waitDialog = new WaitLoginDialog();
var wait = DialogHost.Show(waitDialog);
try
{
await MaClient.LoginAgain(SelectedMafile, password, loginAgainVm.SavePassword, waitDialog);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
}
catch (LoginException ex)
{
@@ -118,7 +120,7 @@ public partial class MainVM : ObservableObject
try
{
await MaClient.RefreshSession(SelectedMafile);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SessionRefreshed"));
SnackbarController.SendSnackbar(GetLocalization("SessionRefreshed"));
}
catch (Exception ex) when (ExceptionHandler.Handle(ex))
{
@@ -129,7 +131,6 @@ public partial class MainVM : ObservableObject
[RelayCommand]
public async Task LinkAccount()
{
OffTimer(false);
await DialogsController.ShowLinkerDialog();
}
@@ -140,16 +141,16 @@ public partial class MainVM : ObservableObject
if (selectedMafile == null) return;
if (string.IsNullOrWhiteSpace(selectedMafile.RevocationCode))
{
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalizationOrDefault("MissingRCode"));
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalization("MissingRCode"));
return;
}
try
{
if (await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmRemovingAuthenticator")))
if (await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmRemovingAuthenticator")))
{
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(selectedMafile), selectedMafile);
SnackbarController.SendSnackbar(
result.Success ? GetLocalizationOrDefault("AuthenticatorRemoved") : GetLocalizationOrDefault("AuthenticatorNotRemoved"));
result.Success ? GetLocalization("AuthenticatorRemoved") : GetLocalization("AuthenticatorNotRemoved"));
if (result.Success)
{
@@ -176,17 +177,17 @@ public partial class MainVM : ObservableObject
try
{
LoginConfirmationResult res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
var res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
if (res.Success)
{
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
SnackbarController.SendSnackbar($"{GetLocalization("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
}
else
{
string msg = res.Error switch
{
LoginConfirmationError.NoRequests => GetLocalizationOrDefault("ConfirmLoginFailedNoRequests"),
LoginConfirmationError.MoreThanOneRequest => GetLocalizationOrDefault("ConfirmLoginFailedMoreThanOneRequest"), //TODO
LoginConfirmationError.NoRequests => GetLocalization("ConfirmLoginFailedNoRequests"),
LoginConfirmationError.MoreThanOneRequest => GetLocalization("ConfirmLoginFailedMoreThanOneRequest"), //TODO
_ => throw new ArgumentOutOfRangeException()
};
SnackbarController.SendSnackbar(msg);
@@ -198,4 +199,11 @@ public partial class MainVM : ObservableObject
Shell.Logger.Error(ex);
}
}
private static string GetLocalization(string key)
{
const string locPath = "MainVM";
return LocManager.GetCodeBehindOrDefault(key, locPath, key);
}
}
+41 -9
View File
@@ -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);
}
}
}
+21 -18
View File
@@ -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);
}
}
+43 -16
View File
@@ -14,6 +14,7 @@ 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;
@@ -38,7 +39,7 @@ public partial class MainVM //File //TODO: Refactor
}
if (mafilePath != null)
{
path = $"/select, \"{mafilePath}\""; ;
path = $"/select, \"{mafilePath}\"";
}
try
@@ -65,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)
@@ -87,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)
{
@@ -99,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++;
@@ -115,24 +116,24 @@ public partial class MainVM //File //TODO: Refactor
}
else
{
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("MafileImportError")} {Path.GetFileName(str)}{GetLocalizationOrDefault("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
SnackbarController.SendSnackbar($"{GetLocalization("MafileImportError")} {Path.GetFileName(str)}{GetLocalization("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
}
}
}
var msg = GetLocalizationOrDefault("Import");
var msg = GetLocalization("Import");
if (added > 0)
{
msg += $" {GetLocalizationOrDefault("ImportAdded")} {added}.";
msg += $" {GetLocalization("ImportAdded")} {added}.";
}
if (notAdded > 0)
{
msg += $" {GetLocalizationOrDefault("ImportSkipped")} {notAdded}.";
msg += $" {GetLocalization("ImportSkipped")} {notAdded}.";
}
if (errors > 0)
{
msg += $" {GetLocalizationOrDefault("ImportErrors")} {errors}.";
msg += $" {GetLocalization("ImportErrors")} {errors}.";
}
SnackbarController.SendSnackbar(msg, TimeSpan.FromSeconds(2));
}
@@ -155,7 +156,7 @@ public partial class MainVM //File //TODO: Refactor
try
{
await MaClient.LoginAgain(data, password, loginAgainVm.SavePassword, waitDialog);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
SnackbarController.SendSnackbar(GetLocalization("SuccessfulLogin"));
}
catch (LoginException ex)
{
@@ -188,7 +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
@@ -197,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}");
}
}
@@ -218,7 +219,7 @@ public partial class MainVM //File //TODO: Refactor
}
[RelayCommand]
private async Task CopyMafileFromBuffer()
private async Task PasteMafilesFromClipboard()
{
StringCollection files;
try
@@ -238,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"));
}
}
+4 -11
View File
@@ -2,17 +2,16 @@
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using AchiesUtilities.Extensions;
namespace NebulaAuth.ViewModel;
public partial class MainVM //Groups
{
[ObservableProperty]
private ObservableCollection<string> _groups = new();
private ObservableCollection<string> _groups = [];
public string? SelectedGroup
@@ -48,7 +47,6 @@ public partial class MainVM //Groups
var mafile = SelectedMafile;
if (mafile == null) return;
if (string.IsNullOrEmpty(value)) return;
if (!ValidateCanSaveAndWarn(mafile)) return;
mafile.Group = value;
Storage.UpdateMafile(mafile);
@@ -68,7 +66,6 @@ public partial class MainVM //Groups
var mafile = (Mafile?)value[1];
if (group == null || mafile == null) return;
if (!ValidateCanSaveAndWarn(mafile)) return;
mafile.Group = group;
Storage.UpdateMafile(mafile);
QueryGroups();
@@ -80,7 +77,6 @@ public partial class MainVM //Groups
private void RemoveGroup(Mafile? mafile)
{
if (mafile?.Group == null) return;
if (!ValidateCanSaveAndWarn(mafile)) return;
var mafGroup = mafile.Group;
mafile.Group = null;
Storage.UpdateMafile(mafile);
@@ -110,6 +106,7 @@ public partial class MainVM //Groups
private void PerformQuery()
{
MaacDisplay = false;
if (string.IsNullOrWhiteSpace(SelectedGroup) && string.IsNullOrWhiteSpace(SearchText))
{
MaFiles = Storage.MaFiles;
@@ -136,11 +133,7 @@ public partial class MainVM //Groups
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);
}
}
@@ -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);
}
}
+177
View File
@@ -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();
}
}
}
-37
View File
@@ -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());
});
}
}
+10 -19
View File
@@ -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,9 +100,8 @@ public partial class MainVM
[RelayCommand]
private void RemoveProxy()
{
if(SelectedProxy == null) return;
if (SelectedProxy == null) return;
if (SelectedMafile == null) return;
if (!ValidateCanSaveAndWarn(SelectedMafile)) return;
SelectedMafile.Proxy = null;
SelectedProxy = null;
Storage.UpdateMafile(SelectedMafile);
@@ -116,19 +117,9 @@ public partial class MainVM
}
if (SelectedMafile == null) return;
if (!ValidateCanSaveAndWarn(SelectedMafile)) return;
ProxyExist = true;
SelectedMafile.Proxy = SelectedProxy;
Storage.UpdateMafile(SelectedMafile);
}
private bool ValidateCanSaveAndWarn(Mafile data)
{
var canSave = Storage.ValidateCanSave(data);
if (!canSave)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRetrieveSteamIDToUpdate"));
}
return canSave;
}
}
-141
View File
@@ -1,141 +0,0 @@
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using SteamLib.Exceptions;
using SteamLib.SteamMobile.Confirmations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace NebulaAuth.ViewModel;
public partial class MainVM //Timer
{
private readonly Timer _confirmTimer;
[ObservableProperty] private bool _marketTimerEnabled;
[ObservableProperty] private bool _tradeTimerEnabled;
private int _timerCheckSeconds = Settings.Instance.TimerSeconds;
public int TimerCheckSeconds
{
get => _timerCheckSeconds;
set => SetTimer(value);
}
private async void ConfirmByTimer(object? state = null)
{
if (SelectedMafile == null)
return;
var selected = SelectedMafile;
if (InterruptTimer(selected, null)) return;
List<Confirmation> conf;
try
{
conf = (await HandleTimerRequest(() => MaClient.GetConfirmations(selected), SelectedMafile)).ToList();
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "Error GetConf in timer.");
return;
}
if (InterruptTimer(selected, conf)) return;
var toConfirm = new List<Confirmation>();
if (MarketTimerEnabled)
{
var market = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction);
toConfirm.AddRange(market);
}
if (TradeTimerEnabled)
{
var trade = conf.Where(c => c.ConfType == ConfirmationType.Trade);
toConfirm.AddRange(trade);
}
if (InterruptTimer(selected, toConfirm)) return;
bool result;
try
{
Shell.Logger.Debug("Sending confirmations. Count: {count}", toConfirm.Count);
result = await HandleTimerRequest(() => MaClient.SendMultipleConfirmation(SelectedMafile, toConfirm, confirm: true), SelectedMafile);
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "MultiConf error in Timer.");
return;
}
SnackbarController.SendSnackbar(result ? $"{GetLocalizationOrDefault("TimerConfirmed")} {toConfirm.Count}" : GetLocalizationOrDefault("TimerNotConfirmed"));
}
private bool InterruptTimer(Mafile cachedValue, List<Confirmation>? confirmations)
{
return SelectedMafile == null
|| (MarketTimerEnabled || TradeTimerEnabled) == false
|| SelectedMafile != cachedValue
|| confirmations is { Count: 0 };
}
private void OffTimer(bool dispatcher)
{
if (dispatcher)
{
Application.Current.Dispatcher.BeginInvoke(new Action(OffTimerInline));
}
else
{
OffTimerInline();
}
return;
void OffTimerInline()
{
MarketTimerEnabled = false;
TradeTimerEnabled = false;
}
}
private async Task<T> HandleTimerRequest<T>(Func<Task<T>> func, Mafile mafile)
{
Exception innerException;
try
{
return await SessionHandler.Handle(func, mafile);
}
catch (SessionInvalidException ex)
{
Shell.Logger.Warn("Session error while requesting in timer. Timer disabled");
SnackbarController.SendSnackbar(GetLocalizationOrDefault("TimerSessionError"));
OffTimer(dispatcher: true);
innerException = ex;
}
catch (Exception ex) when (ExceptionHandler.Handle(ex, GetLocalizationOrDefault("TimerPrefix")))
{
innerException = ex;
}
throw new ApplicationException("Swallowed", innerException);
}
private void SetTimer(int value)
{
if (_timerCheckSeconds == value) return;
if (value < 10)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("TimerTooFast"));
OnPropertyChanged(nameof(TimerCheckSeconds));
return;
}
Settings.TimerSeconds = value;
_timerCheckSeconds = value;
OnPropertyChanged(nameof(TimerCheckSeconds));
_confirmTimer.Change(value * 1000, value * 1000);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("TimerChanged"));
}
}
+54 -10
View File
@@ -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,13 +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;
using SteamLib.Utility.MaFiles;
namespace NebulaAuth.ViewModel.Other;
@@ -62,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))]
@@ -137,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;
@@ -180,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);
@@ -193,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;
}
@@ -314,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)
@@ -327,8 +344,8 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
{
Directory.CreateDirectory("mafiles_backup");
}
var json = Storage.SerializeMafile(data, null);
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)
{
@@ -2,7 +2,6 @@
using NebulaAuth.Model.Entities;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
namespace NebulaAuth.ViewModel.Other;
+1 -9
View File
@@ -1,9 +1,4 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using CommunityToolkit.Mvvm.ComponentModel;
namespace NebulaAuth.ViewModel.Other;
@@ -21,7 +16,4 @@ public partial class LoginAgainVM : ObservableObject
public bool IsFormValid => !string.IsNullOrWhiteSpace(Password);
public LoginAgainVM()
{ }
}
+52 -57
View File
@@ -1,7 +1,5 @@
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using AutoUpdaterDotNET;
using CodingSeb.Localization.WPF;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
@@ -23,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()
@@ -35,74 +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.Where(s => string.IsNullOrWhiteSpace(s) == false))
{
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;
}
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(new KeyValuePair<int?, ProxyData>(id, proxy));
}
else
{
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, "");
}
if (ProxyStorage.DefaultScheme.TryParse(input, out var data))
idPresent ??= idMatch.Success;
if (idPresent.Value != idMatch.Success)
{
ProxyStorage.SetProxy(id, data);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
return;
}
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
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
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;
@@ -132,7 +127,7 @@ public partial class ProxyManagerVM : ObservableObject
break;
}
}
ProxyStorage.RemoveProxy(s.Key);
SelectedProxy = nextNeighbor ?? prevNeighbor;
CheckIfDefaultProxyStay();
@@ -167,7 +162,7 @@ public partial class ProxyManagerVM : ObservableObject
{
Shell.Logger.Error(ex);
}
}
[RelayCommand]
+9 -9
View File
@@ -11,11 +11,6 @@ public partial class SettingsVM : ObservableObject
{
public Settings Settings => Settings.Instance;
public bool DisableTimersOnChange
{
get => Settings.DisableTimersOnChange;
set => Settings.DisableTimersOnChange = value;
}
public BackgroundMode BackgroundMode
{
get => Settings.BackgroundMode;
@@ -115,14 +110,19 @@ public partial class SettingsVM : ObservableObject
set => Settings.UseAccountNameAsMafileName = value;
}
[ObservableProperty] private string _password;
public bool IgnorePatchTuesdayErrors
{
get => Settings.IgnorePatchTuesdayErrors;
set => Settings.IgnorePatchTuesdayErrors = value;
}
[ObservableProperty] private string? _password;
[RelayCommand]
public void SetPassword()
private void SetPassword()
{
PHandler.SetPassword(Password);
Settings.IsPasswordSet = PHandler.IsPasswordSet;
Settings.IsPasswordSet = PHandler.SetPassword(Password);
}
}
+1 -2
View File
@@ -1,10 +1,9 @@
using AutoUpdaterDotNET;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.VisualBasic.FileIO;
namespace NebulaAuth.ViewModel.Other;
public partial class UpdaterVM : ObservableObject
public class UpdaterVM : ObservableObject
{
public UpdateInfoEventArgs UpdateInfoEventArgs { get; }
+149 -61
View File
@@ -73,7 +73,7 @@
"ru": "Чтобы начать пользоваться программой вы можете привязать аккаунт через меню \"Аккаунт\", либо импортировать существующие мафайлы одним из способов:\n1. Скопировать их в папку mafiles и перезапустить приложение\n2. Перетянуть файлы прямо в окно программы\n3. Скопировать файлы и нажать CTRL+V в окне программы\n4. Через меню \"Файл\" - \"Импорт\"",
"en": "To start using the program, you can link an account through the \"Account\" menu, or import existing mafiles in one of the following ways:\n1. Copy them to the mafiles folder and restart the application\n2. Drag files directly into the program window\n3. Copy files and press CTRL+V in the program window\n4. Through the \"File\" - \"Import\" menu",
"ua": "Щоб почати користуватися програмою, ви можете прив'язати акаунт через меню \"Акаунт\", або імпортувати існуючі мафайли одним із способів:\n1. Скопіювати їх у папку mafiles та перезапустити програму\n2. Перетягнути файли безпосередньо в вікно програми\n3. Скопіювати файли та натиснути CTRL+V в вікні програми\n4. Через меню \"Файл\" - \"Імпорт\""
}
}
},
"Menu": {
"File": {
@@ -172,14 +172,31 @@
}
},
"TradeTimerHint": {
"en": "Trade",
"ru": "Трейд",
"ua": "Трейд"
"en": "Auto-confirm trades. RMC for mass control",
"ru": "Автоподтверждение трейдов. ПКМ для массового управления",
"ua": "Автопідтвердження трейдів. ПКМ для масового управління"
},
"MarketTimerHint": {
"en": "Market",
"ru": "Маркет",
"ua": "Маркет"
"en": "Auto-confirm market listings. RMC for mass control",
"ru": "Автоподтверждение лотов на маркете. ПКМ для массового управления",
"ua": "Автопідтвердження лотів на маркеті. ПКМ для масового управління"
},
"TimersContextMenu": {
"SwitchMAACGroup": {
"en": "Switch in group",
"ru": "Переключить в группе",
"ua": "Перемкнути в групі"
},
"SwitchMAACAll": {
"en": "Switch all",
"ru": "Переключить все",
"ua": "Перемкнути всі"
}
},
"ShowAutoConfirmAccountsHint": {
"en": "Show accounts with auto-confirmation enabled.",
"ru": "Показать аккаунты с включенным автоподтверждением.",
"ua": "Показати акаунти з включеним автопідтвердженням."
}
},
"LeftPart": {
@@ -242,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": "Добавить в группу",
@@ -256,13 +288,13 @@
"Proxy": {
"Copy": {
"en": "Copy",
"ru": "Копировать",
"ua": "Копіювати"
"ru": "Скопировать",
"ua": "Скопіювати"
},
"CopyAddress": {
"en": "Copy address",
"ru": "Копировать адрес",
"ua": "Копіювати адресу"
"ru": "Скопировать адрес",
"ua": "Скопіювати адресу"
}
}
}
@@ -295,11 +327,6 @@
"ua": "Без фону"
}
},
"DisableTimersOnSwitch": {
"en": "Disable timers on account switch",
"ru": "Отключать таймеры при смене аккаунта",
"ua": "Вимикати таймери при зміні акаунта"
},
"MinimizeToTray": {
"en": "Minimize to tray",
"ru": "Сворачивать в трей",
@@ -310,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": "Свой цвет окна",
@@ -346,7 +378,19 @@
"en": "Use account name on mafiles",
"ru": "Использовать имя аккаунта на мафайлах",
"ua": "Використовувати ім'я акаунта на мафайлах"
},
"IgnorePatchTuesdayErrors": {
"en": "Ignore Patch Tuesday errors in timer (exp.)",
"ru": "Игнорировать ошибки Patch Tuesday в таймере (эксп.)",
"ua": "Ігнорувати помилки Patch Tuesday у таймері (експ.)"
},
"IgnorePatchTuesdayErrorsHint": {
"en": "Ignore errors that occur every Tuesday (Wednesday night in GMT) when Steam doing technical works and auto-confirm timers turns off unnecessarily",
"ru": "Игнорировать ошибки, которые возникают каждый вторник (ночь среды по GMT) когда Steam проводит технические работы и автоподтверждения отключаются без надобности",
"ua": "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби"
}
},
"LoginAgainDialog": {
"Title": {
@@ -388,7 +432,7 @@
"en": "Press 'DEL' to remove",
"ru": "Нажмите 'DEL' для удаления",
"ua": "Натисніть 'DEL' для видалення"
}
}
},
"ProxyManagerDialog": {
"Title": {
@@ -420,6 +464,11 @@
"ru": "Привязка",
"ua": "Прив'язка"
},
"GotErrorHyperlinkText": {
"en": "(getting error?)",
"ru": "(возникает ошибка?)",
"ua": "(виникає помилка?)"
},
"Proxy": {
"en": "Proxy",
"ru": "Прокси",
@@ -441,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",
@@ -576,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": "Не удалось переместить мафайл, возможно в папке уже существует мафайл с таким именем.",
@@ -590,26 +639,6 @@
"en": "Can't remove mafile:",
"ua": "Не вдалося видалити (перемістити) мафайл:"
},
"TimerConfirmed": {
"ru": "Авто: подтверждено ",
"en": "Auto: confirmed ",
"ua": "Авто: підтверджено "
},
"TimerNotConfirmed": {
"ru": "Авто: подтверждение не сработало",
"en": "Auto: confirmation was unsuccessful",
"ua": "Авто: підтвердження не спрацювало"
},
"TimerSessionError": {
"ru": "Авто: необходимо обновить сессию или перелогиниться",
"en": "Auto: need to refresh session or relogin",
"ua": "Авто: необхідно оновити сесію або перелогінитися"
},
"TimerPrefix": {
"ru": "Авто: ",
"en": "Auto: ",
"ua": "Авто: "
},
"TimerTooFast": {
"ru": "Слишком быстрый таймер.",
"en": "Too fast timer.",
@@ -629,7 +658,27 @@
"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": {
"Login": {
@@ -877,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",
@@ -889,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": "Произошла отмена операции",
@@ -923,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": "Акаунт не налаштований на отримання підтверджень"
}
}
},
@@ -1007,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": "Авто "
}
}
},
+3 -3
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.4.9.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.4.9/NebulaAuth.1.4.9.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.4.9.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>
+24 -16
View File
@@ -2,22 +2,30 @@
## Описание
NebulaAuth — это приложение для эмуляции действий из мобильного приложения Steam. Которая заменяет ваш смартфон при работе в Steam.
<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 для удобства.
- **Настройка дизайна** для персонализации интерфейса.
- **Полная функциональность 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)
@@ -35,28 +43,28 @@ NebulaAuth — это приложение для эмуляции действ
- управление аккаунтом (вход, привязка, отвязка)
- группировка
- выбор прокси
- индикатор с подсказкой об используемом прокси (светится либо желтым, либо красным, при наведении отобразит дополнительную информацию)
- индикатор с подсказкой об используемом прокси (горит либо желтым, либо красным, при наведении отобразит дополнительную информацию)
- таймеры для автоматического подтверждения трейдов/продаж на торговой площадке
- как часто проверять подтверждения при включенных таймерах (в секундах)
- интервал таймера подтверждений (в секундах)
2. Список ваших аккаунтов
3. Код подтверждения входа (нажмите, чтобы скопировать)
4. Главное окно подтверждений
5. Поиск по логину или SteamID (7xxxxxxxxxxxxx)
6. Подтвердить вход с другого устройства.
6. Подтвердить вход на другогом устройстве.
7. Гиперссылка на официальную страницу приложения с указанием авторства.
## Настройки
![gh-settings-rus](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/33246ed1-1e3d-4310-88c5-085e5c50bc6b)
1. Режим фона. Переключите, если вы хотите отключить фон или установить собственный (поместите файл «Background.png» в папку приложения)
2. Язык локализации
1. Режим фона. Используйте, если вы хотите отключить фон или установить собственный (поместите файл «Background.png» в папку приложения)
2. Язык приложения
3. Отключить таймеры при переключении между аккаунтами
4. Скрывать в трей при сворачивании
5. Индикатор с цветом. Маленький кружочек на значке панели задач с произвольным цветом. Полезно при использовании нескольких окон.
6. Пользовательский цвет приложения.
7. Текущий пароль шифрования. Если установлено, вы можете сохранять зашифрованные пароли в mafile, чтобы облегчить повторный вход в систему при проблемах с сеансом. (Не рекомендуется)
8. Режим устаревших файлов. Режим совместимости Mafile. Если установлено, приложение будет сохранять файлы в старом стандартном формате (по умолчанию: включено).
7. Текущий пароль шифрования. Если установлено, вы можете сохранять зашифрованные пароли в mafile, чтобы облегчить повторный вход в систему при проблемах с сессией. (Не рекомендуется)
8. Режим устаревших файлов. Режим совместимости Mafile с другими клиентами (SDA и т.д.). Если установлено, приложение будет сохранять файлы в старом стандартном формате (по умолчанию: включено).
9. Разрешить автообновление без подтверждения
+21 -14
View File
@@ -2,20 +2,27 @@
## Опис
NebulaAuth - це програма для емуляції дій з мобільного додатку Steam. Яка замінює ваш смартфон під час роботи в Steam.
<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 для зручності.
- **Налаштування дизайну** для персоналізації інтерфейсу.
- **Підтримка проксі** у всіх процесах роботи з обліковим записом.
- **Угруповання мафайлів** для просунутого контролю.
- **Автоматичне підтвердження трейдів/дій на маркеті** для економії часу.
- **Масовий імпорт мафайлів** за допомогою Drag'n'Drop або CTRL+V для зручності.
- **Налаштування зовнішнього вигляду** для персоналізації інтерфейсу.
- **Можливість підтвердити вхід до облікового запису без введення коду** для полегшення доступу.
- **Автооновлення** програми для використання новітніх функцій.
- **Автоматичний повторний вхід у разі проблем із сеансом** для безперервної роботи.
- **Автоматичний повторний вхід у разі проблем із сесією** для безперервної роботи.
- **Інтуїтивно зрозумілий інтерфейс** з підказками та зручностями
- **Постійна підтримка** коду програми та інших функцій.
## Монтаж
@@ -33,28 +40,28 @@ NebulaAuth - це програма для емуляції дій з мобіл
- керування файлами та налаштування
- керування акаунтом (вхід, прив'язка, відв'язування)
- угруповання
- Вибір проксі
- вибір проксі
- індикатор з підказкою про проксі (світиться або жовтим, або червоним, при наведенні відобразить додаткову інформацію)
- таймери для автоматичного підтвердження трейдів/продажів на торговому майданчику
- як часто перевіряти підтвердження при увімкнених таймерах (у секундах)
- таймери для автоматичного підтвердження трейдів/продажів на маркеті
- інтервал таймера підтверджень (у секундах)
2. Список ваших облікових записів
3. Код підтвердження входу (натисніть, щоб скопіювати)
4. Головне вікно підтвердження
5. Пошук за логіном або SteamID (7xxxxxxxxxxxxx)
6. Підтвердити вхід із іншого пристрою.
6. Підтвердити вхід на іншому пристрою.
7. Гіперпосилання на офіційну сторінку додатку із зазначенням авторства.
## Налаштування
![gh-settings-ua](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/3f57e58d-d647-42a1-a4c9-ca53ab7b38a3)
1. Режим фону. Перемкніть, якщо ви хочете вимкнути фон або встановити власний (помістіть файл "Background.png" у папку програми)
1. Режим фону. Використовуйте, якщо ви хочете вимкнути фон або встановити власний (помістіть файл "Background.png" у папку програми)
2. Мова локалізації
3. Вимкнути таймери під час перемикання між обліковими записами
4. Приховувати у трей при згортанні
5. Індикатор із кольором. Маленький кружечок на піктограмі панелі завдань з довільним кольором. Корисно при використанні кількох вікон.
6. Власний колір програми.
7. Поточний пароль шифрування. Якщо встановлено, ви можете зберігати зашифровані паролі в mafile, щоб полегшити повторний вхід до системи у разі проблеми з сеансом. (Не рекомендується)
8. Режим застарілих файлів. Режим сумісності Mafile. Якщо встановлено, програма зберігатиме файли у старому стандартному форматі (за замовчуванням: увімкнено).
7. Поточний пароль шифрування. Якщо встановлено, ви можете зберігати зашифровані паролі в mafile, щоб полегшити повторний вхід до системи у разі проблеми з сесією. (Не рекомендується)
8. Режим застарілих файлів. Режим сумісності Mafile з іншими клієнтами (SDA тощо). Якщо встановлено, програма зберігатиме файли у старому стандартному форматі (за замовчуванням: увімкнено).
9. Дозволити автооновлення без підтвердження
+13 -8
View File
@@ -3,28 +3,33 @@
* [Русский](README-RU.md)
* [Українська](README-UA.md)
## Description
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Latest release</a>
<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
@@ -45,12 +50,12 @@
- 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
@@ -63,7 +68,7 @@
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. If set application will save mafiles with old standart format (Default: checked)
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
+3 -4
View File
@@ -2,6 +2,7 @@
using SteamLib.Core.Interfaces;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using SteamLib.Core.Models;
namespace SteamLib.Account;
@@ -35,8 +36,8 @@ public sealed class MobileSessionData : SessionData, IMobileSessionData
}
public MobileSessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken,
SteamAuthToken? mobileToken, IEnumerable<SteamAuthToken>? tokens)
: base(sessionId, steamId, refreshToken, tokens)
SteamAuthToken? mobileToken, IEnumerable<SteamAuthToken>? tokensCollection)
: base(sessionId, steamId, refreshToken, tokensCollection)
{
MobileToken = mobileToken;
}
@@ -46,6 +47,4 @@ public sealed class MobileSessionData : SessionData, IMobileSessionData
{
return new MobileSessionData(SessionId, SteamId, RefreshToken, MobileToken, Tokens);
}
}
+7 -5
View File
@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using SteamLib.Core.Enums;
using SteamLib.Core.Interfaces;
using SteamLib.Core.Models;
using SteamLib.Web.Converters;
using System.Collections.Concurrent;
@@ -8,8 +9,9 @@ namespace SteamLib.Account;
public class SessionData : ISessionData
{
[JsonIgnore]
public bool? IsValid { get; set; }
[JsonIgnore] public bool? IsValid { get; set; }
[JsonIgnore] public bool IsExpired => RefreshToken.IsExpired;
public string SessionId { get; }
[JsonConverter(typeof(SteamIdToSteam64Converter))]
@@ -26,13 +28,13 @@ public class SessionData : ISessionData
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokens ?? new Dictionary<SteamDomain, SteamAuthToken>());
}
public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IEnumerable<SteamAuthToken>? tokens)
public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IEnumerable<SteamAuthToken>? tokensCollection)
{
SessionId = sessionId;
SteamId = steamId;
RefreshToken = refreshToken;
tokens ??= Array.Empty<SteamAuthToken>();
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokens.ToDictionary(t => t.Domain, t => t));
tokensCollection ??= Array.Empty<SteamAuthToken>();
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokensCollection.ToDictionary(t => t.Domain, t => t));
}
public virtual SteamAuthToken? GetToken(SteamDomain domain)

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