Added MAAC persistence support and password management features

- Introduced `MAACStorage` for managing multi-account auto-confirmers persistence
- Added `SetAccountPasswordsView` dialog for managing account passwords setup
- Enhanced `Storage` with async methods for file operations
- Added `BoolToValueConverter` for flexible XAML data bindings
- Enhanced user experience in dialogs with `IsDefault` and `IsCancel` properties
This commit is contained in:
achiez
2025-11-07 10:27:16 +02:00
parent 882d39b8f3
commit 8e5fea54cd
24 changed files with 648 additions and 71 deletions
+5 -3
View File
@@ -1,8 +1,9 @@
using System;
using System.Windows;
using NebulaAuth.Core;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Exceptions;
using NebulaAuth.Model.MAAC;
using System;
using System.Windows;
namespace NebulaAuth;
@@ -20,6 +21,7 @@ public partial class App
Shell.Initialize();
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
await Storage.Initialize(threads);
MAACStorage.Initialize();
var mainWindow = new MainWindow();
Current.MainWindow = mainWindow;
mainWindow.Show();
@@ -0,0 +1,28 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class BoolToValueConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not bool b) return Binding.DoNothing;
if (parameter is Array)
{
var arr = (Array)parameter;
if (arr.Length == 0) return Binding.DoNothing;
var first = arr.GetValue(0);
var second = arr.Length > 1 ? arr.GetValue(1) : null;
return b ? first : second;
}
return b ? parameter : null;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
+1 -1
View File
@@ -14,7 +14,7 @@
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter" />
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter" />
<converters:BoolToMafileNamingConverter x:Key="BoolToMafileNamingConverter"/>
<converters:BoolToValueConverter x:Key="BoolToValueConverter"/>
<converters:NullableToBooleanConverter x:Key="NullableToBooleanConverter" />
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
+10
View File
@@ -121,4 +121,14 @@ public static class DialogsController
vm?.Dispose();
}
}
public static async Task ShowSetAccountsPasswordDialog()
{
var vm = new SetAccountPasswordsVM();
var dialog = new SetAccountPasswordsView
{
DataContext = vm
};
await DialogHost.Show(dialog);
}
}
+17 -4
View File
@@ -85,8 +85,17 @@
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}"
Command="{Binding OpenMafileFolderCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.ProxyManager}"
Command="{Binding OpenProxyManagerCommand}">
</MenuItem>
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}"
Command="{Binding OpenSettingsDialogCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.Other.Title}">
<MenuItem Header="{Tr MainWindow.Menu.File.Other.SetPasswords}"
Command="{Binding DebugCommand}"
/>
</MenuItem>
</MenuItem>
</Menu>
<Menu>
@@ -314,6 +323,9 @@
<KeyBinding Key="X" Modifiers="Control"
Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
<KeyBinding Modifiers="Control+Shift" Key="C"
Command="{Binding DataContext.CopyPasswordCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
</ListBox.InputBindings>
<FrameworkElement.ContextMenu>
<ContextMenu Tag="{Binding Groups}">
@@ -321,7 +333,11 @@
Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}"
Command="{Binding CopyLoginCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}"
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
Command="{Binding Path=CopyPasswordCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
InputGestureText="CTRL+⇧+C"/>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}"
Command="{Binding CopySteamIdCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem InputGestureText="Ctrl+X"
@@ -374,9 +390,6 @@
Command="{Binding Path=RemoveGroupCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
/>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
Command="{Binding Path=CopyPasswordCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
+1 -1
View File
@@ -64,7 +64,7 @@ public partial class MainWindow
if (ItemsControl.ContainerFromElement((ListBox) sender, (DependencyObject) e.OriginalSource) is ListBoxItem
{
IsSelected: true
} item)
})
{
e.Handled = true;
}
+8 -1
View File
@@ -20,9 +20,16 @@ public partial class Mafile : MobileDataExtended
set => SetProperty(ref _linkedClient, value);
}
[JsonIgnore] public string? Filename { get; set; }
[JsonIgnore] public string? Filename
{
get => _filename;
set => SetProperty(ref _filename, value);
}
[JsonIgnore] private PortableMaClient? _linkedClient;
private string? _filename;
public void SetSessionData(MobileSessionData? sessionData)
{
SessionData = sessionData;
+139
View File
@@ -0,0 +1,139 @@
using System;
using NebulaAuth.Model.Entities;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using NebulaAuth.Core;
namespace NebulaAuth.Model.MAAC;
public static class MAACStorage
{
private static Dictionary<string, StoredClient> Clients { get; set; } = [];
static MAACStorage(){}
private static void ClientsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
Clients.Clear();
}
else if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
if (item is Mafile { Filename: not null } mafile)
{
if (mafile.LinkedClient != null)
mafile.LinkedClient.PropertyChanged += LinkedClientOnPropertyChanged;
Clients.Add(mafile.Filename, new StoredClient
{
AutoConfirmMarket = mafile.LinkedClient?.AutoConfirmMarket ?? false,
AutoConfirmTrades = mafile.LinkedClient?.AutoConfirmTrades ?? false
});
}
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
if (item is Mafile { Filename: not null } mafile)
{
if (mafile.LinkedClient != null)
mafile.LinkedClient.PropertyChanged -= LinkedClientOnPropertyChanged;
Clients.Remove(mafile.Filename);
}
}
}
Save();
}
private static void LinkedClientOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is not PortableMaClient client) return;
if (client.Mafile.Filename == null) return;
var anyChanges = false;
if (!Clients.TryGetValue(client.Mafile.Filename, out var storedClient))
{
client.PropertyChanged -= LinkedClientOnPropertyChanged;
return;
}
if (e.PropertyName == nameof(PortableMaClient.AutoConfirmMarket))
{
anyChanges = storedClient.AutoConfirmMarket != client.AutoConfirmMarket;
storedClient.AutoConfirmMarket = client.AutoConfirmMarket;
}
else if (e.PropertyName == nameof(PortableMaClient.AutoConfirmTrades))
{
anyChanges = storedClient.AutoConfirmTrades != client.AutoConfirmTrades;
storedClient.AutoConfirmTrades = client.AutoConfirmTrades;
}
if (anyChanges)
Save();
}
public static void Save()
{
var json = JsonConvert.SerializeObject(Clients, Formatting.Indented);
File.WriteAllText("maac.json", json);
}
public static void Initialize()
{
if (!File.Exists("maac.json")) return;
try
{
var json = File.ReadAllText("maac.json");
var clients = JsonConvert.DeserializeObject<Dictionary<string, StoredClient>>(json) ?? [];
foreach (var (fileName, storedClient) in clients)
{
var mafile = Storage.MaFiles.FirstOrDefault(x => x.Filename == fileName);
if (mafile == null) continue;
if(storedClient is {AutoConfirmMarket: false, AutoConfirmTrades: false}) continue;
if (MultiAccountAutoConfirmer.TryAddToConfirm(mafile) && mafile.LinkedClient != null)
{
mafile.LinkedClient.AutoConfirmMarket = storedClient.AutoConfirmMarket;
mafile.LinkedClient.AutoConfirmTrades = storedClient.AutoConfirmTrades;
Clients[fileName] = storedClient;
}
}
}
catch(Exception ex)
{
Shell.Logger.Error(ex, "Failed to load MAAC storage");
SnackbarController.SendSnackbar(LocManager.GetCodeBehindOrDefault("FailedToLoadStorage", "MAAC", "FailedToLoadStorage"));
}
MultiAccountAutoConfirmer.Clients.CollectionChanged += ClientsOnCollectionChanged;
}
public static void NotifyMafilesRenamed(IDictionary<string, string> oldNewNames)
{
var updatedClients = new Dictionary<string, StoredClient>();
foreach (var (oldName, newName) in oldNewNames)
{
if(Clients.Remove(oldName, out var storedClient))
{
updatedClients[newName] = storedClient;
}
}
Clients = updatedClients;
Save();
}
private class StoredClient
{
public bool AutoConfirmMarket { get; set; }
public bool AutoConfirmTrades { get; set; }
}
}
@@ -25,7 +25,7 @@ public static class MultiAccountAutoConfirmer
Clients = [];
Timer = new Timer(TimerConfirm);
Settings.Instance.PropertyChanged += SettingsOnPropertyChanged;
UpdateTimer();
UpdateTimer(true);
}
// ReSharper disable once AsyncVoidMethod //Already safe
@@ -120,8 +120,8 @@ public static class MultiAccountAutoConfirmer
return Lock.WriteLock(() =>
{
if (Clients.Contains(mafile)) return false;
Clients.Add(mafile);
mafile.LinkedClient = new PortableMaClient(mafile);
Clients.Add(mafile);
return true;
});
}
@@ -131,9 +131,10 @@ public static class MultiAccountAutoConfirmer
{
Lock.WriteLock(() =>
{
Clients.Remove(mafile);
mafile.LinkedClient?.Dispose();
mafile.LinkedClient = null;
Clients.Remove(mafile);
});
}
@@ -144,11 +145,16 @@ public static class MultiAccountAutoConfirmer
UpdateTimer();
}
private static void UpdateTimer()
private static void UpdateTimer(bool firstStart = false)
{
var timerInterval = Settings.Instance.TimerSeconds;
var intervalTimeSpan = TimeSpan.FromSeconds(timerInterval);
Timer.Change(intervalTimeSpan, intervalTimeSpan);
var dueTime = intervalTimeSpan;
if (firstStart)
{
dueTime = timerInterval >= 30 ? intervalTimeSpan : TimeSpan.FromSeconds(30);
}
Timer.Change(dueTime, intervalTimeSpan);
}
private static string GetLocalization(string key)
@@ -1,12 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using AchiesUtilities.Web.Models;
using AchiesUtilities.Web.Models;
using AchiesUtilities.Web.Proxy;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Core;
@@ -19,6 +11,14 @@ using SteamLib.Exceptions.Authorization;
using SteamLib.SteamMobile.Confirmations;
using SteamLib.Web;
using SteamLibForked.Abstractions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace NebulaAuth.Model.MAAC;
-3
View File
@@ -20,11 +20,8 @@ namespace NebulaAuth.Model;
public static class MaClient
{
private static HttpClientHandler ClientHandler { get; }
private static HttpClient Client { get; }
private static DynamicProxy Proxy { get; }
public static ProxyData? DefaultProxy { get; set; }
+2 -2
View File
@@ -29,7 +29,7 @@ public partial class SessionHandler //API
//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);
await Storage.UpdateMafileAsync(mafile);
chp.Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
}
@@ -53,6 +53,6 @@ public partial class SessionHandler //API
mafile.SetSessionData((MobileSessionData) result);
if (PHandler.IsPasswordSet)
mafile.Password = savePassword ? PHandler.Encrypt(password) : null;
Storage.UpdateMafile(mafile);
await Storage.UpdateMafileAsync(mafile);
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using NebulaAuth.Model.Exceptions;
using NLog;
using NLog.Extensions.Logging;
using SteamLib.Core;
using SteamLib.SteamMobile;
using System;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace NebulaAuth.Model;
@@ -39,7 +39,7 @@ public static class Shell
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Fatal((Exception) e.ExceptionObject);
Logger.Fatal((Exception)e.ExceptionObject);
LogManager.Shutdown();
}
}
+38 -7
View File
@@ -7,6 +7,7 @@ using SteamLib.Exceptions.Authorization;
using SteamLib.SteamMobile;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Compression;
@@ -14,6 +15,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Data;
using NebulaAuth.Model.MAAC;
namespace NebulaAuth.Model;
@@ -131,12 +133,37 @@ public static class Storage
MaFiles.Add(data);
}
}
public static async Task SaveMafileAsync(Mafile data)
{
var path = GetOrCreateMafilePath(data);
var str = NebulaSerializer.SerializeMafile(data);
await File.WriteAllTextAsync(Path.GetFullPath(path), str);
var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
if (existed != null)
{
var index = MaFiles.IndexOf(existed);
MaFiles[index] = data;
}
else
{
MaFiles.Add(data);
}
}
public static void UpdateMafile(Mafile data)
{
var path = GetOrCreateMafilePath(data);
var str = NebulaSerializer.SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
}
public static async Task UpdateMafileAsync(Mafile data)
{
var path = GetOrCreateMafilePath(data);
var str = NebulaSerializer.SerializeMafile(data);
await File.WriteAllTextAsync(Path.GetFullPath(path), str);
}
public static void MoveToRemoved(Mafile data)
{
var sourcePath = GetOrCreateMafilePath(data);
@@ -229,19 +256,22 @@ public static class Storage
BackupFileName = backupFileName
};
var dic = new Dictionary<string, string>();
foreach (var mafile in mafiles)
{
try
{
var targetFileName = CreateMafileFileName(mafile, loginAsFileName);
if (mafile.Filename == targetFileName || mafile.Filename == null)
{
res.Renamed += 1;
continue;
}
var sourceFileName = mafile.Filename;
var fullSourcePath = Path.Combine(MafilesDirectory, sourceFileName);
var fullTargetPath = Path.Combine(MafilesDirectory, targetFileName);
var fullSourcePath = Path.Combine(MafilesDirectory, mafile.Filename);
if (!File.Exists(fullSourcePath))
{
Shell.Logger.Warn("Can't rename mafile {old} to {new} because source file not found",
@@ -254,11 +284,11 @@ public static class Storage
{
Shell.Logger.Warn("Can't rename mafile {old} to {new} because target file already exist",
mafile.Filename, targetFileName);
res.AlreadyExist += 1;
res.Conflict += 1;
continue;
}
File.Move(fullSourcePath, fullTargetPath);
dic[sourceFileName] = targetFileName;
res.Renamed += 1;
mafile.Filename = targetFileName;
@@ -269,8 +299,9 @@ public static class Storage
mafile.AccountName);
IncErrors();
}
}
MAACStorage.NotifyMafilesRenamed(dic);
return res;
void IncErrors() => res.Errors += 1;
@@ -280,9 +311,9 @@ public static class Storage
{
public int Total { get; set; }
public int Renamed { get; set; }
public int NotRenamed => Errors + AlreadyExist;
public int NotRenamed => Errors + Conflict;
public int Errors { get; set; }
public int AlreadyExist { get; set; }
public int Conflict { get; set; }
public string BackupFileName { get; set; }
}
}
@@ -55,7 +55,8 @@
Style="{StaticResource MaterialDesignRaisedButton}"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource True}"
Content="{Tr Common.OK}"/>
Content="{Tr Common.OK}"
IsDefault="True"/>
<Button Grid.Column="1"
HorizontalAlignment="Right"
Width="130"
@@ -54,6 +54,7 @@
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{Binding Text, ElementName=TextField}"
Content="{Tr Common.OK}"
IsDefault="True"
/>
<Button Grid.Column="1"
HorizontalAlignment="Right"
@@ -0,0 +1,158 @@
<UserControl x:Class="NebulaAuth.View.SetAccountPasswordsView"
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:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="400"
d:DataContext="{d:DesignInstance other:SetAccountPasswordsVM}"
d:Background="#141119"
d:Foreground="White"
FontFamily="{md:MaterialDesignFont}"
MinHeight="500"
MaxHeight="500"
MinWidth="400"
MaxWidth="400"
Background="Transparent"
FontSize="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- ===== HEADER ===== -->
<Grid Margin="10,10,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<md:PackIcon Kind="ShieldKey" Width="20" Height="20"
Margin="0,0,5,0" VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryContentBrush}" />
<TextBlock Text="{Tr SetAccountPasswordsView.Title}"
FontSize="18"
VerticalAlignment="Center"
Foreground="{DynamicResource BaseContentBrush}" />
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30"
Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right"
Command="{x:Static md:DialogHost.CloseDialogCommand}"
IsCancel="True">
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1" />
<!-- ===== ENCRYPTION PASSWORD SECTION ===== -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition/>
</Grid.RowDefinitions>
<md:Card Margin="15,15,15,15" BorderThickness="1" BorderBrush="Gray" Padding="12" >
<StackPanel>
<TextBlock Text="{Tr SetAccountPasswordsView.EncryptionPassword}" FontSize="16"
Margin="0,0,0,5"
Foreground="{DynamicResource PrimaryContentBrush}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox
md:HintAssist.Hint="{Tr SetAccountPasswordsView.EnterPassword}"
md:HintAssist.IsFloating="False"
Margin="0,0,10,0"
Text="{Binding EncryptionPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="1" Command="{Binding SetEncryptionPasswordCommand}">
<md:PackIcon Kind="ContentSave"/>
</Button>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
<StackPanel.Resources>
<x:Array x:Key="BoolToIcon" Type="{x:Type md:PackIconKind}">
<md:PackIconKind>CheckBox</md:PackIconKind>
<md:PackIconKind>CloseBox</md:PackIconKind>
</x:Array>
<x:Array x:Key="BoolToColor" Type="{x:Type SolidColorBrush}">
<SolidColorBrush Color="{DynamicResource SuccessColor}"/>
<SolidColorBrush Color="{DynamicResource ErrorColor}"/>
</x:Array>
</StackPanel.Resources>
<TextBlock Text="{Tr SetAccountPasswordsView.Status}" VerticalAlignment="Center"/>
<md:PackIcon Width="20" Height="20"
VerticalAlignment="Center"
Foreground="{Binding IsEncryptionPasswordSet, Converter={StaticResource BoolToValueConverter}, ConverterParameter={StaticResource BoolToColor}}"
Kind="{Binding IsEncryptionPasswordSet, Converter={StaticResource BoolToValueConverter}, ConverterParameter={StaticResource BoolToIcon}}" />
</StackPanel>
</StackPanel>
</md:Card>
</Grid>
<!-- ===== ACCOUNTS INPUT ===== -->
<TextBox Grid.Row="3"
Margin="15,0,15,10"
Text="{Binding AccountsPasswords, UpdateSourceTrigger=PropertyChanged}"
FontSize="13"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
md:TextFieldAssist.HasClearButton="True"
md:HintAssist.IsFloating="False"
md:HintAssist.Hint="{Tr SetAccountPasswordsView.InputFormat}"
md:TextFieldAssist.TextBoxIsMultiLine="True"
md:TextFieldAssist.TextBoxViewVerticalAlignment="Top"
VerticalContentAlignment="Top"
>
</TextBox>
<!-- ===== INFO TEXT ===== -->
<TextBlock Grid.Row="4"
Margin="20,0,20,15"
TextWrapping="Wrap"
FontSize="12"
Foreground="{DynamicResource BaseContentBrush}"
Text="{Tr SetAccountPasswordsView.EncryptionHint}" />
<Button Grid.Row="5"
Margin="10,0,0,10"
Width="120"
Content="{Tr Common.Save}"
Command="{Binding SetPasswordsCommand}"
Style="{StaticResource MaterialDesignOutlinedButton}" Cursor="Hand" />
<!-- ===== HINT / RESULT BOX ===== -->
<nebulaAuth:HintBox Grid.Row="6"
Margin="15,0,15,10"
Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
CloseCommand="{Binding ClearTipCommand}"
Text="{Binding Tip}"
FontSize="13"
Severity="Info"
ShowCloseButton="True" />
</Grid>
</UserControl>
@@ -0,0 +1,13 @@
using System.Windows.Controls;
using System.Windows.Input;
namespace NebulaAuth.View;
public partial class SetAccountPasswordsView
{
public SetAccountPasswordsView()
{
InitializeComponent();
}
}
@@ -252,7 +252,7 @@ public partial class MafileMoverVM : ObservableObject, IAuthProvider, IDisposabl
Logger.Error(ex, "Error during saving Nebula data to mafile");
}
Storage.SaveMafile(mafile);
await Storage.SaveMafileAsync(mafile);
await Done(mafile.RevocationCode ?? string.Empty, mafile.SteamId.Steam64.ToString());
}
+1 -1
View File
@@ -49,7 +49,7 @@ public partial class MainVM : ObservableObject
[RelayCommand]
public async Task Debug()
{
await DialogsController.ShowSetAccountsPasswordDialog();
}
+1 -1
View File
@@ -181,7 +181,7 @@ public partial class MainVM //File //TODO: Refactor
var result = data.SessionData != null;
if (!result) return result;
Storage.SaveMafile(data);
await Storage.SaveMafileAsync(data);
{
ResetQuery();
SearchText = data.AccountName ?? string.Empty;
@@ -0,0 +1,122 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Model;
using SteamLibForked.Models.SteamIds;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NebulaAuth.ViewModel.Other;
public partial class SetAccountPasswordsVM : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SetPasswordsCommand))]
private bool _isEncryptionPasswordSet;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SetEncryptionPasswordCommand))]
private string? _encryptionPassword;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SetPasswordsCommand))]
private string? _accountsPasswords;
[ObservableProperty] private string? _tip;
public SetAccountPasswordsVM()
{
_isEncryptionPasswordSet = PHandler.IsPasswordSet;
}
[RelayCommand(CanExecute = nameof(SetEncryptionPasswordCanExecute))]
private void SetEncryptionPassword()
{
if (string.IsNullOrWhiteSpace(EncryptionPassword))
{
return;
}
Settings.Instance.IsPasswordSet = PHandler.SetPassword(EncryptionPassword);
IsEncryptionPasswordSet = PHandler.IsPasswordSet;
EncryptionPassword = null;
}
private bool SetEncryptionPasswordCanExecute() => !string.IsNullOrWhiteSpace(EncryptionPassword);
[RelayCommand(CanExecute = nameof(SetPasswordsCanExecute))]
private async Task SetPasswords()
{
Tip = null;
var input = AccountsPasswords;
if (string.IsNullOrWhiteSpace(input)) return;
var lines = input.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
var success = 0;
var errors = 0;
var notFound = 0;
var mafs = Storage.MaFiles.ToList();
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line)) continue;
try
{
var split = line.Split(":", 2);
if (split.Length != 2)
{
errors++;
continue;
}
string login = split[0];
string password = split[1];
SteamId64? steamId = null;
if (SteamId64.TryParse(login, out var id64))
{
steamId = id64;
}
var maf = steamId != null
? mafs.FirstOrDefault(m => m.SteamId == steamId || m.AccountName == login)
: mafs.FirstOrDefault(m => string.Equals(m.AccountName, login, StringComparison.OrdinalIgnoreCase));
if (maf == null)
{
notFound++;
continue;
}
maf.Password = PHandler.Encrypt(password);
await Storage.UpdateMafileAsync(maf);
success++;
}
catch
{
errors++;
}
}
if (success > 0 && errors == 0 && notFound == 0)
{
Tip = "Успех";
}
else
{
Tip = $"Успешно: {success}, Не найдено: {notFound}, Ошибки: {errors}";
}
}
private bool SetPasswordsCanExecute()
{
return !string.IsNullOrWhiteSpace(AccountsPasswords) && IsEncryptionPasswordSet;
}
[RelayCommand]
private void ClearTip()
{
Tip = null;
}
}
+1 -2
View File
@@ -4,7 +4,6 @@ using NebulaAuth.Core;
using NebulaAuth.Model;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
@@ -241,7 +240,7 @@ public partial class SettingsVM : ObservableObject
{
var l = GetLoc("PartialSuccess");
RenameResultText =
string.Format(l, result.Total, result.Renamed, result.Errors, result.AlreadyExist, result.BackupFileName);
string.Format(l, result.Total, result.Renamed, result.Errors, result.Conflict, result.BackupFileName);
}
+76 -26
View File
@@ -55,6 +55,11 @@
"ru": "ОК",
"ua": "ОК"
},
"Save": {
"en": "Save",
"ru": "Сохранить",
"ua": "Зберегти"
},
"Abbreviations": {
"Time": {
"Seconds": {
@@ -88,9 +93,9 @@
"Menu": {
"File": {
"Caption": {
"en": "File",
"ru": "Файл",
"ua": "Файл"
"en": "Menu",
"ru": "Меню",
"ua": "Меню"
},
"Import": {
"en": "Import",
@@ -111,6 +116,23 @@
"en": "Settings",
"ru": "Настройки",
"ua": "Налаштування"
},
"ProxyManager": {
"en": "Proxy manager",
"ru": "Менеджер прокси",
"ua": "Менеджер проксі"
},
"Other": {
"Title": {
"en": "Other",
"ru": "Прочее",
"ua": "Інше"
},
"SetPasswords": {
"en": "Set passwords",
"ru": "Назначить пароли",
"ua": "Призначити паролі"
}
}
},
"Account": {
@@ -459,8 +481,7 @@
},
"LegacyMafileModeHint": {
"en": "Save mafiles in compatibility format with Steam Desktop Authenticator and other applications",
"ru":
"Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
"ru": "Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
"ua": "Зберігати мафайли у старому форматі для сумісності зі Steam Desktop Authenticator та іншими додатками"
},
"AllowAutoUpdate": {
@@ -484,12 +505,9 @@
"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 проводить технічні роботи і автопідтвердження вимикаються без потреби"
"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 проводить технічні роботи і автопідтвердження вимикаються без потреби"
}
@@ -674,12 +692,9 @@
"ua": "Пароль шифрування"
},
"DialogText": {
"en":
"You have previously set an encryption password for passwords in mafiles, specify it so that the application can automatically log in in case of problems with the session.\r\n(Optional)",
"ru":
"Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией.\r\n(Не обязательно)",
"ua":
"Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)"
"en": "You have previously set an encryption password for passwords in mafiles, specify it so that the application can automatically log in in case of problems with the session.\r\n(Optional)",
"ru": "Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией.\r\n(Не обязательно)",
"ua": "Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)"
},
"Ok": {
"en": "Ok",
@@ -735,6 +750,39 @@
"ua": "Введіть значення"
}
},
"SetAccountPasswordsView": {
"Title": {
"en": "Set passwords",
"ru": "Назначить пароли",
"ua": "Призначити паролі"
},
"EncryptionPassword": {
"en": "Encryption password:",
"ru": "Пароль шифрования:",
"ua": "Пароль шифрування:"
},
"EnterPassword": {
"en": "Enter password",
"ru": "Введите пароль",
"ua": "Введіть пароль"
},
"Status": {
"en": "Status:",
"ru": "Статус:",
"ua": "Статус:"
},
"EncryptionHint": {
"en": "All passwords in mafiles will be encrypted using the encryption password.",
"ru": "Все пароли в мафайлах будут зашифрованы с помощью пароля шифрования.",
"ua": "Всі паролі в мафайлах будуть зашифровані за допомогою пароля шифрування."
},
"InputFormat": {
"en": "Supported formats:\r\n\r\nlogin:password\r\nsteamid:password",
"ru": "Поддерживаемые форматы:\r\n\r\nlogin:password\r\nsteamid:password",
"ua": "Підтримувані формати:\r\n\r\nlogin:password\r\nsteamid:password"
}
},
"CodeBehind": {
"MainVM": {
"SuccessfulLogin": {
@@ -1381,7 +1429,12 @@
"ru": "Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
"en": "Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
"ua": "Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку"
}
},
"FailedToLoadStorage": {
"en": "Failed to load auto-confirm timers. File seems to be corrupted",
"ru": "Не удалось загрузить таймеры авто-подтверждений. Файл, похоже, поврежден",
"ua": "Не вдалося завантажити таймери авто-підтверджень. Файл, схоже, пошкоджено"
}
},
"SettingsVM": {
"MafileRenaming": {
@@ -1414,15 +1467,12 @@
"en": "Error while loading settings. Settings were reset",
"ru": "Ошибка при загрузке настроек. Настройки были сброшены",
"ua": "Помилка при завантаженні налаштувань. Налаштування були скинуті"
}
}
}
}
},
"CantAlignTimeError": {
"ru":
"Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log",
"en":
"Can't align time with Steam. Maybe no internet connection or Steam is unavailable. Detailed error saved in log.log",
"ua":
"Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log"
"ru": "Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log",
"en": "Can't align time with Steam. Maybe no internet connection or Steam is unavailable. Detailed error saved in log.log",
"ua": "Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log"
}
}