mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 06:14:31 +00:00
Add MafileExport feature and Perform Resharper Cleanup
This commit is contained in:
@@ -3,8 +3,6 @@ using System.Windows;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using NebulaAuth.Model.MAAC;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
|
||||
namespace NebulaAuth;
|
||||
|
||||
@@ -17,12 +15,8 @@ public partial class App
|
||||
var splashScreen = new SplashScreen("Theme\\SplashScreen.png");
|
||||
splashScreen.Show(false, true);
|
||||
base.OnStartup(e);
|
||||
LocManager.Init();
|
||||
LocManager.SetApplicationLocalization(Settings.Instance.Language);
|
||||
Shell.Initialize();
|
||||
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
|
||||
await Storage.Initialize(threads);
|
||||
MAACStorage.Initialize();
|
||||
|
||||
await Shell.Initialize();
|
||||
var mainWindow = new MainWindow();
|
||||
Current.MainWindow = mainWindow;
|
||||
mainWindow.Show();
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
|
||||
<materialDesign:NullableToVisibilityConverter x:Key="NullableToVisibilityConverter" />
|
||||
|
||||
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
|
||||
|
||||
<system:Boolean x:Key="True">True</system:Boolean>
|
||||
<system:Boolean x:Key="False">False</system:Boolean>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
public class InverseBooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is bool b && !b ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value is Visibility v && v != Visibility.Visible;
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,16 @@ public static class DialogsController
|
||||
await DialogHost.Show(dialog);
|
||||
}
|
||||
|
||||
public static async Task ShowExportDialog()
|
||||
{
|
||||
var vm = new MafileExporterVM();
|
||||
var dialog = new MafileExporterView
|
||||
{
|
||||
DataContext = vm
|
||||
};
|
||||
await DialogHost.Show(dialog);
|
||||
}
|
||||
|
||||
#region CommonDialogs
|
||||
|
||||
public static async Task<bool> ShowConfirmCancelDialog(string? msg = null)
|
||||
|
||||
@@ -69,7 +69,7 @@ public static class TrayManager
|
||||
private static void HideMainWindow()
|
||||
{
|
||||
if (!Init) return;
|
||||
if (IsEnabled == false) return;
|
||||
if (!IsEnabled) return;
|
||||
_notifyIcon.Visible = true;
|
||||
MainWindow.Hide();
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
Kind="FileReplaceOutline" />
|
||||
|
||||
</StackPanel>
|
||||
<ToolBarTray>
|
||||
<ToolBarTray Grid.Row="0">
|
||||
<ToolBar ClipToBounds="True" HorizontalContentAlignment="Stretch"
|
||||
Style="{StaticResource MaterialDesignToolBar}" FontSize="16">
|
||||
<Menu FontSize="16">
|
||||
@@ -92,7 +92,9 @@
|
||||
<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}" />
|
||||
Command="{Binding OpenSetPasswordsDialogCommand}" />
|
||||
<MenuItem Header="{Tr MainWindow.Menu.File.Other.OpenExport}"
|
||||
Command="{Binding OpenExporterDialogCommand}" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
@@ -356,7 +358,7 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<md:PackIcon Kind="PlusCircle"
|
||||
VerticalAlignment="Center" />
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Margin="5,0,0,0" Grid.Column="1"
|
||||
Text="{Tr MainWindow.ContextMenus.Mafile.CreateGroup}" />
|
||||
|
||||
@@ -30,7 +30,7 @@ public partial class MainWindow
|
||||
private async void OnApplicationStarted(object? sender, EventArgs e)
|
||||
{
|
||||
((MainVM) DataContext).CurrentDialogHost = DialogHostInstance;
|
||||
if (Settings.Instance.IsPasswordSet == false) return;
|
||||
if (!Settings.Instance.IsPasswordSet) return;
|
||||
Topmost = false;
|
||||
await Dispatcher.InvokeAsync(ShowSetPasswordDialog, DispatcherPriority.ContextIdle);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public partial class MainWindow
|
||||
|
||||
var result = await DialogHost.Show(dialog);
|
||||
var pass = vm.Password;
|
||||
if (result is true && string.IsNullOrWhiteSpace(pass) == false)
|
||||
if (result is true && !string.IsNullOrWhiteSpace(pass))
|
||||
{
|
||||
PHandler.SetPassword(pass);
|
||||
}
|
||||
@@ -79,12 +79,12 @@ public partial class MainWindow
|
||||
|
||||
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
if (int.TryParse(e.Text, out _) == false) e.Handled = true;
|
||||
if (!int.TryParse(e.Text, out _)) e.Handled = true;
|
||||
}
|
||||
|
||||
private void Rectangle_DragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false)
|
||||
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
@@ -96,7 +96,7 @@ public partial class MainWindow
|
||||
|
||||
private async void Rectangle_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
|
||||
if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
|
||||
var filePaths = (string[]) e.Data.GetData(DataFormats.FileDrop)!;
|
||||
if (filePaths.Length == 0) return;
|
||||
if (DataContext is MainVM mainVm)
|
||||
|
||||
@@ -187,7 +187,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable
|
||||
|
||||
private bool IgnoreTimerErrors()
|
||||
{
|
||||
if (Settings.Instance.IgnorePatchTuesdayErrors == false) return false;
|
||||
if (!Settings.Instance.IgnorePatchTuesdayErrors) return false;
|
||||
|
||||
var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
|
||||
var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone);
|
||||
|
||||
@@ -153,7 +153,7 @@ public static class MaClient
|
||||
if (mafile.SessionData.RefreshToken.IsExpired)
|
||||
throw new SessionPermanentlyExpiredException();
|
||||
|
||||
if (ignoreAccessToken == false)
|
||||
if (!ignoreAccessToken)
|
||||
{
|
||||
var access = mafile.SessionData.GetMobileToken();
|
||||
if (access == null || access.Value.IsExpired)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using NebulaAuth.Model.Entities;
|
||||
|
||||
namespace NebulaAuth.Model.MafileExport;
|
||||
|
||||
public class ExportResult
|
||||
{
|
||||
public string? Error { get; }
|
||||
public Dictionary<Mafile, string>? Exported { get; }
|
||||
public List<string>? NotFound { get; }
|
||||
public List<string>? Conflict { get; }
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Exported))]
|
||||
[MemberNotNullWhen(true, nameof(NotFound))]
|
||||
[MemberNotNullWhen(true, nameof(Conflict))]
|
||||
[MemberNotNullWhen(false, nameof(Error))]
|
||||
public bool Success => Error == null;
|
||||
|
||||
private ExportResult(string? error, Dictionary<Mafile, string>? exported, List<string>? notFound,
|
||||
List<string>? conflict)
|
||||
{
|
||||
Error = error;
|
||||
Exported = exported;
|
||||
NotFound = notFound;
|
||||
Conflict = conflict;
|
||||
}
|
||||
|
||||
public ExportResult(Dictionary<Mafile, string> exported, List<string> notFound, List<string> conflict)
|
||||
: this(null, exported, notFound, conflict)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public ExportResult(string? error)
|
||||
: this(error, null, null, null)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace NebulaAuth.Model.MafileExport;
|
||||
|
||||
public class MafileExportTemplate
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool UseLoginAsMafileName { get; set; }
|
||||
public bool IncludeSharedSecret { get; set; }
|
||||
public bool IncludeIdentitySecret { get; set; }
|
||||
public bool IncludeRCode { get; set; }
|
||||
public bool IncludeSessionData { get; set; }
|
||||
public bool IncludeOtherInfo { get; set; }
|
||||
public bool IncludeNebulaProxy { get; set; }
|
||||
public bool IncludeNebulaPassword { get; set; }
|
||||
public bool IncludeNebulaGroup { get; set; }
|
||||
public string? Path { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
using SteamLibForked.Models.SteamIds;
|
||||
|
||||
namespace NebulaAuth.Model.MafileExport;
|
||||
|
||||
public static class MafileExporter
|
||||
{
|
||||
public static async Task<ExportResult> ExportMafiles(IEnumerable<string> keys, MafileExportTemplate template)
|
||||
{
|
||||
if (template.Path == null || !PathIsValid(template.Path))
|
||||
{
|
||||
return new ExportResult(LocManager.GetCodeBehindOrDefault("InvalidPath", "MafileExporter.InvalidPath"));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(template.Path))
|
||||
{
|
||||
Directory.CreateDirectory(template.Path);
|
||||
}
|
||||
|
||||
if (IsNebulaUtilizedDirectory(template.Path))
|
||||
{
|
||||
return new ExportResult(LocManager.GetCodeBehindOrDefault("NebulaUtilizedDirectory",
|
||||
"MafileExporter.NebulaUtilizedDirectory"));
|
||||
}
|
||||
|
||||
if (!IsAllowedToWrite(template.Path))
|
||||
{
|
||||
return new ExportResult(LocManager.GetCodeBehindOrDefault("AccessDenied", "MafileExporter.AccessDenied"));
|
||||
}
|
||||
|
||||
|
||||
var exported = new Dictionary<Mafile, string>();
|
||||
var notFound = new List<string>();
|
||||
var conflict = new List<string>();
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
SteamId? steamId = null;
|
||||
if (SteamId64.TryParse(key, out var id64))
|
||||
{
|
||||
steamId = id64;
|
||||
}
|
||||
|
||||
var maf = Storage.MaFiles.FirstOrDefault(m => m.AccountName.EqualsIgnoreCase(key) || m.SteamId == steamId);
|
||||
if (maf != null)
|
||||
{
|
||||
var fileName = await ExportMafile(template.Path, template, maf);
|
||||
if (fileName == null)
|
||||
{
|
||||
conflict.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
exported[maf] = fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
notFound.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return new ExportResult(exported, notFound, conflict);
|
||||
}
|
||||
|
||||
private static async Task<string?> ExportMafile(string path, MafileExportTemplate template, Mafile mafile)
|
||||
{
|
||||
var serializeMaf = new Mafile
|
||||
{
|
||||
SharedSecret = template.IncludeSharedSecret ? mafile.SharedSecret : null!,
|
||||
IdentitySecret = template.IncludeIdentitySecret ? mafile.IdentitySecret : null!,
|
||||
DeviceId = template.IncludeIdentitySecret ? mafile.DeviceId : null!,
|
||||
RevocationCode = template.IncludeRCode ? mafile.RevocationCode : null!,
|
||||
AccountName = mafile.AccountName,
|
||||
SessionData = template.IncludeSessionData ? mafile.SessionData : null!,
|
||||
SteamId = mafile.SteamId,
|
||||
ServerTime = template.IncludeOtherInfo ? mafile.ServerTime : 0,
|
||||
SerialNumber = template.IncludeOtherInfo ? mafile.SerialNumber : 0,
|
||||
Uri = template.IncludeOtherInfo ? mafile.Uri : null!,
|
||||
TokenGid = template.IncludeOtherInfo ? mafile.TokenGid : null!,
|
||||
Secret1 = template.IncludeOtherInfo ? mafile.Secret1 : null!,
|
||||
Proxy = template.IncludeNebulaProxy ? mafile.Proxy : null!,
|
||||
Group = template.IncludeNebulaGroup ? mafile.Group : null!,
|
||||
Password = template.IncludeNebulaPassword ? mafile.Password : null!,
|
||||
LinkedClient = null,
|
||||
Filename = null
|
||||
};
|
||||
|
||||
var serialized = NebulaSerializer.SerializeMafile(serializeMaf);
|
||||
var strategy = template.UseLoginAsMafileName ? MafileNamingStrategy.Login : MafileNamingStrategy.SteamId;
|
||||
var fileName = strategy.GetMafileName(serializeMaf);
|
||||
var fullPath = Path.Combine(path, fileName);
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(fullPath, serialized);
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
private static bool PathIsValid(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var full = Path.GetFullPath(path);
|
||||
|
||||
var invalidPathChars = Path.GetInvalidPathChars();
|
||||
if (full.IndexOfAny(invalidPathChars) >= 0) return false;
|
||||
|
||||
var invalidFileChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
foreach (var part in full.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
|
||||
{
|
||||
if (string.IsNullOrEmpty(part)) continue;
|
||||
if (part is [_, ':']) continue;
|
||||
|
||||
if (part.IndexOfAny(invalidFileChars) >= 0) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAllowedToWrite(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var testFilePath = Path.Combine(path, "test.tmp");
|
||||
using (var fs = File.Create(testFilePath))
|
||||
{
|
||||
}
|
||||
|
||||
File.Delete(testFilePath);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNebulaUtilizedDirectory(string path)
|
||||
{
|
||||
return Storage.MafilesDirectories.Any(x => Path.GetFullPath(path).EqualsIgnoreCase(x));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NebulaAuth.Model.MafileExport;
|
||||
|
||||
public static class MafileExporterStorage
|
||||
{
|
||||
private const string FILE_NAME = "export_templates.json";
|
||||
public static ObservableCollection<MafileExportTemplate> Templates { get; } = new();
|
||||
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
LoadOrCreateTemplate();
|
||||
}
|
||||
|
||||
public static MafileExportTemplate? GetTemplate(string name)
|
||||
{
|
||||
return Templates.FirstOrDefault(x => x.Name == name);
|
||||
}
|
||||
|
||||
public static void AddTemplate(MafileExportTemplate template)
|
||||
{
|
||||
Templates.Add(template);
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void DeleteTemplate(MafileExportTemplate template)
|
||||
{
|
||||
if (Templates.Remove(template))
|
||||
{
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadOrCreateTemplate()
|
||||
{
|
||||
if (!File.Exists(FILE_NAME))
|
||||
{
|
||||
Templates.Clear();
|
||||
foreach (var mafileExportTemplate in CreateDefaultTemplate())
|
||||
{
|
||||
Templates.Add(mafileExportTemplate);
|
||||
}
|
||||
|
||||
Save();
|
||||
return;
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(FILE_NAME);
|
||||
try
|
||||
{
|
||||
var loadedTemplates = JsonConvert.DeserializeObject<List<MafileExportTemplate>>(json);
|
||||
if (loadedTemplates != null)
|
||||
{
|
||||
Templates.Clear();
|
||||
foreach (var tmpl in loadedTemplates)
|
||||
{
|
||||
Templates.Add(tmpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Error(ex, $"Failed to deserialize {FILE_NAME}");
|
||||
Templates.Clear();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
var json = JsonConvert.SerializeObject(Templates, Formatting.Indented);
|
||||
File.WriteAllText(FILE_NAME, json);
|
||||
}
|
||||
|
||||
private static IEnumerable<MafileExportTemplate> CreateDefaultTemplate()
|
||||
{
|
||||
return
|
||||
[
|
||||
new MafileExportTemplate
|
||||
{
|
||||
Name = "Default",
|
||||
UseLoginAsMafileName = false,
|
||||
IncludeSharedSecret = true,
|
||||
IncludeIdentitySecret = true,
|
||||
IncludeRCode = true,
|
||||
IncludeSessionData = true,
|
||||
IncludeOtherInfo = true,
|
||||
IncludeNebulaProxy = true,
|
||||
IncludeNebulaPassword = true,
|
||||
IncludeNebulaGroup = true,
|
||||
Path = null
|
||||
},
|
||||
new MafileExportTemplate
|
||||
{
|
||||
Name = "Auth only",
|
||||
UseLoginAsMafileName = true,
|
||||
IncludeSharedSecret = true,
|
||||
IncludeIdentitySecret = false,
|
||||
IncludeRCode = false,
|
||||
IncludeSessionData = false,
|
||||
IncludeOtherInfo = false,
|
||||
IncludeNebulaProxy = false,
|
||||
IncludeNebulaPassword = false,
|
||||
IncludeNebulaGroup = false,
|
||||
Path = null
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
using NebulaAuth.Model.Entities;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
using NebulaAuth.Model.Entities;
|
||||
|
||||
namespace NebulaAuth.Model.Mafiles;
|
||||
|
||||
internal static class MafilesStorageHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns <see cref="Mafile.Filename" /> or creates a new one and updates the property. <see cref="Mafile.Filename"/> is always not <see langword="null"/> after this method call.
|
||||
/// Returns <see cref="Mafile.Filename" /> or creates a new one and updates the property.
|
||||
/// <see cref="Mafile.Filename" /> is always not <see langword="null" /> after this method call.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -22,12 +22,19 @@ public static class Storage
|
||||
public const string DIR_MAFILES = "maFiles";
|
||||
public const string DIR_REMOVED_MAFILES = "maFiles_removed";
|
||||
public const string DIR_BACKUP_MAFILES = "maFiles_backup";
|
||||
|
||||
public static readonly string[] MafilesDirectories;
|
||||
public static string MafilesDirectory { get; } = Path.GetFullPath(DIR_MAFILES);
|
||||
public static string RemovedMafilesDirectory { get; } = Path.GetFullPath(DIR_REMOVED_MAFILES);
|
||||
public static string BackupMafilesDirectory { get; } = Path.GetFullPath(DIR_BACKUP_MAFILES);
|
||||
|
||||
public static ObservableCollection<Mafile> MaFiles { get; private set; } = [];
|
||||
|
||||
static Storage()
|
||||
{
|
||||
MafilesDirectories = [MafilesDirectory, RemovedMafilesDirectory, BackupMafilesDirectory];
|
||||
}
|
||||
|
||||
public static async Task Initialize(int threadCount, CancellationToken token = default)
|
||||
{
|
||||
Directory.CreateDirectory(MafilesDirectory);
|
||||
@@ -60,6 +67,7 @@ public static class Storage
|
||||
}, token);
|
||||
|
||||
MaFiles = new ObservableCollection<Mafile>(localList.OrderBy(m => m.AccountName));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -38,7 +38,7 @@ public static class NebulaSerializer
|
||||
var data = Serializer.Deserialize(cont);
|
||||
var mobileData = data.Data;
|
||||
var info = data.Info;
|
||||
if (info.IsExtended == false)
|
||||
if (!info.IsExtended)
|
||||
throw new FormatException("Mafile is not extended data");
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public static class NebulaSerializer
|
||||
|
||||
private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
|
||||
{
|
||||
if (dictionary.TryGetValue(name, out var prop) == false) return default;
|
||||
if (!dictionary.TryGetValue(name, out var prop)) return default;
|
||||
var value = prop.Value;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ public static class ProxyStorage
|
||||
|
||||
static ProxyStorage()
|
||||
{
|
||||
if (File.Exists("proxies.json") == false)
|
||||
if (!File.Exists("proxies.json"))
|
||||
return;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -45,7 +45,7 @@ public static partial class SessionHandler
|
||||
return await func();
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
when (refreshTokenExpired == false || password != null)
|
||||
when (!refreshTokenExpired || password != null)
|
||||
{
|
||||
if (ex is SessionPermanentlyExpiredException)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ public partial class Settings : ObservableObject
|
||||
|
||||
static Settings()
|
||||
{
|
||||
if (File.Exists("settings.json") == false)
|
||||
if (!File.Exists("settings.json"))
|
||||
{
|
||||
Instance = new Settings();
|
||||
Instance.PropertyChanged += SettingsOnPropertyChanged;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using NebulaAuth.Model.MAAC;
|
||||
using NebulaAuth.Model.MafileExport;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
using NLog;
|
||||
using NLog.Extensions.Logging;
|
||||
using SteamLib.Core;
|
||||
@@ -14,8 +20,11 @@ public static class Shell
|
||||
public static Logger Logger { get; private set; } = null!;
|
||||
public static ILogger ExtensionsLogger { get; private set; } = null!;
|
||||
|
||||
public static void Initialize()
|
||||
public static async Task Initialize()
|
||||
{
|
||||
File.Delete("log.log");
|
||||
LocManager.Init();
|
||||
LocManager.SetApplicationLocalization(Settings.Instance.Language);
|
||||
Logger = LogManager.GetLogger("Logger");
|
||||
var lp = new NLogLoggerProvider();
|
||||
var logger = lp.CreateLogger("SteamLib");
|
||||
@@ -27,13 +36,18 @@ public static class Shell
|
||||
|
||||
try
|
||||
{
|
||||
TimeAligner.AlignTime();
|
||||
await TimeAligner.AlignTimeAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new CantAlignTimeException("", ex);
|
||||
}
|
||||
|
||||
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
|
||||
await Storage.Initialize(threads);
|
||||
MAACStorage.Initialize();
|
||||
MafileExporterStorage.Initialize();
|
||||
|
||||
ExtensionsLogger.LogDebug("Application started");
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
throwExceptions="true">
|
||||
|
||||
<targets>
|
||||
<target xsi:type="File" name="File" fileName="log.log"
|
||||
deleteOldFileOnStartup="true">
|
||||
<target xsi:type="File" name="File" fileName="${basedir}/logs/log-${shortdate}.log">
|
||||
<layout xsi:type='CompoundLayout'>
|
||||
<layout xsi:type="JsonLayout" includeEventProperties="true" IncludeScopeProperties="true">
|
||||
<attribute name="time" layout="${longdate}" />
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.4.1" />
|
||||
<PackageReference Include="CodingSebLocalization.Fody" Version="1.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="5.2.1" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="5.3.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||
<PackageReference Include="NLog" Version="5.5.0" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.5.0" />
|
||||
|
||||
@@ -31,8 +31,8 @@ public static class LanguageUtility
|
||||
"RU", "BY", "KZ", "KG", "TJ", "TM", "UZ", "AM", "AZ", "GE", "MD"
|
||||
];
|
||||
|
||||
return cisRegions.Any(r => userRegion.EndsWith(r, StringComparison.OrdinalIgnoreCase))
|
||||
? LocalizationLanguage.Russian
|
||||
return cisRegions.Any(r => userRegion.EndsWith(r, StringComparison.OrdinalIgnoreCase))
|
||||
? LocalizationLanguage.Russian
|
||||
: LocalizationLanguage.English;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<UserControl x:Class="NebulaAuth.View.MafileExporterView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
|
||||
mc:Ignorable="d"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
MinWidth="400"
|
||||
MaxWidth="400"
|
||||
Background="{DynamicResource WindowBackground}"
|
||||
d:DataContext="{d:DesignInstance other:MafileExporterVM}"
|
||||
d:Background="#141119"
|
||||
d:Foreground="White"
|
||||
FontSize="18">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="FileExport" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr ExportDialog.ExportTitle}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
||||
IsCancel="True">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Background="DarkGray" Opacity="0.4" Grid.Row="1" />
|
||||
<Grid Grid.Row="2" Margin="15" TextElement.FontSize="14">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ComboBox Grid.Column="0"
|
||||
Padding="10"
|
||||
Style="{StaticResource MaterialDesignOutlinedComboBox}"
|
||||
Margin="0,0,5,0"
|
||||
materialDesign:HintAssist.Hint="{Tr ExportDialog.TemplateHint}"
|
||||
ItemsSource="{Binding Templates}"
|
||||
DisplayMemberPath="Name"
|
||||
SelectedItem="{Binding CurrentTemplate}"
|
||||
Visibility="{Binding EditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
|
||||
|
||||
<TextBox x:Name="EditTemplateNameTextBox"
|
||||
Grid.Column="0"
|
||||
Padding="10"
|
||||
Margin="0,0,5,0"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
materialDesign:HintAssist.Hint="{Tr ExportDialog.TemplateHint}"
|
||||
Text="{Binding CurrentTemplate.Name,
|
||||
FallbackValue=''}"
|
||||
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}"
|
||||
Visibility="{Binding EditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Enter"
|
||||
Command="{Binding ToggleEditModeCommand}" />
|
||||
</TextBox.InputBindings>
|
||||
|
||||
</TextBox>
|
||||
|
||||
|
||||
<Button Command="{Binding AddTemplateCommand}" Grid.Column="1">
|
||||
<materialDesign:PackIcon Kind="Add" />
|
||||
</Button>
|
||||
|
||||
<Button Command="{Binding ToggleEditModeCommand}" Click="EditButton_Click" Grid.Column="2">
|
||||
<materialDesign:PackIcon Kind="Edit" />
|
||||
</Button>
|
||||
|
||||
<Button Grid.Column="3" Command="{Binding RemoveCurrentTemplateCommand}">
|
||||
<materialDesign:PackIcon Kind="Trash" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,15,0,0" Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}"
|
||||
MouseDoubleClick="PathTB_DoubleClick" materialDesign:TextFieldAssist.HasClearButton="True"
|
||||
Text="{Binding CurrentTemplate.Path, FallbackValue=''}" Padding="10"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}" Grid.Column="0"
|
||||
materialDesign:HintAssist.Hint="{Tr ExportDialog.SelectPathHint}" />
|
||||
<Button Command="{Binding OpenSelectDirectoryDialogCommand}" Grid.Column="1" Margin="5,0,0,0">
|
||||
<materialDesign:PackIcon Kind="FolderOpen" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="2" BorderThickness="1" CornerRadius="4" Margin="0,15,0,0" Padding="2">
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource BaseContentColor}" Opacity="0.4" />
|
||||
</Border.BorderBrush>
|
||||
<Expander x:Name="TemplateExpander"
|
||||
Header="{Tr ExportDialog.ExportSettingsHeader}"
|
||||
materialDesign:ExpanderAssist.HorizontalHeaderPadding="5"
|
||||
materialDesign:ExpanderAssist.ExpanderButtonPosition="Start"
|
||||
HorizontalAlignment="Stretch"
|
||||
Padding="5,0,5,5"
|
||||
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}">
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock FontWeight="DemiBold"
|
||||
Text="{Tr ExportDialog.ExportSettingsCommon}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.UseLoginAsMafileName, FallbackValue=False}"
|
||||
Margin="3,0,0,0"
|
||||
Content="{Tr ExportDialog.ExportOptions.UseLoginAsMafileName}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.UseLoginAsMafileName}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeSharedSecret, FallbackValue=False}"
|
||||
Margin="3,0,0,0"
|
||||
Content="{Tr ExportDialog.ExportOptions.IncludeSharedSecret}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.IncludeSharedSecret}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeIdentitySecret, FallbackValue=False}"
|
||||
Margin="3,0,0,0"
|
||||
Content="{Tr ExportDialog.ExportOptions.IncludeIdentitySecret}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.IncludeIdentitySecret}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeRCode, FallbackValue=False}"
|
||||
Margin="3,0,0,0"
|
||||
Content="{Tr ExportDialog.ExportOptions.IncludeRCode}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.IncludeRCode}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeSessionData, FallbackValue=False}"
|
||||
Margin="3,0,0,0"
|
||||
Content="{Tr ExportDialog.ExportOptions.IncludeSessionData}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.IncludeSessionData}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.IncludeOtherInfo, FallbackValue=False}"
|
||||
Margin="3,0,0,0"
|
||||
Content="{Tr ExportDialog.ExportOptions.IncludeOtherInfo}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.IncludeOtherInfo}" />
|
||||
|
||||
<TextBlock FontWeight="DemiBold"
|
||||
Margin="0,10,0,0"
|
||||
Text="{Tr ExportDialog.ExportSettingsNebula}" />
|
||||
|
||||
<Grid Margin="3,0,3,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<CheckBox Grid.Column="0"
|
||||
IsChecked="{Binding CurrentTemplate.IncludeNebulaProxy, FallbackValue=False}"
|
||||
Content="{Tr ExportDialog.ExportOptions.NebulaProxy}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.NebulaProxy}" />
|
||||
|
||||
<CheckBox Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding CurrentTemplate.IncludeNebulaPassword, FallbackValue=False}"
|
||||
Content="{Tr ExportDialog.ExportOptions.NebulaPassword}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.NebulaPassword}" />
|
||||
|
||||
<CheckBox Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
IsChecked="{Binding CurrentTemplate.IncludeNebulaGroup, FallbackValue=False}"
|
||||
Content="{Tr ExportDialog.ExportOptions.NebulaGroup}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.NebulaGroup}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</Expander>
|
||||
</Border>
|
||||
<TextBox GotFocus="ExportTB_GotFocus" PreviewMouseUp="ExportTB_GotFocus" Grid.Row="3"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}" MaxHeight="600" Margin="0,10,0,0"
|
||||
MinHeight="250" Height="250" AcceptsReturn="True"
|
||||
Text="{Binding AccountsText, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsEnabled="{Binding ExportCommand.IsRunning, Converter={StaticResource ReverseBooleanConverter}}"
|
||||
Name="AccountsTB" />
|
||||
|
||||
|
||||
<TextBlock Grid.Row="3"
|
||||
Text="{Tr ExportDialog.ExportAccountsPlaceholder}"
|
||||
Margin="18,27,10,10"
|
||||
Opacity="0.6"
|
||||
IsHitTestVisible="False"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding Text.IsEmpty,
|
||||
ElementName=AccountsTB,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<nebulaAuth:HintBox Margin="0,10,0,0" Severity="{Binding HintBoxSeverity}" Grid.Row="4"
|
||||
Text="{Binding HintText}" />
|
||||
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
||||
<Separator Grid.Row="3" />
|
||||
<Button Command="{Binding ExportCommand}" Style="{StaticResource MaterialDesignFlatDarkBgButton}" Grid.Row="4"
|
||||
Margin="20,7,20,7" MaxWidth="150" Content="{Tr ExportDialog.ExportButton}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
public partial class MafileExporterView : UserControl
|
||||
{
|
||||
public MafileExporterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void PathTB_DoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is TextBox textBox)
|
||||
{
|
||||
textBox.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportTB_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TemplateExpander.IsExpanded = false;
|
||||
}
|
||||
|
||||
private void EditButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() => { EditTemplateNameTextBox.Focus(); }, DispatcherPriority.Input);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
using SteamLib.SteamMobile;
|
||||
using SteamLibForked.Exceptions.Authorization;
|
||||
@@ -23,7 +22,6 @@ public partial class MainVM : ObservableObject
|
||||
{
|
||||
[UsedImplicitly] public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
|
||||
|
||||
|
||||
public Mafile? SelectedMafile
|
||||
{
|
||||
get => _selectedMafile;
|
||||
@@ -50,10 +48,8 @@ public partial class MainVM : ObservableObject
|
||||
[RelayCommand]
|
||||
public async Task Debug()
|
||||
{
|
||||
await DialogsController.ShowSetAccountsPasswordDialog();
|
||||
}
|
||||
|
||||
|
||||
private void SetMafile(Mafile? mafile)
|
||||
{
|
||||
if (mafile != SelectedMafile)
|
||||
@@ -135,12 +131,6 @@ public partial class MainVM : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LinkAccount()
|
||||
{
|
||||
await DialogsController.ShowLinkerDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task MoveAccount()
|
||||
{
|
||||
@@ -218,16 +208,6 @@ public partial class MainVM : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenLinksView()
|
||||
{
|
||||
CurrentDialogHost.CloseOnClickAway = true;
|
||||
var view = new LinksView();
|
||||
await DialogHost.Show(view);
|
||||
CurrentDialogHost.CloseOnClickAway = false;
|
||||
}
|
||||
|
||||
|
||||
private static string GetLocalization(string key)
|
||||
{
|
||||
const string locPath = "MainVM";
|
||||
|
||||
@@ -240,7 +240,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
|
||||
var arr = files.Cast<string>().ToArray();
|
||||
if (arr.All(p => p.ContainsIgnoreCase("mafile") == false)) return;
|
||||
if (arr.All(p => !p.ContainsIgnoreCase("mafile"))) return;
|
||||
|
||||
|
||||
await AddMafile(arr);
|
||||
|
||||
@@ -88,7 +88,7 @@ public partial class MainVM //Groups
|
||||
Storage.UpdateMafile(mafile);
|
||||
OnPropertyChanged(nameof(SelectedMafile)); //For bindings
|
||||
QueryGroups();
|
||||
if (Groups.All(g => g.Equals(mafGroup) == false))
|
||||
if (Groups.All(g => !g.Equals(mafGroup)))
|
||||
{
|
||||
SelectedGroup = null;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public partial class MainVM //Groups
|
||||
private void QueryGroups()
|
||||
{
|
||||
var groups = Storage.MaFiles
|
||||
.Where(m => string.IsNullOrWhiteSpace(m.Group) == false)
|
||||
.Where(m => !string.IsNullOrWhiteSpace(m.Group))
|
||||
.Select(m => m.Group)
|
||||
.Distinct()
|
||||
.Order()
|
||||
|
||||
@@ -130,7 +130,7 @@ public partial class MainVM //MAAC
|
||||
{
|
||||
mafiles = mafiles.ToArray();
|
||||
|
||||
var turnOn = mafiles.All(m => m.LinkedClient == null || GetCurrentMode(m.LinkedClient) == false);
|
||||
var turnOn = mafiles.All(m => m.LinkedClient == null || !GetCurrentMode(m.LinkedClient));
|
||||
if (turnOn)
|
||||
{
|
||||
foreach (var mafile in mafiles)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.View;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM : ObservableObject
|
||||
{
|
||||
[RelayCommand]
|
||||
private Task OpenSetPasswordsDialog()
|
||||
{
|
||||
return DialogsController.ShowSetAccountsPasswordDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task OpenExporterDialog()
|
||||
{
|
||||
return DialogsController.ShowExportDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public Task LinkAccount()
|
||||
{
|
||||
return DialogsController.ShowLinkerDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenLinksView()
|
||||
{
|
||||
CurrentDialogHost.CloseOnClickAway = true;
|
||||
var view = new LinksView();
|
||||
await DialogHost.Show(view);
|
||||
CurrentDialogHost.CloseOnClickAway = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.MafileExport;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public partial class MafileExporterVM : ObservableObject
|
||||
{
|
||||
public MafileExportTemplateVM? CurrentTemplate
|
||||
{
|
||||
get => _currentTemplate;
|
||||
set => SetCurrentTemplate(value);
|
||||
}
|
||||
|
||||
|
||||
public ObservableCollection<MafileExportTemplateVM> Templates { get; }
|
||||
|
||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ExportCommand))]
|
||||
private string? _accountsText;
|
||||
|
||||
private string? _cachedName;
|
||||
private MafileExportTemplateVM? _currentTemplate;
|
||||
|
||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(AddTemplateCommand), nameof(RemoveCurrentTemplateCommand))]
|
||||
private bool _editMode;
|
||||
|
||||
[ObservableProperty] private HintBoxSeverity _hintBoxSeverity = HintBoxSeverity.Error;
|
||||
|
||||
[ObservableProperty] private string? _hintText;
|
||||
|
||||
public MafileExporterVM()
|
||||
{
|
||||
var templates = MafileExporterStorage.Templates
|
||||
.Select(MafileExportTemplateVM.FromModel);
|
||||
|
||||
|
||||
Templates = new ObservableCollection<MafileExportTemplateVM>(templates);
|
||||
MafileExporterStorage.Templates.CollectionChanged += OnTemplatesOnCollectionChanged;
|
||||
CurrentTemplate = Templates.FirstOrDefault();
|
||||
}
|
||||
|
||||
private void SetCurrentTemplate(MafileExportTemplateVM? value)
|
||||
{
|
||||
if (_currentTemplate != null)
|
||||
{
|
||||
_currentTemplate.PropertyChanged -= OnCurrentTemplatePropertyChanged;
|
||||
}
|
||||
|
||||
SetProperty(ref _currentTemplate, value, nameof(CurrentTemplate));
|
||||
if (value != null)
|
||||
{
|
||||
value.PropertyChanged += OnCurrentTemplatePropertyChanged;
|
||||
}
|
||||
|
||||
ToggleEditModeCommand.NotifyCanExecuteChanged();
|
||||
RemoveCurrentTemplateCommand.NotifyCanExecuteChanged();
|
||||
OpenSelectDirectoryDialogCommand.NotifyCanExecuteChanged();
|
||||
ExportCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private void OnTemplatesOnCollectionChanged(object? s, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (MafileExportTemplate newItem in e.NewItems!)
|
||||
{
|
||||
Templates.Add(MafileExportTemplateVM.FromModel(newItem));
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (MafileExportTemplate oldItem in e.OldItems!)
|
||||
{
|
||||
var vmToRemove = Templates.FirstOrDefault(x => x.Name == oldItem.Name);
|
||||
if (vmToRemove != null)
|
||||
{
|
||||
Templates.Remove(vmToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
foreach (MafileExportTemplate newItem in e.NewItems!)
|
||||
{
|
||||
var vmToReplace = Templates.FirstOrDefault(x => x.Name == newItem.Name);
|
||||
if (vmToReplace != null)
|
||||
{
|
||||
var index = Templates.IndexOf(vmToReplace);
|
||||
Templates[index] = MafileExportTemplateVM.FromModel(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
Templates.Clear();
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCurrentTemplatePropertyChanged(object? sender, PropertyChangedEventArgs propertyChangedEventArgs)
|
||||
{
|
||||
var currentTemplate = CurrentTemplate;
|
||||
if (currentTemplate == null) return;
|
||||
var key = propertyChangedEventArgs.PropertyName == nameof(MafileExportTemplateVM.Name)
|
||||
? _cachedName!
|
||||
: currentTemplate.Name;
|
||||
|
||||
var existed = MafileExporterStorage.GetTemplate(key);
|
||||
if (existed != null)
|
||||
{
|
||||
currentTemplate.ApplyTo(existed);
|
||||
MafileExporterStorage.Save();
|
||||
}
|
||||
else
|
||||
{
|
||||
MafileExporterStorage.AddTemplate(currentTemplate.ToModel());
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CurrentTemplateNotNull))]
|
||||
private void ToggleEditMode()
|
||||
{
|
||||
EditMode = !EditMode;
|
||||
if (EditMode) _cachedName = CurrentTemplate?.Name;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(NotEditMode))]
|
||||
private void AddTemplate()
|
||||
{
|
||||
ResetHintText();
|
||||
var name = "New Template";
|
||||
var i = 0;
|
||||
while (Templates.Any(x => x.Name == name))
|
||||
{
|
||||
name = $"New Template ({++i})";
|
||||
}
|
||||
|
||||
var template = new MafileExportTemplate
|
||||
{
|
||||
Name = name,
|
||||
UseLoginAsMafileName = false,
|
||||
IncludeSharedSecret = true,
|
||||
IncludeIdentitySecret = true,
|
||||
IncludeRCode = true,
|
||||
IncludeSessionData = true,
|
||||
IncludeOtherInfo = true,
|
||||
IncludeNebulaProxy = true,
|
||||
IncludeNebulaPassword = true,
|
||||
IncludeNebulaGroup = true,
|
||||
Path = null
|
||||
};
|
||||
MafileExporterStorage.AddTemplate(template);
|
||||
CurrentTemplate = Templates.FirstOrDefault(x => x.Name == name);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CurrentTemplateNotNullAndNotEditMode))]
|
||||
private void RemoveCurrentTemplate()
|
||||
{
|
||||
if (CurrentTemplate != null)
|
||||
{
|
||||
ResetHintText();
|
||||
var index = Templates.IndexOf(CurrentTemplate);
|
||||
var existed = MafileExporterStorage.GetTemplate(CurrentTemplate.Name);
|
||||
if (existed != null)
|
||||
{
|
||||
MafileExporterStorage.DeleteTemplate(existed);
|
||||
}
|
||||
|
||||
if (Templates.Count > 0)
|
||||
{
|
||||
if (index >= Templates.Count)
|
||||
{
|
||||
index = Templates.Count - 1;
|
||||
}
|
||||
|
||||
CurrentTemplate = Templates[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentTemplate = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CurrentTemplateNotNull))]
|
||||
private void OpenSelectDirectoryDialog()
|
||||
{
|
||||
if (CurrentTemplate == null) return;
|
||||
var dialog = new FolderBrowserDialog
|
||||
{
|
||||
UseDescriptionForTitle = true,
|
||||
ShowNewFolderButton = true
|
||||
};
|
||||
var result = dialog.ShowDialog();
|
||||
if (result == DialogResult.OK)
|
||||
{
|
||||
CurrentTemplate!.Path = dialog.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(ExportCanExecute))]
|
||||
private async Task Export()
|
||||
{
|
||||
var lines = AccountsText;
|
||||
var template = CurrentTemplate;
|
||||
if (string.IsNullOrWhiteSpace(lines) || template == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var split = Regex.Split(lines, "\r\n|\r|\n").Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
|
||||
ResetHintText();
|
||||
ExportResult res;
|
||||
try
|
||||
{
|
||||
res = await MafileExporter.ExportMafiles(split, template.ToModel());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetHintText(ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res.Success)
|
||||
{
|
||||
SetHintText(res.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var hint = string.Format(LocManager.GetCodeBehindOrDefault("Exported", "MafileExporterVM.Result.Exported"),
|
||||
res.Exported.Count);
|
||||
|
||||
if (res.NotFound.Count > 0)
|
||||
{
|
||||
hint += string.Format(LocManager.GetCodeBehindOrDefault("NotFound", "MafileExporterVM.Result.NotFound"),
|
||||
res.NotFound.Count);
|
||||
}
|
||||
|
||||
if (res.Conflict.Count > 0)
|
||||
{
|
||||
hint += string.Format(LocManager.GetCodeBehindOrDefault("Conflict", "MafileExporterVM.Result.Conflict"),
|
||||
res.Conflict.Count);
|
||||
}
|
||||
|
||||
SetHintText(hint, HintBoxSeverity.Info);
|
||||
var errors = res.NotFound.Concat(res.Conflict).ToHashSet();
|
||||
var errorLines = split.Where(errors.Contains);
|
||||
var text = string.Join(Environment.NewLine, errorLines);
|
||||
|
||||
AccountsText = text;
|
||||
}
|
||||
|
||||
private void SetHintText(string? text, HintBoxSeverity severity = HintBoxSeverity.Error)
|
||||
{
|
||||
HintText = text;
|
||||
HintBoxSeverity = severity;
|
||||
}
|
||||
|
||||
private void ResetHintText()
|
||||
{
|
||||
SetHintText(null);
|
||||
}
|
||||
|
||||
#region CanExecute
|
||||
|
||||
private bool ExportCanExecute()
|
||||
{
|
||||
return CurrentTemplateNotNull() && !string.IsNullOrWhiteSpace(AccountsText);
|
||||
}
|
||||
|
||||
|
||||
private bool CurrentTemplateNotNull()
|
||||
{
|
||||
return CurrentTemplate != null;
|
||||
}
|
||||
|
||||
private bool CurrentTemplateNotNullAndNotEditMode()
|
||||
{
|
||||
return CurrentTemplateNotNull() && NotEditMode();
|
||||
}
|
||||
|
||||
private bool NotEditMode()
|
||||
{
|
||||
return !EditMode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public partial class MafileExportTemplateVM : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private bool _includeIdentitySecret;
|
||||
[ObservableProperty] private bool _includeNebulaGroup;
|
||||
[ObservableProperty] private bool _includeNebulaPassword;
|
||||
[ObservableProperty] private bool _includeNebulaProxy;
|
||||
[ObservableProperty] private bool _includeOtherInfo;
|
||||
[ObservableProperty] private bool _includeRCode;
|
||||
[ObservableProperty] private bool _includeSessionData;
|
||||
[ObservableProperty] private bool _includeSharedSecret;
|
||||
[ObservableProperty] private string _name;
|
||||
[ObservableProperty] private string? _path;
|
||||
[ObservableProperty] private bool _useLoginAsMafileName;
|
||||
|
||||
|
||||
public static MafileExportTemplateVM FromModel(MafileExportTemplate x)
|
||||
{
|
||||
return new MafileExportTemplateVM
|
||||
{
|
||||
Name = x.Name,
|
||||
UseLoginAsMafileName = x.UseLoginAsMafileName,
|
||||
IncludeSharedSecret = x.IncludeSharedSecret,
|
||||
IncludeIdentitySecret = x.IncludeIdentitySecret,
|
||||
IncludeRCode = x.IncludeRCode,
|
||||
IncludeSessionData = x.IncludeSessionData,
|
||||
IncludeOtherInfo = x.IncludeOtherInfo,
|
||||
IncludeNebulaProxy = x.IncludeNebulaProxy,
|
||||
IncludeNebulaPassword = x.IncludeNebulaPassword,
|
||||
IncludeNebulaGroup = x.IncludeNebulaGroup,
|
||||
Path = x.Path
|
||||
};
|
||||
}
|
||||
|
||||
public MafileExportTemplate ToModel()
|
||||
{
|
||||
return new MafileExportTemplate
|
||||
{
|
||||
Name = Name,
|
||||
UseLoginAsMafileName = UseLoginAsMafileName,
|
||||
IncludeSharedSecret = IncludeSharedSecret,
|
||||
IncludeIdentitySecret = IncludeIdentitySecret,
|
||||
IncludeRCode = IncludeRCode,
|
||||
IncludeSessionData = IncludeSessionData,
|
||||
IncludeOtherInfo = IncludeOtherInfo,
|
||||
IncludeNebulaProxy = IncludeNebulaProxy,
|
||||
IncludeNebulaPassword = IncludeNebulaPassword,
|
||||
IncludeNebulaGroup = IncludeNebulaGroup,
|
||||
Path = Path
|
||||
};
|
||||
}
|
||||
|
||||
public void ApplyTo(MafileExportTemplate model)
|
||||
{
|
||||
model.Name = Name;
|
||||
model.UseLoginAsMafileName = UseLoginAsMafileName;
|
||||
model.IncludeSharedSecret = IncludeSharedSecret;
|
||||
model.IncludeIdentitySecret = IncludeIdentitySecret;
|
||||
model.IncludeRCode = IncludeRCode;
|
||||
model.IncludeSessionData = IncludeSessionData;
|
||||
model.IncludeOtherInfo = IncludeOtherInfo;
|
||||
model.IncludeNebulaProxy = IncludeNebulaProxy;
|
||||
model.IncludeNebulaPassword = IncludeNebulaPassword;
|
||||
model.IncludeNebulaGroup = IncludeNebulaGroup;
|
||||
model.Path = Path;
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public partial class ProxyManagerVM : ObservableObject
|
||||
|
||||
var split = input
|
||||
.Split(Environment.NewLine)
|
||||
.Where(s => string.IsNullOrWhiteSpace(s) == false)
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.Select(x => x.Trim())
|
||||
.ToArray();
|
||||
|
||||
@@ -72,7 +72,7 @@ public partial class ProxyManagerVM : ObservableObject
|
||||
var i = 0;
|
||||
|
||||
|
||||
foreach (var s in split.Where(s => string.IsNullOrWhiteSpace(s) == false))
|
||||
foreach (var s in split.Where(s => !string.IsNullOrWhiteSpace(s)))
|
||||
{
|
||||
i++;
|
||||
var str = s;
|
||||
|
||||
@@ -144,7 +144,7 @@ public partial class SettingsVM : ObservableObject
|
||||
get => IconColor != null;
|
||||
set
|
||||
{
|
||||
if (value == false)
|
||||
if (!value)
|
||||
IconColor = null;
|
||||
else
|
||||
IconColor = Color.FromRgb(202, 39, 39);
|
||||
|
||||
@@ -180,6 +180,13 @@
|
||||
"zh": "设置密码",
|
||||
"ua": "Призначити паролі",
|
||||
"fr": "Définir les mots de passe"
|
||||
},
|
||||
"OpenExport": {
|
||||
"ru": "Экспорт",
|
||||
"en": "Export",
|
||||
"ua": "Експорт",
|
||||
"fr": "Exporter",
|
||||
"zh": "导出"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -652,10 +659,12 @@
|
||||
},
|
||||
"LegacyMafileModeHint": {
|
||||
"en": "Save mafiles in compatibility format with Steam Desktop Authenticator and other applications",
|
||||
"ru": "Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
|
||||
"ru":
|
||||
"Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
|
||||
"zh": "以与 Steam 桌面认证器及其他应用程序兼容的格式保存 mafiles",
|
||||
"ua": "Зберігати мафайли у старому форматі для сумісності зі Steam Desktop Authenticator та іншими додатками",
|
||||
"fr": "Enregistrer les mafiles dans un format compatible avec Steam Desktop Authenticator et d'autres applications"
|
||||
"fr":
|
||||
"Enregistrer les mafiles dans un format compatible avec Steam Desktop Authenticator et d'autres applications"
|
||||
},
|
||||
"AllowAutoUpdate": {
|
||||
"en": "Allow auto update",
|
||||
@@ -686,10 +695,13 @@
|
||||
"fr": "Ignorer les erreurs dues à la maintenance Steam du mardi soir dans le timer (exp.)"
|
||||
},
|
||||
"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 проводит технические работы и автоподтверждения отключаются без надобности",
|
||||
"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 проводит технические работы и автоподтверждения отключаются без надобности",
|
||||
"zh": "忽略每周三(格林尼治标准时间周三夜间)发生的错误,因为 Steam 进行技术维护时自动确认功能会被无故关闭",
|
||||
"ua": "Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби",
|
||||
"ua":
|
||||
"Ігнорувати помилки, які виникають щосереди (ніч середи за GMT) коли Steam проводить технічні роботи і автопідтвердження вимикаються без потреби",
|
||||
"fr": "Ignorer les erreurs dues à la maintenance Steam du mardi soir (Mercredi à minuit en GMT)"
|
||||
}
|
||||
},
|
||||
@@ -941,11 +953,15 @@
|
||||
"fr": "Mot de passe de chiffrement"
|
||||
},
|
||||
"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(Не обязательно)",
|
||||
"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(Не обязательно)",
|
||||
"zh": "之前您为密码文件设置了加密密码,请输入它,以便应用程序在会话出现问题时可以自动登录。\r\n(非必填)",
|
||||
"ua": "Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)",
|
||||
"fr": "Vous avez déjà défini un mot de passe de chiffrement pour les mots de passe des mafiles, spécifiez-le pour que l'appli puisse se connecter automatiquement en cas de problèmes avec la session.\r\n(Facultatif)"
|
||||
"ua":
|
||||
"Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією.\r\n(Не обов'язково)",
|
||||
"fr":
|
||||
"Vous avez déjà défini un mot de passe de chiffrement pour les mots de passe des mafiles, spécifiez-le pour que l'appli puisse se connecter automatiquement en cas de problèmes avec la session.\r\n(Facultatif)"
|
||||
},
|
||||
"Ok": {
|
||||
"en": "Ok",
|
||||
@@ -1019,6 +1035,209 @@
|
||||
"fr": "Entrer la valeur"
|
||||
}
|
||||
},
|
||||
"ExportDialog": {
|
||||
"ExportTitle": {
|
||||
"ru": "Экспорт",
|
||||
"en": "Export",
|
||||
"ua": "Експорт",
|
||||
"fr": "Exportation",
|
||||
"zh": "导出"
|
||||
},
|
||||
"ExportButton": {
|
||||
"ru": "Экспорт",
|
||||
"en": "Export",
|
||||
"ua": "Експорт",
|
||||
"fr": "Exporter",
|
||||
"zh": "导出"
|
||||
},
|
||||
"TemplateHint": {
|
||||
"ru": "Шаблон",
|
||||
"en": "Template",
|
||||
"ua": "Шаблон",
|
||||
"fr": "Modèle",
|
||||
"zh": "模板"
|
||||
},
|
||||
"SelectPathHint": {
|
||||
"ru": "Выберите путь",
|
||||
"en": "Select path",
|
||||
"ua": "Виберіть шлях",
|
||||
"fr": "Sélectionner le chemin",
|
||||
"zh": "选择路径"
|
||||
},
|
||||
"ExportSettingsHeader": {
|
||||
"ru": "Настройки экспорта",
|
||||
"en": "Export settings",
|
||||
"ua": "Налаштування експорту",
|
||||
"fr": "Paramètres d’exportation",
|
||||
"zh": "导出设置"
|
||||
},
|
||||
"ExportSettingsCommon": {
|
||||
"ru": "Основные:",
|
||||
"en": "General:",
|
||||
"ua": "Основні:",
|
||||
"fr": "Général :",
|
||||
"zh": "常规:"
|
||||
},
|
||||
"ExportSettingsNebula": {
|
||||
"ru": "Nebula:",
|
||||
"en": "Nebula:",
|
||||
"ua": "Nebula:",
|
||||
"fr": "Nebula :",
|
||||
"zh": "Nebula:"
|
||||
},
|
||||
"ExportOptions": {
|
||||
"UseLoginAsMafileName": {
|
||||
"ru": "Использовать логин как имя mafile",
|
||||
"en": "Use login as mafile name",
|
||||
"ua": "Використовувати логін як ім’я mafile",
|
||||
"fr": "Utiliser le login comme nom de mafile",
|
||||
"zh": "使用登录名作为 mafile 名称"
|
||||
},
|
||||
"IncludeSharedSecret": {
|
||||
"ru": "Включать shared secret",
|
||||
"en": "Include shared secret",
|
||||
"ua": "Включати shared secret",
|
||||
"fr": "Inclure le secret partagé",
|
||||
"zh": "包含 shared secret"
|
||||
},
|
||||
"IncludeIdentitySecret": {
|
||||
"ru": "Включать identity secret",
|
||||
"en": "Include identity secret",
|
||||
"ua": "Включати identity secret",
|
||||
"fr": "Inclure le secret d’identité",
|
||||
"zh": "包含 identity secret"
|
||||
},
|
||||
"IncludeRCode": {
|
||||
"ru": "Включать RCode",
|
||||
"en": "Include RCode",
|
||||
"ua": "Включати RCode",
|
||||
"fr": "Inclure RCode",
|
||||
"zh": "包含 RCode"
|
||||
},
|
||||
"IncludeSessionData": {
|
||||
"ru": "Включать данные сессии",
|
||||
"en": "Include session data",
|
||||
"ua": "Включати дані сесії",
|
||||
"fr": "Inclure les données de session",
|
||||
"zh": "包含会话数据"
|
||||
},
|
||||
"IncludeOtherInfo": {
|
||||
"ru": "Включать прочую информацию",
|
||||
"en": "Include other info",
|
||||
"ua": "Включати іншу інформацію",
|
||||
"fr": "Inclure d’autres informations",
|
||||
"zh": "包含其他信息"
|
||||
},
|
||||
"NebulaProxy": {
|
||||
"ru": "Прокси",
|
||||
"en": "Proxy",
|
||||
"ua": "Проксі",
|
||||
"fr": "Proxy",
|
||||
"zh": "代理"
|
||||
},
|
||||
"NebulaPassword": {
|
||||
"ru": "Пароль",
|
||||
"en": "Password",
|
||||
"ua": "Пароль",
|
||||
"fr": "Mot de passe",
|
||||
"zh": "密码"
|
||||
},
|
||||
"NebulaGroup": {
|
||||
"ru": "Группа",
|
||||
"en": "Group",
|
||||
"ua": "Група",
|
||||
"fr": "Groupe",
|
||||
"zh": "组"
|
||||
}
|
||||
},
|
||||
"Tooltips": {
|
||||
"UseLoginAsMafileName": {
|
||||
"ru": "Использовать логин аккаунта в качестве имени mafile.",
|
||||
"en": "Use the account login as the mafile name.",
|
||||
"ua": "Використовувати логін акаунта як ім’я mafile.",
|
||||
"fr": "Utiliser le login du compte comme nom du mafile.",
|
||||
"zh": "使用账户登录名作为 mafile 的名称。"
|
||||
},
|
||||
"IncludeSharedSecret": {
|
||||
"ru": "Позволяет генерировать коды подтверждения и входить в аккаунт при наличии пароля или активной сессии.",
|
||||
"en":
|
||||
"Allows generating confirmation codes and logging into the account if a password or active session is available.",
|
||||
"ua": "Дозволяє генерувати коди підтвердження та входити в акаунт за наявності пароля або активної сесії.",
|
||||
"fr":
|
||||
"Permet de générer des codes de confirmation et de se connecter au compte si un mot de passe ou une session active est disponible.",
|
||||
"zh": "允许生成确认代码,并在有密码或活动会话的情况下登录账户。"
|
||||
},
|
||||
"IncludeIdentitySecret": {
|
||||
"ru":
|
||||
"Включает identity_secret и device_id. Необходимы для отправки подтверждений и дают доступ к трейдам и торговой площадке.",
|
||||
"en":
|
||||
"Includes identity_secret and device_id. Required for confirmations and provides access to trades and the market.",
|
||||
"ua":
|
||||
"Включає identity_secret і device_id. Потрібні для надсилання підтверджень і надають доступ до трейдів та торгового майданчика.",
|
||||
"fr":
|
||||
"Inclut identity_secret et device_id. Nécessaires pour les confirmations et donnent accès aux échanges et au marché.",
|
||||
"zh": "包含 identity_secret 和 device_id。用于发送确认,并可访问交易和市场。"
|
||||
},
|
||||
"IncludeRCode": {
|
||||
"ru": "Код восстановления. Позволяет отвязать Steam Guard от аккаунта в случае потери доступа.",
|
||||
"en": "Recovery code. Allows removing Steam Guard from the account if access is lost.",
|
||||
"ua": "Код відновлення. Дозволяє відв’язати Steam Guard від акаунта у разі втрати доступу.",
|
||||
"fr": "Code de récupération. Permet de supprimer Steam Guard du compte en cas de perte d’accès.",
|
||||
"zh": "恢复代码。在失去访问权限时可将 Steam Guard 与账户解绑。"
|
||||
},
|
||||
"IncludeSessionData": {
|
||||
"ru":
|
||||
"Включает данные текущей сессии. Если сессия активна и не истекла, позволяет получить доступ к аккаунту без ввода пароля с помощью mafile.",
|
||||
"en":
|
||||
"Includes current session data. If the session is active and valid, allows accessing the account without entering a password using the mafile.",
|
||||
"ua":
|
||||
"Включає дані поточної сесії. Якщо сесія активна й не прострочена, дозволяє отримати доступ до акаунта без введення пароля за допомогою mafile.",
|
||||
"fr":
|
||||
"Inclut les données de la session actuelle. Si la session est active et valide, permet d’accéder au compte sans saisir de mot de passe via le mafile.",
|
||||
"zh": "包含当前会话数据。如果会话处于活动状态且未过期,可通过 mafile 在无需输入密码的情况下访问账户。"
|
||||
},
|
||||
"IncludeOtherInfo": {
|
||||
"ru":
|
||||
"Включает дополнительные необязательные поля, которые в данный момент не используются Steam, но могут применяться в будущем и содержать чувствительную информацию.",
|
||||
"en":
|
||||
"Includes additional optional fields not currently used by Steam, but potentially usable in the future and may contain sensitive information.",
|
||||
"ua":
|
||||
"Включає додаткові необов’язкові поля, які наразі не використовуються Steam, але можуть бути задіяні в майбутньому та містити чутливу інформацію.",
|
||||
"fr":
|
||||
"Inclut des champs supplémentaires optionnels actuellement non utilisés par Steam, mais susceptibles d’être exploités à l’avenir et de contenir des informations sensibles.",
|
||||
"zh": "包含目前未被 Steam 使用的可选附加字段,这些字段将来可能会被使用,并可能包含敏感信息。"
|
||||
},
|
||||
"NebulaProxy": {
|
||||
"ru": "Включить Nebula-совместимые данные прокси.",
|
||||
"en": "Include Nebula-compatible proxy data.",
|
||||
"ua": "Увімкнути Nebula-сумісні дані проксі.",
|
||||
"fr": "Inclure les données de proxy compatibles avec Nebula.",
|
||||
"zh": "包含与 Nebula 兼容的代理数据。"
|
||||
},
|
||||
"NebulaPassword": {
|
||||
"ru": "Включить Nebula-совместимые данные пароля.",
|
||||
"en": "Include Nebula-compatible password data.",
|
||||
"ua": "Увімкнути Nebula-сумісні дані пароля.",
|
||||
"fr": "Inclure les données de mot de passe compatibles avec Nebula.",
|
||||
"zh": "包含与 Nebula 兼容的密码数据。"
|
||||
},
|
||||
"NebulaGroup": {
|
||||
"ru": "Включить Nebula-совместимые данные группы.",
|
||||
"en": "Include Nebula-compatible group data.",
|
||||
"ua": "Увімкнути Nebula-сумісні дані групи.",
|
||||
"fr": "Inclure les données de groupe compatibles avec Nebula.",
|
||||
"zh": "包含与 Nebula 兼容的组数据。"
|
||||
}
|
||||
},
|
||||
"ExportAccountsPlaceholder": {
|
||||
"ru": "Введите логины аккаунтов или SteamID (по одному на строку) для экспорта в выбранную директорию.",
|
||||
"en": "Enter account logins or SteamIDs (one per line) to export to the selected directory.",
|
||||
"ua": "Введіть логіни акаунтів або SteamID (по одному на рядок) для експорту у вибрану директорію.",
|
||||
"fr":
|
||||
"Entrez les identifiants de compte ou les SteamID (un par ligne) pour exporter vers le répertoire sélectionné.",
|
||||
"zh": "输入要导出到所选目录的账户登录名或 SteamID(每行一个)。"
|
||||
}
|
||||
},
|
||||
"SetAccountPasswordsView": {
|
||||
"Title": {
|
||||
"en": "Set passwords",
|
||||
@@ -1087,11 +1306,15 @@
|
||||
"fr": "RCode manquant"
|
||||
},
|
||||
"ConfirmRemovingAuthenticator": {
|
||||
"ru": "Вы точно уверены, что хотите удалить аутентификатор?\r\nПосле этого вы не сможете обмениваться, использовать торговую площадку 15 дней. Мафайл будет удалён навсегда",
|
||||
"en": "Are you sure you want to remove the authenticator?\r\nAfter that, you will not be able to trade or use the marketplace for 15 days. Mafile will be deleted permanently",
|
||||
"ru":
|
||||
"Вы точно уверены, что хотите удалить аутентификатор?\r\nПосле этого вы не сможете обмениваться, использовать торговую площадку 15 дней. Мафайл будет удалён навсегда",
|
||||
"en":
|
||||
"Are you sure you want to remove the authenticator?\r\nAfter that, you will not be able to trade or use the marketplace for 15 days. Mafile will be deleted permanently",
|
||||
"zh": "您确定要移除身份验证器吗?\r\n之后,您将在15天内无法进行交易或使用市场。Mafile 将被永久删除。",
|
||||
"ua": "Ви точно впевнені, що хочете видалити автентифікатор?\r\nПісля цього ви не зможете обмінюватися, використовувати торгову площадку 15 днів. Мафайл буде видалено назавжди",
|
||||
"fr": "Êtes-vous sûr de vouloir supprimer l'authenticateur?\r\nAprès cela, vous ne pourrez pas échanger ou utiliser le marché pendant 15 jours. Le mafile sera supprimé définitivement"
|
||||
"ua":
|
||||
"Ви точно впевнені, що хочете видалити автентифікатор?\r\nПісля цього ви не зможете обмінюватися, використовувати торгову площадку 15 днів. Мафайл буде видалено назавжди",
|
||||
"fr":
|
||||
"Êtes-vous sûr de vouloir supprimer l'authenticateur?\r\nAprès cela, vous ne pourrez pas échanger ou utiliser le marché pendant 15 jours. Le mafile sera supprimé définitivement"
|
||||
},
|
||||
"AuthenticatorRemoved": {
|
||||
"ru": "Аутентификатор удален",
|
||||
@@ -1185,11 +1408,15 @@
|
||||
"fr": "erreurs:"
|
||||
},
|
||||
"RemoveMafileConfirmation": {
|
||||
"ru": "Удалить mafile?\r\nМафайл будет перемещен в папку 'mafiles_removed'\r\nЭто действие НЕ удаляет Steam Guard с аккаунта",
|
||||
"en": "Remove mafile?\r\nMafile will be moved to 'mafiles_removed' directory\r\nThis action will NOT remove the Steam Guard from the account",
|
||||
"ru":
|
||||
"Удалить mafile?\r\nМафайл будет перемещен в папку 'mafiles_removed'\r\nЭто действие НЕ удаляет Steam Guard с аккаунта",
|
||||
"en":
|
||||
"Remove mafile?\r\nMafile will be moved to 'mafiles_removed' directory\r\nThis action will NOT remove the Steam Guard from the account",
|
||||
"zh": "是否删除mafile?\r\nMafile将被移动到“mafiles_removed”目录\r\n此操作不会从账户中移除令牌",
|
||||
"ua": "Видалити mafile?\r\nМафайл буде переміщено у каталог 'mafiles_removed'\r\nЦя дія НЕ видаляє автентифікатор з облікового запису",
|
||||
"fr": "Supprimer le mafile?\r\nLe mafile sera déplacé dans le dossier 'mafiles_removed'\r\nCette action NE SUPPRIME PAS le Steam Guard du compte"
|
||||
"ua":
|
||||
"Видалити mafile?\r\nМафайл буде переміщено у каталог 'mafiles_removed'\r\nЦя дія НЕ видаляє автентифікатор з облікового запису",
|
||||
"fr":
|
||||
"Supprimer le mafile?\r\nLe mafile sera déplacé dans le dossier 'mafiles_removed'\r\nCette action NE SUPPRIME PAS le Steam Guard du compte"
|
||||
},
|
||||
"CantRemoveAlreadyExist": {
|
||||
"ru": "Не удалось переместить mafile, возможно в папке уже существует мафайл с таким именем.",
|
||||
@@ -1224,7 +1451,8 @@
|
||||
"en": "Can't retrieve SteamID from mafile to save changes. Need to relogin. Canceled",
|
||||
"zh": "无法从配置文件获取 SteamID 并保存更改。需要重新登录。已取消",
|
||||
"ua": "Не вдалося отримати SteamID з мафайла та зберегти зміни. Потрібно зробити релогін. Скасовано",
|
||||
"fr": "Impossible de récupérer le SteamID du mafile pour sauvegarder les modifications. Reconnexion nécessaire. Annulé"
|
||||
"fr":
|
||||
"Impossible de récupérer le SteamID du mafile pour sauvegarder les modifications. Reconnexion nécessaire. Annulé"
|
||||
},
|
||||
"LoginCopied": {
|
||||
"en": "Login copied",
|
||||
@@ -1263,9 +1491,11 @@
|
||||
},
|
||||
"CantDecryptPassword": {
|
||||
"en": "Can't decrypt password. Maybe password from this mafile was encrypted with another encryption password",
|
||||
"ru": "Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования",
|
||||
"ru":
|
||||
"Не удалось расшифровать пароль. Возможно пароль из этого мафайла был зашифрован другим паролем шифрования",
|
||||
"zh": "无法解密密码。可能此文件中的密码是用其他加密密码加密的。",
|
||||
"ua": "Не вдалося розшифрувати пароль. Можливо пароль з цього мафайла був зашифрований іншим паролем шифрування",
|
||||
"ua":
|
||||
"Не вдалося розшифрувати пароль. Можливо пароль з цього мафайла був зашифрований іншим паролем шифрування",
|
||||
"fr": "Impossible de déchiffrer le mot de passe. Le mot de passe de chiffrement est peut-être différent"
|
||||
},
|
||||
"CreateGroupTitle": {
|
||||
@@ -1620,10 +1850,13 @@
|
||||
},
|
||||
"InvalidStateWithStatus2": {
|
||||
"ru": "Неверное состояние (InvalidState) со статусом 2. Откройте ссылку на руководство сверху этого окна.",
|
||||
"en": "Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
|
||||
"en":
|
||||
"Invalid state (InvalidState) with status 2. Open link to the troubleshooting guide at the top of this window.",
|
||||
"zh": "状态无效(InvalidState),状态码为 2. 请打开本窗口顶部的故障排除指南链接。",
|
||||
"ua": "Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна.",
|
||||
"fr": "État invalide (InvalidState) avec le statut 2. Ouvrez le lien vers le guide de dépannage en haut de cette fenêtre."
|
||||
"ua":
|
||||
"Невірний стан (InvalidState) зі статусом 2. Відкрийте посилання на посібник з усунення несправностей у верхній частині цього вікна.",
|
||||
"fr":
|
||||
"État invalide (InvalidState) avec le statut 2. Ouvrez le lien vers le guide de dépannage en haut de cette fenêtre."
|
||||
},
|
||||
"GeneralFailure": {
|
||||
"ru": "General Failure",
|
||||
@@ -1689,7 +1922,8 @@
|
||||
"ru": "Ошибка: Был запрошен вход с помощью Steam Guard. На аккаунте уже активирован мобильный аутентификатор",
|
||||
"en": "Error: Login with Steam Guard was requested. Mobile authenticator is already activated on the account",
|
||||
"zh": "错误:请求使用令牌登录。该账户已激活移动验证器",
|
||||
"ua": "Помилка: Був запрошений вхід за допомогою Steam Guard. На акаунті вже активовано мобільний автентифікатор",
|
||||
"ua":
|
||||
"Помилка: Був запрошений вхід за допомогою Steam Guard. На акаунті вже активовано мобільний автентифікатор",
|
||||
"fr": "Erreur: Connexion avec Steam Guard demandée. Authenticateur mobile est déjà activé sur le compte"
|
||||
},
|
||||
"Unknown": {
|
||||
@@ -1828,11 +2062,15 @@
|
||||
"fr": "Un email a été envoyé. Ouvrez le lien de l'email, puis cliquez sur 'Continuer'"
|
||||
},
|
||||
"ClickOnEmailLinkRetry": {
|
||||
"ru": "Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
|
||||
"en": "We couldn’t verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
|
||||
"ru":
|
||||
"Не удалось подтвердить переход по ссылке. Убедитесь, что вы открыли ссылку из письма, затем нажмите — 'Продолжить'",
|
||||
"en":
|
||||
"We couldn’t verify that you clicked the link. Make sure you opened the link from the email, then click 'Proceed'",
|
||||
"zh": "无法确认通过链接的跳转。请确保您是从邮件中打开的链接,然后点击——“继续”",
|
||||
"ua": "Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'",
|
||||
"fr": "Nous n'avons pas pu vérifier que vous avez cliqué sur le lien. Assurez-vous d'avoir ouvert le lien de l'email, puis cliquez sur 'Continuer'"
|
||||
"ua":
|
||||
"Не вдалося підтвердити перехід за посиланням. Переконайтесь, що ви відкрили посилання з листа, а потім натисніть — 'Продовжити'",
|
||||
"fr":
|
||||
"Nous n'avons pas pu vérifier que vous avez cliqué sur le lien. Assurez-vous d'avoir ouvert le lien de l'email, puis cliquez sur 'Continuer'"
|
||||
},
|
||||
"PhoneHint": {
|
||||
"ru": "На телефон {0} была отправлена СМС",
|
||||
@@ -1842,11 +2080,15 @@
|
||||
"fr": "SMS envoyé au téléphone {0}"
|
||||
},
|
||||
"EnterPhoneNumber": {
|
||||
"ru": "Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера",
|
||||
"en": "Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number",
|
||||
"ru":
|
||||
"Введите номер в международном формате, только цифры (без '+', пробелов и скобок), или оставьте поле пустым, чтобы привязать без номера",
|
||||
"en":
|
||||
"Enter number in international format, digits only (no '+', spaces or brackets), or leave the field empty to link without a number",
|
||||
"zh": "请输入国际格式的号码,仅数字(不含空格和括号),或留空以绑定无号码",
|
||||
"ua": "Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера",
|
||||
"fr": "Entrez un numéro au format international, uniquement des chiffres (sans '+', espaces ou parenthèses), ou laissez le champ vide pour lier sans numéro"
|
||||
"ua":
|
||||
"Введіть номер у міжнародному форматі, лише цифри (без '+', пробілів і дужок), або залиште поле порожнім, щоб прив’язати без номера",
|
||||
"fr":
|
||||
"Entrez un numéro au format international, uniquement des chiffres (sans '+', espaces ou parenthèses), ou laissez le champ vide pour lier sans numéro"
|
||||
}
|
||||
},
|
||||
"MafileMoverVM": {
|
||||
@@ -1879,10 +2121,12 @@
|
||||
"fr": "RCode: {0}\r\nNom du fichier: {1}.mafile"
|
||||
},
|
||||
"GuardIsNotActive": {
|
||||
"ru": "Steam Guard установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
|
||||
"ru":
|
||||
"Steam Guard установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
|
||||
"en": "Steam Guard is set on this account. To link mafile to the account, use the 'Link' window",
|
||||
"zh": "此账户已启用令牌。要将 mafile 绑定到此账户,请使用“绑定”窗口。",
|
||||
"ua": "Steam Guard встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'",
|
||||
"ua":
|
||||
"Steam Guard встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'",
|
||||
"fr": "Steam Guard est configuré sur ce compte. Pour lier mafile au compte, utilisez la fenêtre 'Lien'"
|
||||
},
|
||||
"SeemsNoPhoneNumber": {
|
||||
@@ -1930,11 +2174,15 @@
|
||||
"fr": "Auto "
|
||||
},
|
||||
"TimerPreventedOverlap": {
|
||||
"ru": "Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
|
||||
"en": "Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
|
||||
"ru":
|
||||
"Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
|
||||
"en":
|
||||
"Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
|
||||
"zh": "自动: 确认计时器在前一个周期尚未完成时尝试启动新周期。我们建议增加延迟。",
|
||||
"ua": "Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку",
|
||||
"fr": "Auto: le timer de confirmation a tenté de démarrer un nouveau cycle alors que le précédent n'était pas encore terminé. Nous recommandons d'augmenter le délai"
|
||||
"ua":
|
||||
"Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку",
|
||||
"fr":
|
||||
"Auto: le timer de confirmation a tenté de démarrer un nouveau cycle alors que le précédent n'était pas encore terminé. Nous recommandons d'augmenter le délai"
|
||||
},
|
||||
"FailedToLoadStorage": {
|
||||
"en": "Failed to load auto-confirm timers. File seems to be corrupted",
|
||||
@@ -1961,11 +2209,15 @@
|
||||
"fr": "Tous les mafiles ont été renommés avec succès ({0}).\r\nUn backup a été créé: {1}"
|
||||
},
|
||||
"PartialSuccess": {
|
||||
"en": "Total mafiles: {0}\r\nRenamed: {1}\r\nConflicts: {2}\r\nErrors: {3}\r\n\r\nBackup of mafiles saved in file: {4}",
|
||||
"ru": "Всего мафайлов: {0}\r\nПереименовано: {1}\r\nКонфликты: {2}\r\nОшибок: {3}\r\n\r\nРезервная копия мафайлов сохранена в файле: {4}",
|
||||
"en":
|
||||
"Total mafiles: {0}\r\nRenamed: {1}\r\nConflicts: {2}\r\nErrors: {3}\r\n\r\nBackup of mafiles saved in file: {4}",
|
||||
"ru":
|
||||
"Всего мафайлов: {0}\r\nПереименовано: {1}\r\nКонфликты: {2}\r\nОшибок: {3}\r\n\r\nРезервная копия мафайлов сохранена в файле: {4}",
|
||||
"zh": "总文件数: {0}\r\n重命名: {1}\r\n冲突: {2}\r\n错误: {3}\r\n\r\n文件备份已保存至: {4}",
|
||||
"ua": "Всього мафайлів: {0}\r\nПерейменовано: {1}\r\nКонфлікти: {2}\r\nПомилок: {3}\r\n\r\nРезервна копія мафайлів збережена у файлі: {4}",
|
||||
"fr": "Total des mafiles: {0}\r\nRenommés: {1}\r\nConflits: {2}\r\nErreurs: {3}\r\n\r\nBackup des mafiles enregistré dans le fichier: {4}"
|
||||
"ua":
|
||||
"Всього мафайлів: {0}\r\nПерейменовано: {1}\r\nКонфлікти: {2}\r\nПомилок: {3}\r\n\r\nРезервна копія мафайлів збережена у файлі: {4}",
|
||||
"fr":
|
||||
"Total des mafiles: {0}\r\nRenommés: {1}\r\nConflits: {2}\r\nErreurs: {3}\r\n\r\nBackup des mafiles enregistré dans le fichier: {4}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2002,13 +2254,70 @@
|
||||
"ua": "Успішно: {0}, Не знайдено: {1}, Помилки: {2}",
|
||||
"fr": "Succès: {0}, Non trouvé: {1}, Erreurs: {2}"
|
||||
}
|
||||
},
|
||||
"MafileExporter": {
|
||||
"InvalidPath": {
|
||||
"ru": "Указан некорректный путь для экспорта.",
|
||||
"en": "The specified export path is invalid.",
|
||||
"ua": "Вказано некоректний шлях для експорту.",
|
||||
"fr": "Le chemin d’exportation spécifié est invalide.",
|
||||
"zh": "指定的导出路径无效。"
|
||||
},
|
||||
"NebulaUtilizedDirectory": {
|
||||
"ru":
|
||||
"Невозможно выполнить экспорт в папку, которая используется приложением. Рекомендуется выбрать отдельную директорию.",
|
||||
"en":
|
||||
"Cannot export to a directory that is currently used by the application. Please choose a separate folder.",
|
||||
"ua":
|
||||
"Неможливо виконати експорт у папку, яка використовується застосунком. Рекомендується вибрати окрему директорію.",
|
||||
"fr": "Impossible d’exporter vers un dossier utilisé par l’application. Veuillez choisir un dossier distinct.",
|
||||
"zh": "无法导出到应用程序正在使用的文件夹。请选择一个单独的目录。"
|
||||
},
|
||||
"AccessDenied": {
|
||||
"ru": "Недостаточно прав для записи в указанную папку. Проверьте права доступа или выберите другой путь.",
|
||||
"en":
|
||||
"Insufficient permissions to write to the specified directory. Please check access rights or choose another path.",
|
||||
"ua": "Недостатньо прав для запису в указану папку. Перевірте права доступу або виберіть інший шлях.",
|
||||
"fr":
|
||||
"Autorisation insuffisante pour écrire dans le dossier spécifié. Vérifiez les droits d’accès ou choisissez un autre chemin.",
|
||||
"zh": "没有权限写入指定的文件夹。请检查访问权限或选择其他路径。"
|
||||
}
|
||||
},
|
||||
"MafileExporterVM": {
|
||||
"Result": {
|
||||
"Exported": {
|
||||
"ru": "Экспортировано: {0}",
|
||||
"en": "Exported: {0}",
|
||||
"ua": "Експортовано: {0}",
|
||||
"fr": "Exporté : {0}",
|
||||
"zh": "已导出:{0}"
|
||||
},
|
||||
"NotFound": {
|
||||
"ru": ". Не найдено: {0}",
|
||||
"en": ". Not found: {0}",
|
||||
"ua": ". Не знайдено: {0}",
|
||||
"fr": ". Introuvable : {0}",
|
||||
"zh": "。未找到:{0}"
|
||||
},
|
||||
"Conflict": {
|
||||
"ru": ". Конфликты: {0}",
|
||||
"en": ". Conflicts: {0}",
|
||||
"ua": ". Конфлікти: {0}",
|
||||
"fr": ". Conflits : {0}",
|
||||
"zh": "。冲突:{0}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"zh": "无法与 Steam 对齐时间。可能没有互联网连接或 Steam 不可用。详细错误已保存到 log.log 中",
|
||||
"ua": "Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log",
|
||||
"fr": "Impossible de synchroniser l'heure avec Steam. Peut-être qu'il n'y a pas de connexion internet ou Steam est indisponible. Détails de l'erreur enregistrés dans log.log"
|
||||
"ua":
|
||||
"Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log",
|
||||
"fr":
|
||||
"Impossible de synchroniser l'heure avec Steam. Peut-être qu'il n'y a pas de connexion internet ou Steam est indisponible. Détails de l'erreur enregistrés dans log.log"
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public static class SteamAuthenticatorLinkerApi
|
||||
var content = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
|
||||
var j = JObject.Parse(content);
|
||||
|
||||
if (j["success"]!.Value<bool>() == false)
|
||||
if (!j["success"]!.Value<bool>())
|
||||
return CheckPhoneResult.GeneralFailure;
|
||||
|
||||
if (j["is_voip"]!.Value<bool>())
|
||||
@@ -187,7 +187,7 @@ public static class SteamAuthenticatorLinkerApi
|
||||
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri,
|
||||
new EmptyMessage());
|
||||
|
||||
if (resp.IsWaiting == false) return true;
|
||||
if (!resp.IsWaiting) return true;
|
||||
|
||||
await Task.Delay(resp.SecondsToWait * 1000);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public static class AdmissionHelper
|
||||
{
|
||||
var uri = SteamDomains.GetDomainUri(domain);
|
||||
foreach (var cookie in container.GetCookies(uri)
|
||||
.Where(c => c.Expired == false && c.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)))
|
||||
.Where(c => !c.Expired && c.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)))
|
||||
{
|
||||
cookie.Expired = true;
|
||||
}
|
||||
@@ -131,7 +131,7 @@ public static class AdmissionHelper
|
||||
}
|
||||
|
||||
var mobileToken = mobileSession.GetMobileToken();
|
||||
if (domainCookieSet == false && mobileToken is {IsExpired: false})
|
||||
if (!domainCookieSet && mobileToken is {IsExpired: false})
|
||||
{
|
||||
var domain = SteamDomains.GetDomainUri(SteamDomain.Community);
|
||||
container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, mobileToken.Value.SignedToken)
|
||||
@@ -193,7 +193,7 @@ public static class AdmissionHelper
|
||||
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
if (cookie.Domain.Contains("steamcommunity.com") == false || cookie.Expired ||
|
||||
if (!cookie.Domain.Contains("steamcommunity.com") || cookie.Expired ||
|
||||
cookie.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)) continue;
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ public static class AdmissionHelper
|
||||
var cookies = container.GetAllCookies();
|
||||
return cookies
|
||||
.FirstOrDefault(c => c.Name.Equals(SESSION_ID_COOKIE_NAME, StringComparison.InvariantCultureIgnoreCase)
|
||||
&& c.Expired == false
|
||||
&& !c.Expired
|
||||
&& c.Domain.Contains(domain, StringComparison.InvariantCultureIgnoreCase))?
|
||||
.Value;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public static class SteamTokenHelper
|
||||
{
|
||||
result = default;
|
||||
var match = SteamTokenRegex.Match(input);
|
||||
if (match.Success == false)
|
||||
if (!match.Success)
|
||||
{
|
||||
if (trying) return false;
|
||||
throw new ArgumentException("Provided Token is not valid SteamLoginSecure or SteamAccess token");
|
||||
@@ -64,7 +64,7 @@ public static class SteamTokenHelper
|
||||
}
|
||||
|
||||
var steamIdStr = jwt.Subject;
|
||||
if (steamIdStr == null || long.TryParse(steamIdStr, out var steamId) == false || steamId < SteamId64.SEED)
|
||||
if (steamIdStr == null || !long.TryParse(steamIdStr, out var steamId) || steamId < SteamId64.SEED)
|
||||
{
|
||||
if (trying) return false;
|
||||
throw new ArgumentException(
|
||||
@@ -101,7 +101,7 @@ public static class SteamTokenHelper
|
||||
if (jwt.Audiences.ToList().Count > 0)
|
||||
{
|
||||
var aud = audiences[0];
|
||||
if (AudDomains.TryGetValue(aud, out domain) == false && aud != "web")
|
||||
if (!AudDomains.TryGetValue(aud, out domain) && aud != "web")
|
||||
{
|
||||
if (trying)
|
||||
{
|
||||
@@ -180,13 +180,13 @@ public static class SteamTokenHelper
|
||||
|
||||
internal static string CombineLoginValueIfNeeded(long steamId, string loginValue)
|
||||
{
|
||||
return CheckIfProperLoginValue(loginValue) == false ? CombineJwtWithSteamId(steamId, loginValue) : loginValue;
|
||||
return !CheckIfProperLoginValue(loginValue) ? CombineJwtWithSteamId(steamId, loginValue) : loginValue;
|
||||
}
|
||||
|
||||
public static string ExtractJwtToken(string steamLoginSecure)
|
||||
{
|
||||
var match = SteamTokenRegex.Match(steamLoginSecure);
|
||||
if (match.Success == false) throw new ArgumentException("Can't extract JWT Access Token from steamLoginSecure");
|
||||
if (!match.Success) throw new ArgumentException("Can't extract JWT Access Token from steamLoginSecure");
|
||||
return match.Groups["jwt"].Value;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public static class ProtoHttpClientExtension
|
||||
this HttpClient client, Uri uri, HttpMethod method, string? protoMsg, CancellationToken cancellationToken)
|
||||
where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var resp = await SendProto(client, uri, method, protoMsg, cancellationToken);
|
||||
var resp = await client.SendProto(uri, method, protoMsg, cancellationToken);
|
||||
return await ProtoResponse<TProtoResponse>.FromHttpResponseAsync(resp, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public static class ProtoHttpClientExtension
|
||||
string? protoMsg,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var resp = await SendProto(client, uri, method, protoMsg, cancellationToken);
|
||||
var resp = await client.SendProto(uri, method, protoMsg, cancellationToken);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
return ProtoHelpers.GetEResult(resp);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
return SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Post, str, cancellationToken);
|
||||
return client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Post, str, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<ProtoResponse<TProtoResponse>> PostProtoMsg<TProtoResponse>(this HttpClient client, string uri,
|
||||
@@ -105,7 +105,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
return SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Post, str, cancellationToken);
|
||||
return client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Post, str, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -117,7 +117,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
var res = await SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Post, str, cancellationToken);
|
||||
var res = await client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Post, str, cancellationToken);
|
||||
return res.GetResponseEnsureSuccess();
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
var res = await SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Post, str,
|
||||
var res = await client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Post, str,
|
||||
cancellationToken);
|
||||
return res.GetResponseEnsureSuccess();
|
||||
}
|
||||
@@ -179,7 +179,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
return SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Get, str, cancellationToken);
|
||||
return client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Get, str, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<ProtoResponse<TProtoResponse>> GetProtoMsg<TProtoResponse>(this HttpClient client, string uri,
|
||||
@@ -187,7 +187,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
return SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Get, str, cancellationToken);
|
||||
return client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Get, str, cancellationToken);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -199,7 +199,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
var res = await SendProtoWithResponse<TProtoResponse>(client, uri, HttpMethod.Get, str, cancellationToken);
|
||||
var res = await client.SendProtoWithResponse<TProtoResponse>(uri, HttpMethod.Get, str, cancellationToken);
|
||||
res.Result.EnsureSuccessEResult();
|
||||
|
||||
return res.ResponseMsg ?? throw new NullReferenceException("ProtoMsg in response was null");
|
||||
@@ -210,7 +210,7 @@ public static class ProtoHttpClientExtension
|
||||
CancellationToken cancellationToken = default) where TProtoResponse : class, IProtoMsg
|
||||
{
|
||||
var str = ProtoHelpers.ProtoToString(request);
|
||||
var res = await SendProtoWithResponse<TProtoResponse>(client, new Uri(uri), HttpMethod.Get, str,
|
||||
var res = await client.SendProtoWithResponse<TProtoResponse>(new Uri(uri), HttpMethod.Get, str,
|
||||
cancellationToken);
|
||||
res.Result.EnsureSuccessEResult();
|
||||
return res.ResponseMsg ?? throw new NullReferenceException("ProtoMsg in response was null");
|
||||
|
||||
@@ -74,18 +74,18 @@ public class SteamAuthenticatorLinker
|
||||
|
||||
long? phoneNumber = null;
|
||||
|
||||
if (hasPhone == false && Options.PhoneNumberProvider != null)
|
||||
if (!hasPhone && Options.PhoneNumberProvider != null)
|
||||
{
|
||||
phoneNumber = await Options.PhoneNumberProvider.GetPhoneNumber(Consumer);
|
||||
}
|
||||
|
||||
if (hasPhone == false && phoneNumber != null)
|
||||
if (!hasPhone && phoneNumber != null)
|
||||
{
|
||||
Logger?.LogInformation("Attaching phone number {phoneNumber}", phoneNumber);
|
||||
//if (await this.IsValidPhoneNumber(phoneNumber.Value) == false)
|
||||
// throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidPhoneNumber);
|
||||
|
||||
if (await this.AttachPhone(phoneNumber.Value) == false)
|
||||
if (!await this.AttachPhone(phoneNumber.Value))
|
||||
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.CantAttachPhone);
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ public class SteamAuthenticatorLinker
|
||||
Logger?.LogInformation("Finalizing link");
|
||||
var result = await this.FinalizeLink(code, resp.SharedSecret, isPhone);
|
||||
|
||||
if (result.Success == false)
|
||||
if (!result.Success)
|
||||
{
|
||||
var error = result.Error switch
|
||||
{
|
||||
|
||||
@@ -5,24 +5,21 @@ namespace SteamLib.Utility.MafileSerialization;
|
||||
internal class LegacyMafile
|
||||
{
|
||||
[JsonProperty("shared_secret")]
|
||||
[JsonRequired]
|
||||
public string SharedSecret { get; set; } = default!;
|
||||
public string SharedSecret { get; set; } = null!;
|
||||
|
||||
[JsonProperty("identity_secret")]
|
||||
[JsonRequired]
|
||||
public string IdentitySecret { get; set; } = default!;
|
||||
public string IdentitySecret { get; set; } = null!;
|
||||
|
||||
[JsonProperty("device_id")]
|
||||
[JsonRequired]
|
||||
public string DeviceId { get; set; } = default!;
|
||||
public string DeviceId { get; set; } = null!;
|
||||
|
||||
[JsonProperty("revocation_code")] public string RevocationCode { get; set; } = default!;
|
||||
[JsonProperty("account_name")] public string AccountName { get; set; } = default!;
|
||||
[JsonProperty("revocation_code")] public string RevocationCode { get; set; } = null!;
|
||||
[JsonProperty("account_name")] public string AccountName { get; set; } = null!;
|
||||
[JsonProperty("Session")] public object? SessionData { get; set; }
|
||||
[JsonProperty("server_time")] public long ServerTime { get; set; } //Unused
|
||||
[JsonProperty("steamid")] public long SteamId { get; set; }
|
||||
[JsonProperty("serial_number")] public string SerialNumber { get; set; } = default!; //Unused
|
||||
[JsonProperty("uri")] public string Uri { get; set; } = default!; //Unused
|
||||
[JsonProperty("token_gid")] public string TokenGid { get; set; } = default!; //Unused
|
||||
[JsonProperty("secret_1")] public string Secret1 { get; set; } = default!; //Unused
|
||||
[JsonProperty("serial_number")] public string SerialNumber { get; set; } = null!; //Unused
|
||||
[JsonProperty("uri")] public string Uri { get; set; } = null!; //Unused
|
||||
[JsonProperty("token_gid")] public string TokenGid { get; set; } = null!; //Unused
|
||||
[JsonProperty("secret_1")] public string Secret1 { get; set; } = null!; //Unused
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public partial class MafileSerializer //Validate
|
||||
public static void IsValidBase64(string name, string base64)
|
||||
{
|
||||
var buffer = new Span<byte>(new byte[base64.Length]);
|
||||
if (Convert.TryFromBase64String(base64, buffer, out _) == false)
|
||||
if (!Convert.TryFromBase64String(base64, buffer, out _))
|
||||
throw new ArgumentException($"{name} is not valid base64 string");
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public static class MobileConfirmationScrapper
|
||||
throw new SessionInvalidException();
|
||||
}
|
||||
|
||||
if (conf.Success == false)
|
||||
if (!conf.Success)
|
||||
{
|
||||
var error = LoadConfirmationsError.Unknown;
|
||||
if (conf.Message != null && ErrorMessages.TryGetValue(conf.Message, out var e))
|
||||
|
||||
Reference in New Issue
Block a user