mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-26 06:41:45 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed5d3c2eda | |||
| 07eb9f15a9 | |||
| c39660fc55 | |||
| 038f62b4db | |||
| 5029f78247 | |||
| b9452c4baf |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Website": "https://achiefy.pro/?nebula=true",
|
||||
"Documentation": "https://achiefy-project.gitbook.io/nebulaauth"
|
||||
}
|
||||
@@ -77,6 +77,10 @@ public partial class HintBox : UserControl
|
||||
IconKind = PackIconKind.ErrorOutline;
|
||||
IconBrush = (Brush) Application.Current.FindResource("ErrorBrush")!;
|
||||
break;
|
||||
case HintBoxSeverity.Warning:
|
||||
IconKind = PackIconKind.WarningOutline;
|
||||
IconBrush = (Brush) Application.Current.FindResource("WarningBrush")!;
|
||||
break;
|
||||
default:
|
||||
IconKind = PackIconKind.InfoCircleOutline;
|
||||
IconBrush = (Brush) Application.Current.FindResource("InfoBrush")!;
|
||||
@@ -94,5 +98,6 @@ public partial class HintBox : UserControl
|
||||
public enum HintBoxSeverity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
@@ -15,13 +15,15 @@ namespace NebulaAuth.Core;
|
||||
|
||||
public static class DialogsController
|
||||
{
|
||||
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null)
|
||||
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null,
|
||||
bool showNoProxyWarning = false)
|
||||
{
|
||||
var vm = new LoginAgainVM
|
||||
{
|
||||
UserName = username,
|
||||
Password = currentPassword ?? string.Empty,
|
||||
SavePassword = PHandler.IsPasswordSet
|
||||
SavePassword = PHandler.IsPasswordSet,
|
||||
ShowNoProxyWarning = showNoProxyWarning
|
||||
};
|
||||
var content = new LoginAgainDialog
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NebulaAuth.Model;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NebulaAuth.Core;
|
||||
|
||||
public static class LinksManager
|
||||
{
|
||||
private const string LINKS_URL =
|
||||
"https://raw.githubusercontent.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/master/NebulaAuth/links.json";
|
||||
|
||||
private static readonly HttpClient HttpClient = new();
|
||||
|
||||
public static string? WebsiteUrl { get; private set; }
|
||||
public static string? DocumentationUrl { get; private set; }
|
||||
|
||||
public static async Task FetchAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await HttpClient.GetStringAsync(LINKS_URL).ConfigureAwait(false);
|
||||
var links = JsonConvert.DeserializeObject<RemoteLinks>(json);
|
||||
WebsiteUrl = links?.Website;
|
||||
DocumentationUrl = links?.Documentation;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Debug(ex, "Failed to fetch remote links");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,16 @@
|
||||
"es": "Grupo",
|
||||
"tr": "Grup",
|
||||
"kk": "Топ"
|
||||
},
|
||||
"ExportToZip": {
|
||||
"ru": "Экспортировать в ZIP архив",
|
||||
"en": "Export to ZIP archive",
|
||||
"uk": "Експортувати в ZIP архів",
|
||||
"fr": "Exporter en archive ZIP",
|
||||
"zh": "导出为 ZIP 压缩包",
|
||||
"es": "Exportar a archivo ZIP",
|
||||
"tr": "ZIP arşivine aktar",
|
||||
"kk": "ZIP мұрағатқа экспорттау"
|
||||
}
|
||||
},
|
||||
"Tooltips": {
|
||||
@@ -288,17 +298,27 @@
|
||||
"es": "Incluir datos de grupo compatibles con Nebula.",
|
||||
"tr": "Nebula uyumlu grup verilerini dahil et.",
|
||||
"kk": "Nebula үйлесімді топ деректерін қосу."
|
||||
},
|
||||
"ExportToZip": {
|
||||
"ru": "Экспортировать в ZIP архив вместо папки. Архив будет сохранён в выбранной директории с именем вида N_timestamp.zip.",
|
||||
"en": "Export to a ZIP archive instead of a folder. The archive will be saved in the selected directory as N_timestamp.zip.",
|
||||
"uk": "Експортувати в ZIP архів замість папки. Архів буде збережено у вибраній директорії з назвою N_timestamp.zip.",
|
||||
"fr": "Exporter dans une archive ZIP plutôt que dans un dossier. L'archive sera enregistrée dans le répertoire sélectionné sous le nom N_timestamp.zip.",
|
||||
"zh": "导出为 ZIP 压缩包而非文件夹。压缩包将保存在所选目录中,名称格式为 N_timestamp.zip。",
|
||||
"es": "Exportar a un archivo ZIP en lugar de una carpeta. El archivo se guardará en el directorio seleccionado como N_timestamp.zip.",
|
||||
"tr": "Klasör yerine ZIP arşivine aktar. Arşiv, seçilen dizine N_timestamp.zip adıyla kaydedilecektir.",
|
||||
"kk": "Қалта орнына ZIP мұрағатына экспорттау. Мұрағат таңдалған каталогта N_timestamp.zip атауымен сақталады."
|
||||
}
|
||||
},
|
||||
"ExportAccountsPlaceholder": {
|
||||
"ru": "Введите логины аккаунтов или SteamID (по одному на строку) для экспорта в выбранную директорию.",
|
||||
"en": "Enter account logins or SteamIDs (one per line) to export to the selected directory.",
|
||||
"uk": "Введіть логіни акаунтів або SteamID (по одному на рядок) для експорту у вибрану директорію.",
|
||||
"fr": "Entrez les identifiants de compte ou les SteamID (un par ligne) pour exporter vers le répertoire sélectionné.",
|
||||
"zh": "输入要导出到所选目录的账户登录名或 SteamID(每行一个)。",
|
||||
"es": "Introduce los logins de las cuentas o SteamID (uno por línea) para exportarlos al directorio seleccionado.",
|
||||
"tr": "Seçilen dizine dışa aktarmak için hesap girişlerini veya SteamID’leri (her satıra bir tane) girin.",
|
||||
"kk": "Таңдалған каталогқа экспорттау үшін аккаунт логиндерін немесе SteamID (әр жолға біреуден) енгізіңіз."
|
||||
"ru": "Введите логины или SteamID (по одному на строку). Поддерживается формат логин:пароль — пароль будет проигнорирован. Оставьте пустым для экспорта всех аккаунтов.",
|
||||
"en": "Enter logins or SteamIDs (one per line). Supports login:password format — the password will be ignored. Leave empty to export all accounts.",
|
||||
"uk": "Введіть логіни або SteamID (по одному на рядок). Підтримується формат логін:пароль — пароль буде проігноровано. Залиште порожнім для експорту всіх акаунтів.",
|
||||
"fr": "Entrez les identifiants ou SteamID (un par ligne). Supporte le format login:mot_de_passe — le mot de passe sera ignoré. Laissez vide pour exporter tous les comptes.",
|
||||
"zh": "输入登录名或 SteamID(每行一个)。支持 login:password 格式——密码将被忽略。留空则导出所有账户。",
|
||||
"es": "Introduce logins o SteamID (uno por línea). Soporta el formato login:contraseña — la contraseña será ignorada. Déjalo vacío para exportar todas las cuentas.",
|
||||
"tr": "Girişleri veya SteamID’leri girin (her satıra bir tane). login:şifre formatı desteklenir — şifre yok sayılır. Tüm hesapları dışa aktarmak için boş bırakın.",
|
||||
"kk": "Логиндерді немесе SteamID (әр жолға біреуден) енгізіңіз. логин:құпиясөз форматы қолданылады — құпиясөз еленбейді. Барлық аккаунттарды экспорттау үшін бос қалдырыңыз."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,16 @@
|
||||
"es": "Pulsa 'DEL' para eliminar",
|
||||
"tr": "Silmek için 'DEL' tuşuna bas",
|
||||
"kk": "Жою үшін 'DEL' пернесін басыңыз"
|
||||
},
|
||||
"NoProxyWarning": {
|
||||
"en": "No proxy is assigned",
|
||||
"ru": "Прокси не установлен",
|
||||
"zh": "未分配代理",
|
||||
"uk": "Проксі не призначено",
|
||||
"fr": "Aucun proxy assigné",
|
||||
"es": "No hay ningún proxy asignado",
|
||||
"tr": "Proxy atanmamış",
|
||||
"kk": "Прокси тағайындалмаған"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public static class MAACRequestHandler
|
||||
{
|
||||
return withSessionHandler
|
||||
? Result<T>.Success(await SessionHandler.Handle(req, client.Mafile, client.Chp(),
|
||||
GetTimerPrefix(client.Mafile)))
|
||||
GetTimerPrefix(client.Mafile), false))
|
||||
: Result<T>.Success(await req());
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
|
||||
@@ -13,4 +13,5 @@ public class MafileExportTemplate
|
||||
public bool IncludeNebulaPassword { get; set; }
|
||||
public bool IncludeNebulaGroup { get; set; }
|
||||
public string? Path { get; set; }
|
||||
public bool ExportToZip { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Core;
|
||||
@@ -36,48 +39,79 @@ public static class MafileExporter
|
||||
return new ExportResult(LocManager.GetCodeBehindOrDefault("AccessDenied", "MafileExporter.AccessDenied"));
|
||||
}
|
||||
|
||||
|
||||
var exported = new Dictionary<Mafile, string>();
|
||||
var notFound = new List<string>();
|
||||
var conflict = new List<string>();
|
||||
|
||||
var toExport = new List<(string key, Mafile mafile)>();
|
||||
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)
|
||||
toExport.Add((key, maf));
|
||||
else
|
||||
notFound.Add(key);
|
||||
}
|
||||
|
||||
if (template.ExportToZip)
|
||||
{
|
||||
if (toExport.Count > 0)
|
||||
{
|
||||
var fileName = await ExportMafile(template.Path, template, maf);
|
||||
if (fileName == null)
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var zipName = $"{toExport.Count}_{timestamp}.zip";
|
||||
var zipPath = Path.Combine(template.Path, zipName);
|
||||
|
||||
await using var zipStream = File.Create(zipPath);
|
||||
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
|
||||
var addedEntries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var (key, maf) in toExport)
|
||||
{
|
||||
var (content, fileName) = BuildMafileContent(template, maf);
|
||||
if (!addedEntries.Add(fileName))
|
||||
{
|
||||
conflict.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
var entry = archive.CreateEntry(fileName);
|
||||
await using var entryStream = entry.Open();
|
||||
await using var writer = new StreamWriter(entryStream, Encoding.UTF8);
|
||||
await writer.WriteAsync(content);
|
||||
exported[maf] = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var (key, maf) in toExport)
|
||||
{
|
||||
var (content, fileName) = BuildMafileContent(template, maf);
|
||||
var fullPath = Path.Combine(template.Path, fileName);
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
conflict.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
exported[maf] = fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
notFound.Add(key);
|
||||
await File.WriteAllTextAsync(fullPath, content);
|
||||
exported[maf] = fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
return new ExportResult(exported, notFound, conflict);
|
||||
}
|
||||
|
||||
private static async Task<string?> ExportMafile(string path, MafileExportTemplate template, Mafile mafile)
|
||||
private static (string content, string fileName) BuildMafileContent(MafileExportTemplate template, Mafile mafile)
|
||||
{
|
||||
// We preserve SteamId even if other info is not included
|
||||
var session = template.IncludeSessionData
|
||||
? mafile.SessionData
|
||||
: new MobileSessionData(null!, mafile.SteamId, default, null, tokens: null);
|
||||
|
||||
|
||||
var serializeMaf = new Mafile
|
||||
{
|
||||
SharedSecret = template.IncludeSharedSecret ? mafile.SharedSecret : null!,
|
||||
@@ -102,14 +136,7 @@ public static class MafileExporter
|
||||
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;
|
||||
return (serialized, fileName);
|
||||
}
|
||||
|
||||
private static bool PathIsValid(string path)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public class RemoteLinks
|
||||
{
|
||||
public string? Website { get; set; }
|
||||
public string? Documentation { get; set; }
|
||||
}
|
||||
@@ -6,7 +6,9 @@ using AchiesUtilities.Web.Models;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
using NebulaAuth.ViewModel.Other;
|
||||
using SteamLib.Exceptions.Authorization;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
@@ -16,13 +18,13 @@ public static partial class SessionHandler
|
||||
private static readonly SemaphoreSlim Semaphore = new(1, 1);
|
||||
|
||||
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile,
|
||||
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null)
|
||||
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null, bool allowInteractiveLogin = true)
|
||||
{
|
||||
chp ??= MaClient.GetHttpClientHandlerPair(mafile);
|
||||
await Semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix);
|
||||
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix, allowInteractiveLogin);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -31,7 +33,7 @@ public static partial class SessionHandler
|
||||
}
|
||||
|
||||
private static async Task<T> HandleInternal<T>(Func<Task<T>> func, HttpClientHandlerPair chp, Mafile mafile,
|
||||
string? snackbarPrefix = null)
|
||||
string? snackbarPrefix = null, bool allowInteractiveLogin = true)
|
||||
{
|
||||
using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler");
|
||||
Exception? currentException = null;
|
||||
@@ -86,6 +88,32 @@ public static partial class SessionHandler
|
||||
}
|
||||
}
|
||||
|
||||
if (allowInteractiveLogin && !DialogHost.IsDialogOpen(null))
|
||||
{
|
||||
var noProxy = mafile.Proxy == null && MaClient.DefaultProxy == null;
|
||||
var currentPassword = GetPassword(mafile);
|
||||
|
||||
Task<LoginAgainVM?> dialogTask = null!;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
dialogTask = DialogsController.ShowLoginAgainDialog(mafile.AccountName, currentPassword, noProxy);
|
||||
});
|
||||
|
||||
var vm = await dialogTask;
|
||||
if (vm != null)
|
||||
{
|
||||
Shell.Logger.Info("User provided credentials interactively for {name}, attempting login",
|
||||
mafile.AccountName);
|
||||
var logged = await LoginAgainInternal(chp, mafile, vm.Password, vm.SavePassword);
|
||||
if (logged)
|
||||
{
|
||||
Shell.Logger.Debug("Interactive login for {name} succeeded, retrying operation",
|
||||
mafile.AccountName);
|
||||
return await func();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG,
|
||||
currentException);
|
||||
}
|
||||
@@ -118,6 +146,7 @@ public static partial class SessionHandler
|
||||
{
|
||||
Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
ExceptionHandler.Handle(ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -40,7 +39,8 @@ public static class Shell
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new CantAlignTimeException("", ex);
|
||||
Logger.Error(ex, "Failed to align time with Steam");
|
||||
TimeAligner.SetTimeDifference(0);
|
||||
}
|
||||
|
||||
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
|
||||
@@ -48,6 +48,7 @@ public static class Shell
|
||||
ProxyAssignmentCache.Initialize(Storage.MaFiles);
|
||||
MAACStorage.Initialize();
|
||||
MafileExporterStorage.Initialize();
|
||||
_ = LinksManager.FetchAsync();
|
||||
|
||||
ExtensionsLogger.LogDebug("Application started");
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public static class ExceptionHandler
|
||||
}
|
||||
case LoginException e:
|
||||
{
|
||||
return "LoginException".GetCodeBehindLocalization() + ": " +
|
||||
return "LoginException".GetCodeBehindLocalization() +
|
||||
ErrorTranslatorHelper.TranslateLoginError(e.Error);
|
||||
}
|
||||
case UnsupportedAuthTypeException e:
|
||||
|
||||
@@ -25,24 +25,30 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox TabIndex="0" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
|
||||
<nebulaAuth:HintBox Grid.Row="0"
|
||||
Margin="10,10,10,0"
|
||||
Severity="Warning"
|
||||
FontSize="14"
|
||||
Visibility="{Binding ShowNoProxyWarning, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Text="{Tr LoginAgainDialog.NoProxyWarning}" />
|
||||
<TextBox TabIndex="0" Grid.Row="1" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
|
||||
|
||||
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||
Padding="10"
|
||||
FontSize="15"
|
||||
Margin="10,10,10,0"
|
||||
Grid.Row="0"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
|
||||
materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"
|
||||
materialDesign:HintAssist.IsFloating="False"
|
||||
materialDesign:TextFieldAssist.LeadingIcon="Key"
|
||||
materialDesign:TextFieldAssist.HasLeadingIcon="True" />
|
||||
<CheckBox FontSize="15" Grid.Row="1" Margin="10,5,10,0"
|
||||
<CheckBox FontSize="15" Grid.Row="2" Margin="10,5,10,0"
|
||||
IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}"
|
||||
IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}" />
|
||||
<Grid Grid.Row="2" Margin="10,10,10,0">
|
||||
<Grid Grid.Row="3" Margin="10,10,10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
Background="Transparent"
|
||||
RenderOptions.BitmapScalingMode="HighQuality">
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Loaded="LinksView_Loaded">
|
||||
|
||||
<materialDesign:Card Padding="24" HorizontalAlignment="Center" VerticalAlignment="Center" UniformCornerRadius="12">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
@@ -49,16 +50,20 @@
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="0,0,0,5" FontSize="18" Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Click="Website_Click" IsEnabled="False">
|
||||
<Button x:Name="WebsiteButton" Margin="0,0,0,5" FontSize="18"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsEnabled="False"
|
||||
Click="Website_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="Web" Width="24" Height="24" Margin="0 0 8 0" />
|
||||
<TextBlock Text="Web-site" VerticalAlignment="Center" />
|
||||
<TextBlock Text="Achiefy" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="0,0,0,5" FontSize="18" Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Click="Documentation_Click" IsEnabled="False">
|
||||
<Button x:Name="DocumentationButton" Margin="0,0,0,5" FontSize="18"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsEnabled="False"
|
||||
Click="Documentation_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Viewbox Width="22" Height="22" Margin="0 0 8 0">
|
||||
<Canvas Width="22" Height="22">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
@@ -13,6 +13,12 @@ public partial class LinksView : UserControl
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void LinksView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WebsiteButton.IsEnabled = LinksManager.WebsiteUrl != null;
|
||||
DocumentationButton.IsEnabled = LinksManager.DocumentationUrl != null;
|
||||
}
|
||||
|
||||
private void Telegram_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("https://t.me/nebulaauth") {UseShellExecute = true});
|
||||
@@ -26,12 +32,14 @@ public partial class LinksView : UserControl
|
||||
|
||||
private void Website_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("https://yourwebsite.com") {UseShellExecute = true});
|
||||
if (LinksManager.WebsiteUrl != null)
|
||||
Process.Start(new ProcessStartInfo(LinksManager.WebsiteUrl) {UseShellExecute = true});
|
||||
}
|
||||
|
||||
private void Documentation_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("https://yourwebsite.com") {UseShellExecute = true});
|
||||
if (LinksManager.DocumentationUrl != null)
|
||||
Process.Start(new ProcessStartInfo(LinksManager.DocumentationUrl) {UseShellExecute = true});
|
||||
}
|
||||
|
||||
private void CheckForUpdates_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
mc:Ignorable="d"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
MinWidth="400"
|
||||
MaxWidth="400"
|
||||
MinWidth="500"
|
||||
MaxWidth="500"
|
||||
Background="{DynamicResource WindowBackground}"
|
||||
d:DataContext="{d:DesignInstance other:MafileExporterVM}"
|
||||
d:Background="#141119"
|
||||
@@ -113,6 +113,12 @@
|
||||
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}">
|
||||
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.ExportToZip, FallbackValue=False}"
|
||||
Margin="3,0,0,3"
|
||||
Content="{Tr ExportDialog.ExportOptions.ExportToZip}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.ExportToZip}" />
|
||||
|
||||
<TextBlock FontWeight="DemiBold"
|
||||
Text="{Tr ExportDialog.ExportSettingsCommon}" />
|
||||
|
||||
|
||||
@@ -11,5 +11,7 @@ public partial class LoginAgainVM : ObservableObject
|
||||
|
||||
[ObservableProperty] private bool _savePassword;
|
||||
|
||||
[ObservableProperty] private bool _showNoProxyWarning;
|
||||
|
||||
[ObservableProperty] private string _userName = null!;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
@@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.MafileExport;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
@@ -24,8 +25,7 @@ public partial class MafileExporterVM : ObservableObject
|
||||
|
||||
public ObservableCollection<MafileExportTemplateVM> Templates { get; }
|
||||
|
||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ExportCommand))]
|
||||
private string? _accountsText;
|
||||
[ObservableProperty] private string? _accountsText;
|
||||
|
||||
private string? _cachedName;
|
||||
private MafileExportTemplateVM? _currentTemplate;
|
||||
@@ -160,6 +160,7 @@ public partial class MafileExporterVM : ObservableObject
|
||||
IncludeNebulaProxy = true,
|
||||
IncludeNebulaPassword = true,
|
||||
IncludeNebulaGroup = true,
|
||||
ExportToZip = false,
|
||||
Path = null
|
||||
};
|
||||
MafileExporterStorage.AddTemplate(template);
|
||||
@@ -211,21 +212,26 @@ public partial class MafileExporterVM : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(ExportCanExecute))]
|
||||
[RelayCommand(CanExecute = nameof(CurrentTemplateNotNull))]
|
||||
private async Task Export()
|
||||
{
|
||||
var lines = AccountsText;
|
||||
var template = CurrentTemplate;
|
||||
if (string.IsNullOrWhiteSpace(lines) || template == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (template == null) return;
|
||||
|
||||
var split = Regex
|
||||
.Split(lines, "\r\n|\r|\n")
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.ToArray();
|
||||
string[] split;
|
||||
if (string.IsNullOrWhiteSpace(AccountsText))
|
||||
{
|
||||
split = Storage.MaFiles.Select(m => m.AccountName).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
split = Regex
|
||||
.Split(AccountsText, "\r\n|\r|\n")
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(ExtractLogin)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
ResetHintText();
|
||||
ExportResult res;
|
||||
@@ -268,6 +274,14 @@ public partial class MafileExporterVM : ObservableObject
|
||||
AccountsText = text;
|
||||
}
|
||||
|
||||
// Strips the password part from "login:password" input lines.
|
||||
// SteamId64 values contain no colon, so any colon signals the login:password format.
|
||||
private static string ExtractLogin(string line)
|
||||
{
|
||||
var idx = line.IndexOf(':');
|
||||
return idx > 0 ? line[..idx] : line;
|
||||
}
|
||||
|
||||
private void SetHintText(string? text, HintBoxSeverity severity = HintBoxSeverity.Error)
|
||||
{
|
||||
HintText = text;
|
||||
@@ -281,12 +295,6 @@ public partial class MafileExporterVM : ObservableObject
|
||||
|
||||
#region CanExecute
|
||||
|
||||
private bool ExportCanExecute()
|
||||
{
|
||||
return CurrentTemplateNotNull() && !string.IsNullOrWhiteSpace(AccountsText);
|
||||
}
|
||||
|
||||
|
||||
private bool CurrentTemplateNotNull()
|
||||
{
|
||||
return CurrentTemplate != null;
|
||||
@@ -307,6 +315,7 @@ public partial class MafileExporterVM : ObservableObject
|
||||
|
||||
public partial class MafileExportTemplateVM : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private bool _exportToZip;
|
||||
[ObservableProperty] private bool _includeIdentitySecret;
|
||||
[ObservableProperty] private bool _includeNebulaGroup;
|
||||
[ObservableProperty] private bool _includeNebulaPassword;
|
||||
@@ -334,6 +343,7 @@ public partial class MafileExportTemplateVM : ObservableObject
|
||||
IncludeNebulaProxy = x.IncludeNebulaProxy,
|
||||
IncludeNebulaPassword = x.IncludeNebulaPassword,
|
||||
IncludeNebulaGroup = x.IncludeNebulaGroup,
|
||||
ExportToZip = x.ExportToZip,
|
||||
Path = x.Path
|
||||
};
|
||||
}
|
||||
@@ -352,6 +362,7 @@ public partial class MafileExportTemplateVM : ObservableObject
|
||||
IncludeNebulaProxy = IncludeNebulaProxy,
|
||||
IncludeNebulaPassword = IncludeNebulaPassword,
|
||||
IncludeNebulaGroup = IncludeNebulaGroup,
|
||||
ExportToZip = ExportToZip,
|
||||
Path = Path
|
||||
};
|
||||
}
|
||||
@@ -368,6 +379,7 @@ public partial class MafileExportTemplateVM : ObservableObject
|
||||
model.IncludeNebulaProxy = IncludeNebulaProxy;
|
||||
model.IncludeNebulaPassword = IncludeNebulaPassword;
|
||||
model.IncludeNebulaGroup = IncludeNebulaGroup;
|
||||
model.ExportToZip = ExportToZip;
|
||||
model.Path = Path;
|
||||
}
|
||||
}
|
||||
@@ -80,4 +80,10 @@ public static class TimeAligner
|
||||
client.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTimeDifference(int timeDifference)
|
||||
{
|
||||
_timeDifference = timeDifference;
|
||||
_aligned = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user