Compare commits

...

19 Commits

Author SHA1 Message Date
Давид Чернопятов 9a277d07db AchiesUtilities.Web updated to 1.0.11. This version contains proxy credentials fix (407 error) 2024-05-13 18:05:57 +03:00
Давид Чернопятов fcd4056619 Added a temporary solution for validating old mafiles when reading SteamID, as well as changes to the UI and minor fixes 2024-04-25 02:59:17 +03:00
Давид Чернопятов 4cc69e9a57 1.4.8
Assembly version fixed
2024-04-10 01:45:20 +03:00
Давид Чернопятов 672ca22662 Date added 2024-04-10 01:42:59 +03:00
Давид Чернопятов a9fdb58cac The most significant changes in the code include the addition of a new HTML file changelog\1.4.8.html to the project NebulaAuth.sln, which displays the changelog for version 1.4.8 of the software. A new converter ProxyDataTextConverter was added to App.xaml and a new class ProxyDataTextConverter was added in ProxyTextConverter.cs. The ProxyTextConverter was also modified to include the port number in the return string.
Several methods were added and modified in `Storage.cs`, `MainVM_Groups.cs`, `MainVM_Proxy.cs`, and `ProxyManagerVM.cs` to validate if the data can be saved before performing various operations. The `SnackbarController.cs` was modified to increase the minimum snackbar time and adjust the duration calculation.

In `MafileSerializer_SessionData.cs`, the check for whether the refresh token is expired or not was moved to a different location within the code. New localization entries were added in `localization.loc.json`.

The changes are as follows:

1. Added a new HTML file `changelog\1.4.8.html` to the project `NebulaAuth.sln`.
2. Added a new converter `ProxyDataTextConverter` to `App.xaml`.
3. Imported `AchiesUtilities.Web.Proxy` in `ProxyTextConverter.cs`.
4. Modified the return string in `ProxyTextConverter` to include the port number.
5. Added a new class `ProxyDataTextConverter` in `ProxyTextConverter.cs`.
6. Increased the minimum snackbar time from 1000 to 1200 in `SnackbarController.cs`.
7. Modified the duration calculation in `GetSnackbarTime` method in `SnackbarController.cs`.
8. Added tooltips to ComboBoxes in `MainWindow.xaml`.
9. Added a new method `ValidateCanSave` in `Storage.cs` to validate if the data can be saved.
10. Added a new method `GetProxyString` in `ProxyStorage.cs` to get the proxy string.
11. Modified the `CompareProxy` method in `ProxyStorage.cs` to compare the address and port of the proxy data.
12. Removed a logger info line in `Shell.cs`.
13. Added a new property `DuplicateFound` in `Storage.cs`.
14. Modified the `CreatePathForMafile` method in `Storage.cs` to create a file name based on the account name or steam id.
15. Modified the `AddGroup` method in `MainVM_Groups.cs` to validate if the data can be saved before adding a group.
16. Modified the `AddToGroup` method in `MainVM_Groups.cs` to validate if the data can be saved before adding to a group.
17. Modified the `RemoveGroup` method in `MainVM_Groups.cs` to validate if the data can be saved before removing a group.
18. Modified the `PerformQuery` method in `MainVM_Groups.cs` to set `SelectedMafile` to the first item in `MaFiles`.
19. Modified the `RemoveProxy` method in `MainVM_Proxy.cs` to validate if the data can be saved before removing a proxy.
20. Modified the `SelectedProxyChanged` method in `MainVM_Proxy.cs` to validate if the data can be saved before changing the selected proxy.
21. Added a new method `ValidateCanSaveAndWarn` in `MainVM_Proxy.cs` to validate if the data can be saved and send a snackbar message if it can't.
22. Modified the `TimerCheckSeconds` property in `MainVM_Timer.cs` to send a snackbar message when the timer is changed.
23. Added a new method `RemoveProxy` in `LoginAgainOnImportVM.cs` to remove the selected proxy.
24. Modified the `AddProxy` method in `ProxyManagerVM.cs` to use the `DefaultScheme` to parse the proxy data.
25. Modified the `AddProxy` method in `ProxyManagerVM.cs` to use the `DefaultScheme` to parse the proxy data and get the proxy string.
26. Modified the `CopyProxy` method in `ProxyManagerVM.cs` to get the proxy string.
27. Added new localization entries in `localization.loc.json`.
28. The `update.xml` file was updated to reflect the new version of the software (1.4.8.0) and the corresponding download URL and changelog URL were updated accordingly.
29. In `AdmissionHelper.cs`, a new constant `SESSION_ID_COOKIE_NAME` was added to replace hardcoded "sessionid" strings. The same was done for `LANGUAGE_COOKIE_NAME` replacing "Steam_Language". This change was reflected in multiple methods within the `AdmissionHelper` class.
30. A new method `CloneCookie` was added to `AdmissionHelper.cs` to create a copy of a given cookie. This method was then used to replace repetitive code in the `TransferCommunityCookies` method.
31. In `MafileSerializer_SessionData.cs`, the check for whether the refresh token is expired or not was moved to a different location within the code. This change does not affect the functionality but may improve readability or maintainability of the code.
2024-04-10 01:39:42 +03:00
Давид Чернопятов 939fca31e9 1.4.7 2024-02-16 17:07:15 +02:00
Achies c5191d6a40 Add files via upload 2024-02-04 19:28:12 +02:00
Давид Чернопятов 79f62f266c 1.4.6 2024-02-04 19:27:21 +02:00
achiez b7a971b37e Update README.md 2024-02-01 17:51:29 +02:00
achiez 6c11d80508 Update README.md 2024-02-01 17:30:50 +02:00
achiez 4724a4c094 Update README-RU.md 2024-02-01 17:28:47 +02:00
achiez 3297546ac2 Update README.md 2024-02-01 17:08:20 +02:00
achiez 760cfbf4f1 Create README-UA.md 2024-02-01 17:07:10 +02:00
achiez 11a13174b8 Create README-RU.md 2024-02-01 17:00:59 +02:00
achiez 7a8bdc5073 Update README.md 2024-02-01 16:56:27 +02:00
Давид Чернопятов bbcfe59f01 1.4.5 2024-02-01 16:36:52 +02:00
achiez e7c9473eb1 Add files via upload 2024-02-01 16:35:06 +02:00
achiez 06f28b4f76 Update README.md 2024-02-01 16:21:56 +02:00
Давид Чернопятов dd331bf18d update.xml updated 2024-02-01 03:13:44 +02:00
55 changed files with 1807 additions and 251 deletions
+12
View File
@@ -12,6 +12,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAuth.LegacyConverter"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamLibForked", "SteamLibForked\SteamLibForked.csproj", "{09F02072-F91D-4DAA-87BC-A34D3E150570}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{161BFADE-FCF3-45D1-82EA-8A1B187529F7}"
ProjectSection(SolutionItems) = preProject
changelog\1.3.4.html = changelog\1.3.4.html
changelog\1.4.4.html = changelog\1.4.4.html
changelog\1.4.5.html = changelog\1.4.5.html
changelog\1.4.6.html = changelog\1.4.6.html
changelog\1.4.7.html = changelog\1.4.7.html
changelog\1.4.8.html = changelog\1.4.8.html
changelog\1.4.9.html = changelog\1.4.9.html
changelog\1.5.0.html = changelog\1.5.0.html
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+2
View File
@@ -18,8 +18,10 @@
<converters:OnOffBoolTextConverter x:Key="OnOffBoolTextConverter"/>
<converters:SelectedProxyTextConverter x:Key="SelectedProxyTextConverter"/>
<converters:ProxyTextConverter x:Key="ProxyTextConverter"/>
<converters:ProxyDataTextConverter x:Key="ProxyDataTextConverter"/>
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter"/>
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter"/>
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter"/>
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter"/>
@@ -0,0 +1,30 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class AnyMafilesToVisibilityConverter : IValueConverter
{
private static bool EverAnyMafiles;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (EverAnyMafiles)
{
return Visibility.Collapsed;
}
if (value is 0)
{
return Visibility.Visible;
}
EverAnyMafiles = true;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
+20 -1
View File
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Windows.Data;
using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.Converters;
@@ -14,7 +15,25 @@ public class ProxyTextConverter : IValueConverter
return string.Empty;
}
return $"{p.Id}: {p.Data.Address}";
return $"{p.Id}: {p.Data.Address}:{p.Data.Port}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ProxyDataTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not ProxyData p)
{
return string.Empty;
}
return $"{p.Address}:{p.Port}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+20 -2
View File
@@ -1,9 +1,11 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model.Entities;
using NebulaAuth.View;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel.Other;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
namespace NebulaAuth.Core;
@@ -36,7 +38,23 @@ public static class DialogsController
{
UserName = username
};
var content = new LoginAgainDialog()
var content = new LoginAgainDialog
{
DataContext = vm
};
var result = await DialogHost.Show(content);
if (result is true)
{
return vm;
}
return null;
}
public static async Task<LoginAgainOnImportVM?> ShowLoginAgainOnImportDialog(Mafile mafile, IEnumerable<MaProxy> proxies)
{
var vm = new LoginAgainOnImportVM(mafile, proxies);
var content = new LoginAgainOnImportDialog()
{
DataContext = vm
};
+7 -8
View File
@@ -7,11 +7,8 @@ public class SnackbarController
{
public static SnackbarMessageQueue MessageQueue { get; } = new() { DiscardDuplicates = true};
private const int MIN_SNACKBAR_TIME = 1000;
public SnackbarController()
{
private const int MIN_SNACKBAR_TIME = 1200;
}
/// <summary>
///
/// </summary>
@@ -46,10 +43,12 @@ public class SnackbarController
private static TimeSpan GetSnackbarTime(string str)
{
if (str.Length <= 100) return TimeSpan.FromMilliseconds(MIN_SNACKBAR_TIME);
var length = str.Length / 0.07;
return TimeSpan.FromMilliseconds(length);
var duration = str.Length / 0.03;
if (duration < MIN_SNACKBAR_TIME)
{
duration = MIN_SNACKBAR_TIME;
}
return TimeSpan.FromMilliseconds(duration);
}
+1 -1
View File
@@ -20,7 +20,7 @@ public static class TrayManager
_notifyIcon = new NotifyIcon();
_notifyIcon.Text = "NebulaAuth";
Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/nebula lock.ico"))!.Stream;
Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/lock.ico"))!.Stream;
_notifyIcon.Icon = new Icon(iconStream);
_notifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick;
+60 -1
View File
@@ -1,7 +1,14 @@
using AutoUpdaterDotNET;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model;
using NebulaAuth.View;
using NebulaAuth.ViewModel.Other;
using System;
using System.IO;
using System.Net;
using System.Windows.Forms;
using System.Windows.Threading;
using Application = System.Windows.Application;
namespace NebulaAuth.Core;
@@ -17,8 +24,60 @@ public static class UpdateManager
if (Settings.Instance.AllowAutoUpdate)
AutoUpdater.UpdateMode = Mode.ForcedDownload;
AutoUpdater.Start(UPDATE_URL);
}
static UpdateManager()
{
//AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
}
private static async void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args.Error == null)
{
if (args.IsUpdateAvailable)
{
DialogResult dialogResult;
var dialog = new UpdaterView()
{
DataContext = new UpdaterVM(args)
};
await DialogHost.Show(dialog);
Application.Current.Shutdown();
}
else
{
}
}
else
{
if (args.Error is WebException)
{
}
else
{
}
}
}
private static void RunUpdate(UpdateInfoEventArgs args)
{
Application.Current.Dispatcher.Invoke(() =>
{
if (AutoUpdater.DownloadUpdate(args))
{
Application.Current.Shutdown();
}
}, DispatcherPriority.ContextIdle);
}
}
+25 -10
View File
@@ -41,7 +41,7 @@
<Rectangle Grid.Row="0" Grid.RowSpan="2" Opacity="0.5" Fill="#FF1F1D1D" Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}" />
<Rectangle Name="DragNDropOverlay" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="1" Fill="#242123" Opacity="0.9" Visibility="Hidden" />
<StackPanel Name="DragNDropPanel" Grid.Row="0" ZIndex="2" w:FontScaleWindow.Scale="1" w:FontScaleWindow.ResizeFont="True" Grid.RowSpan="3" Visibility="Hidden" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock FontWeight="Bold" Foreground="#9a65b8">Отпустите, чтобы добавить mafile</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#9a65b8" Text="{Tr MainWindow.Global.DragNDropHint}"/>
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36" Kind="FileReplaceOutline" />
</StackPanel>
<ToolBarTray IsLocked="True" Grid.Row="0">
@@ -68,10 +68,14 @@
</MenuItem>
</Menu>
<Separator />
<ComboBox MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}" md:TextFieldAssist.HasClearButton="True" IsEditable="True" ItemsSource="{Binding Groups}" SelectedValue="{Binding SelectedGroup}">
<ComboBox ToolTip="{Tr MainWindow.AppBar.GroupToolTip}" md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}"
MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
ItemsSource="{Binding Groups}"
SelectedValue="{Binding SelectedGroup}">
<FrameworkElement.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type md:PackIcon}">
<Style TargetType="md:PackIcon">
<Setter Property="Width" Value="15" />
<Setter Property="Height" Value="15" />
</Style>
@@ -81,7 +85,7 @@
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}" CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
</UIElement.InputBindings>
</ComboBox>
<ComboBox MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}" md:ComboBoxAssist.ShowSelectedItem="False" SelectedItem="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}" MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}" md:ComboBoxAssist.ShowSelectedItem="False" SelectedItem="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
@@ -96,7 +100,13 @@
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
</UIElement.InputBindings>
</ComboBox>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFF0000" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.MafileProxyInUse}" ToolTipService.InitialShowDelay="600">
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFF0000" Margin="3" ToolTipService.InitialShowDelay="300">
<md:PackIcon.ToolTip>
<TextBlock>
<Run Text="{Tr MainWindow.AppBar.Proxy.ProxyAlert.MafileProxyInUse, IsDynamic=False}"/>
<Run Text="&#10;"/><Run Text="{Binding SelectedMafile.Proxy.Data, FallbackValue='', Mode=OneWay}"/>
</TextBlock>
</md:PackIcon.ToolTip>
<UIElement.Visibility>
<Binding Path="ProxyExist">
<Binding.Converter>
@@ -108,9 +118,9 @@
</Binding>
</UIElement.Visibility>
</md:PackIcon>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="600" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipAccentCheckBox}" IsChecked="{Binding TradeTimerEnabled}" Content="{Tr MainWindow.AppBar.TradeTimerHint}"/>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipAccentCheckBox}" IsChecked="{Binding MarketTimerEnabled}" Content="{Tr MainWindow.AppBar.MarketTimerHint}"/>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="300" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" ></md:PackIcon>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" IsChecked="{Binding TradeTimerEnabled}" Content="{Tr MainWindow.AppBar.TradeTimerHint}"/>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipSecondaryCheckBox}" IsChecked="{Binding MarketTimerEnabled}" Content="{Tr MainWindow.AppBar.MarketTimerHint}"/>
<TextBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
</ToolBar>
</ToolBarTray>
@@ -119,12 +129,12 @@
<ColumnDefinition Width="0.4*" />
<ColumnDefinition Width="0.6*" />
</Grid.ColumnDefinitions>
<Grid>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="10,15,10,15" DisplayMemberPath="AccountName" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
<ListBox Grid.Row="0" w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="10,15,10,15" DisplayMemberPath="AccountName" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}" ItemsSource="{Binding Groups}">
@@ -146,6 +156,11 @@
</ContextMenu>
</FrameworkElement.ContextMenu>
</ListBox>
<TextBlock Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}, Mode=OneWay}" Margin="5" FontSize="16" Grid.Row="0" TextWrapping="WrapWithOverflow" Text="{Tr MainWindow.Global.StartTip}">
<TextBlock.Background>
<SolidColorBrush Color="DarkGray" Opacity="0.5"/>
</TextBlock.Background>
</TextBlock>
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}" md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10" md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<md:Card Grid.Column="1" Margin="10,10,15,10" UniformCornerRadius="15">
+7 -9
View File
@@ -56,7 +56,7 @@ public static class MaClient
{
if (account.SessionData != null)
{
ClientHandler.CookieContainer.SetSteamMobileCookies(account.SessionData);
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(account.SessionData);
}
else
{
@@ -95,6 +95,7 @@ public static class MaClient
Storage.UpdateMafile(mafile);
}
public static async Task RefreshSession(Mafile mafile)
{
ValidateMafile(mafile, true);
@@ -107,6 +108,7 @@ public static class MaClient
mafile.SessionData.SetMobileToken(newToken);
}
//RETHINK: Do we need this? Mobile token is enough
var communityToken = mafile.SessionData!.GetToken(SteamDomain.Community);
if (communityToken == null || communityToken.Value.IsExpired)
{
@@ -114,9 +116,9 @@ public static class MaClient
var newToken = SteamTokenHelper.Parse(communityTokenString);
mafile.SessionData.SetToken(SteamDomain.Community, newToken);
}
Storage.UpdateMafile(mafile);
ClientHandler.CookieContainer.SetSteamMobileCookies(mafile.SessionData);
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
}
public static Task<bool> SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
@@ -173,14 +175,10 @@ public static class MaClient
public static async Task<LoginConfirmationResult> ConfirmLoginRequest(Mafile mafile)
{
if (mafile.SessionData == null)
{
throw new SessionExpiredException();
}
ValidateMafile(mafile);
var token = mafile.SessionData.GetMobileToken()!.Value;
SetProxy(mafile);
var token = mafile.SessionData!.GetMobileToken()!.Value;
var uri = "https://api.steampowered.com/IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" + token.Token;
GetAuthSessionsForAccount_Response getsess;
+27 -11
View File
@@ -3,6 +3,7 @@ using System.IO;
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using System.Linq;
using AchiesUtilities.Web.Proxy.Parsing;
using NebulaAuth.Core;
using Newtonsoft.Json;
@@ -12,13 +13,21 @@ public static class ProxyStorage
{
public const string FORMAT = ADDRESS_FORMAT + ":{USER}:{PASS}";
public const string ADDRESS_FORMAT = "{IP}:{PORT}";
public static readonly ProxyScheme DefaultScheme = new ProxyScheme(
ProxyDefaultFormats.UniversalHostFirstColonDelimiter, false, ProxyProtocol.HTTP,
ProxyPatternProtocol.HTTP | ProxyPatternProtocol.HTTPs,
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4, PatternRequirement.Optional,
PatternRequirement.Optional);
public static ObservableDictionary<int, ProxyData> Proxies { get; } = new();
static ProxyStorage()
{
if(File.Exists("proxies.json") == false)
if (File.Exists("proxies.json") == false)
return;
try
{
@@ -34,14 +43,14 @@ public static class ProxyStorage
MaClient.DefaultProxy = Proxies[proxies.DefaultProxy.Value];
}
}
catch(Exception ex)
catch (Exception ex)
{
SnackbarController.SendSnackbar("Ошибка при загрузке прокси");
SnackbarController.SendSnackbar(ex.Message);
}
}
public static void SetProxy(int? id, ProxyData proxyData)
@@ -59,6 +68,7 @@ public static class ProxyStorage
}
Proxies[id] = proxyData;
Save();
}
public static void RemoveProxy(int id)
@@ -67,8 +77,8 @@ public static class ProxyStorage
Save();
}
public static bool CompareProxy(ProxyData proxyData1, ProxyData proxyData2)
{
return proxyData1.ToString(FORMAT) == proxyData2.ToString(FORMAT);
{
return proxyData1.Address == proxyData2.Address && proxyData1.Port == proxyData2.Port;
}
@@ -79,16 +89,21 @@ public static class ProxyStorage
File.WriteAllText("proxies.json", json);
}
public static string GetProxyString(ProxyData proxyData)
{
return proxyData.AuthEnabled ? proxyData.ToString(FORMAT) : proxyData.ToString(ADDRESS_FORMAT);
}
private static Proxies Create()
{
int? def = null;
if (MaClient.DefaultProxy != null)
{
var search = Proxies.FirstOrDefault(p => p.Value.Equals(MaClient.DefaultProxy));
if (search.Value != null!)
{
def = search.Key;
}
var search = Proxies.FirstOrDefault(p => p.Value.Equals(MaClient.DefaultProxy));
if (search.Value != null!)
{
def = search.Key;
}
}
return new Proxies
@@ -97,7 +112,8 @@ public static class ProxyStorage
DefaultProxy = def
};
}
}
public class Proxies
+4 -3
View File
@@ -16,7 +16,7 @@ public static class SessionHandler
string? password = null;
try
{
if (!string.IsNullOrWhiteSpace(mafile.Password))
if (PHandler.IsPasswordSet && !string.IsNullOrWhiteSpace(mafile.Password))
{
password = PHandler.Decrypt(mafile.Password);
}
@@ -31,8 +31,9 @@ public static class SessionHandler
{
return await func();
}
catch (SessionExpiredException) when (mafile.SessionData is not { RefreshToken.IsExpired: true})
catch (SessionInvalidException) when (mafile.SessionData is { RefreshToken.IsExpired: false})
{
Shell.Logger.Debug("Token on mafile {name} {steamid} expired. Trying to refresh", mafile.AccountName, mafile.SessionData?.SteamId);
refreshed = await TryRefresh(mafile);
}
catch (SessionInvalidException)
@@ -82,7 +83,7 @@ public static class SessionHandler
try
{
await MaClient.RefreshSession(mafile);
SnackbarController.SendSnackbar("Сессия была обновлена автоматически");
SnackbarController.SendSnackbar(LocManager.GetCodeBehindOrDefault("SessionWasRefreshedAutomatically", "SessionHandler", "SessionWasRefreshedAutomatically"));
return true;
}
catch (SessionInvalidException)
+1 -1
View File
@@ -22,7 +22,7 @@ public partial class Settings : ObservableObject
[ObservableProperty] private LocalizationLanguage _language = LocalizationLanguage.English;
[ObservableProperty] private bool _legacyMode = true;
[ObservableProperty] private bool _allowAutoUpdate;
[ObservableProperty] private bool _useAccountNameAsMafileName;
#endregion
public static Settings Instance { get; }
+122 -25
View File
@@ -1,15 +1,17 @@
using NebulaAuth.Model.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SteamLib;
using SteamLib.Account;
using SteamLib.Exceptions;
using SteamLib.SteamMobile;
using SteamLib.Utility.MaFiles;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.DirectoryServices.ActiveDirectory;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using SteamLib.Exceptions;
namespace NebulaAuth.Model;
@@ -23,6 +25,7 @@ public static class Storage
public static ObservableCollection<Mafile> MaFiles { get; } = new();
public static readonly int DuplicateFound;
static Storage()
{
if (Directory.Exists(MafileFolder) == false)
@@ -36,12 +39,25 @@ public static class Storage
}
var files = Directory.GetFiles(MafileFolder);
foreach (var file in files)
var hashNames = new HashSet<string>();
var hashIds = new HashSet<SteamId>();
var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
var ordered = files.Order(comparer).ToList();
foreach (var file in ordered)
{
if (Path.GetExtension(file).Equals(".mafile", StringComparison.InvariantCultureIgnoreCase) == false) continue;
if (Path.GetExtension(file).Equals(".mafile", StringComparison.OrdinalIgnoreCase) == false) continue;
try
{
var data = ReadMafile(file);
if (hashNames.Contains(data.AccountName) || (data.SessionData != null && hashIds.Contains(data.SessionData.SteamId)))
{
DuplicateFound++;
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
continue;
}
hashNames.Add(data.AccountName);
if (data.SessionData != null) hashIds.Add(data.SessionData.SteamId);
MaFiles.Add(data);
}
catch (Exception ex)
@@ -85,14 +101,14 @@ public static class Storage
{
Data = { { "mafile", data } }
};
if (overwrite == false && File.Exists(GetMafilePath(data)))
if (overwrite == false && File.Exists(CreatePathForMafile(data)))
{
throw new IOException("File already exist and overwrite is False");
}
SaveMafile(data);
}
@@ -113,21 +129,25 @@ public static class Storage
}
public static string SerializeMafile(Mafile data)
{
var props = new Dictionary<string, object?>
{
{nameof(Mafile.Proxy), data.Proxy},
{nameof(Mafile.Group), data.Group},
{nameof(Mafile.Password), data.Password}
};
return SerializeMafile(data, props);
}
public static string SerializeMafile(MobileDataExtended data, Dictionary<string, object?>? properties)
{
if (Settings.Instance.LegacyMode)
{
var props = new Dictionary<string, object?>
{
{nameof(Mafile.Proxy), data.Proxy},
{nameof(Mafile.Group), data.Group},
{nameof(Mafile.Password), data.Password}
};
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, props);
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties);
}
else
{
return MafileSerializer.Serialize(data);
return MafileSerializer.Serialize(data);
}
}
@@ -149,8 +169,7 @@ public static class Storage
public static void SaveMafile(Mafile data)
{
var path = GetMafilePath(data);
var path = CreatePathForMafile(data);
var str = SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
@@ -168,14 +187,14 @@ public static class Storage
public static void UpdateMafile(Mafile data)
{
var path = GetMafilePath(data);
var path = CreatePathForMafile(data);
var str = SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
}
public static void RemoveMafile(Mafile data)
{
var path = GetMafilePath(data);
var path = CreatePathForMafile(data);
if (File.Exists(path))
{
File.Delete(path);
@@ -184,7 +203,7 @@ public static class Storage
}
public static void MoveToRemoved(Mafile data)
{
var path = GetMafilePath(data);
var path = CreatePathForMafile(data);
var copyPath = Path.Combine(REMOVED_F, data.SessionData!.SteamId + ".mafile");
var copyPathCompleted = copyPath;
var i = 0;
@@ -197,14 +216,92 @@ public static class Storage
File.Delete(path);
MaFiles.Remove(data);
}
private static string GetMafilePath(Mafile data)
private static string CreatePathForMafile(Mafile data)
{
if (data.SessionData == null)
throw new NullReferenceException("SessionData was null can't retrieve SteamId");
var fileName = data.SessionData.SteamId + ".mafile";
string fileName;
if (Settings.Instance.UseAccountNameAsMafileName)
{
fileName = CreateFileNameWithAccountName(data.AccountName);
}
else
{
if(data.SessionData == null)
throw new NullReferenceException("SessionData was null can't retrieve SteamId"); //FIXME: think about better way to handle
fileName = CreateFileNameWithSteamId(data.SessionData.SteamId);
}
return Path.Combine(MafileFolder, fileName);
}
private static string CreateFileNameWithAccountName(string accountName) => accountName + ".mafile";
private static string CreateFileNameWithSteamId(SteamId steamId) => steamId.Steam64.Id + ".mafile";
public static string? TryFindMafilePath(Mafile data)
//FIXME: write mode to mafile instead of searching it. Search sometimes represents not actual mafile (in case of duplicate steamId + accountName)
{
var pathFileName = Path.Combine(MafileFolder, CreateFileNameWithAccountName(data.AccountName));
string? pathSteamId = null;
if (data.SessionData != null)
{
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SessionData.SteamId));
}
var steamIdExist = pathSteamId != null && File.Exists(pathSteamId);
var accountNameExist = File.Exists(pathFileName);
if (steamIdExist && accountNameExist)
{
return Settings.Instance.UseAccountNameAsMafileName ? pathFileName : pathSteamId;
}
if (steamIdExist ^ accountNameExist)
{
return steamIdExist ? pathSteamId : pathFileName;
}
return null;
}
public static bool ValidateCanSave(Mafile data)
{
return Settings.Instance.UseAccountNameAsMafileName || data.SessionData != null;
}
}
internal class MafileNameComparer : IComparer<string>
{
public bool MafileNameMode { get; }
private const string MAF_64_START = "765";
private static readonly IComparer<string> _defaultComparer = Comparer<string>.Default;
public MafileNameComparer(bool mafileNameMode)
{
MafileNameMode = mafileNameMode;
}
public int Compare(string? x, string? y)
{
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
bool xisSteamId = Path.GetFileName(x).StartsWith(MAF_64_START);
bool yisSteamId = Path.GetFileName(y).StartsWith(MAF_64_START);
if (xisSteamId ^ yisSteamId)
{
if (MafileNameMode)
{
return xisSteamId ? 1 : -1;
}
else
{
return yisSteamId ? 1 : -1;
}
}
return _defaultComparer.Compare(x, y);
}
}
+12 -9
View File
@@ -8,25 +8,24 @@
<UseWindowsForms>true</UseWindowsForms>
<LangVersion>latest</LangVersion>
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
<ApplicationIcon>Theme\nebula lock.ico</ApplicationIcon>
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.4.4</AssemblyVersion>
<AssemblyVersion>1.5.0</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Theme\Background.jpg" />
<None Remove="Theme\nebula lock.ico" />
<None Remove="Theme\nebula.ico" />
<None Remove="Theme\lock.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.4" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.5" />
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.0" />
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.0" />
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
<PackageReference Include="MaterialDesignExtensions" Version="4.0.0-a02" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
<PackageReference Include="NLog" Version="5.2.8" />
@@ -37,13 +36,11 @@
<Resource Include="Theme\Background.jpg">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
<Resource Include="Theme\nebula lock.ico" />
<Resource Include="Theme\nebula.ico" />
<Resource Include="Theme\lock.ico" />
</ItemGroup>
<ItemGroup>
<Folder Include="Model\Exceptions\" />
<Folder Include="ViewModel\Other\" />
<Folder Include="Utility\" />
</ItemGroup>
@@ -51,6 +48,12 @@
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="View\Dialogs\LoginAgainOnImportDialog.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 KiB

+7 -10
View File
@@ -99,14 +99,12 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0"></materialDesign:PackIcon>
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="32" Height="32" Source="{Binding ItemImageUri}" Margin="0,0,10,0"></Image>
<Border Grid.Column="1" Background="{DynamicResource MaterialDesignPaper}" MaxHeight="36" HorizontalAlignment="Center" BorderBrush="{DynamicResource PrimaryHueMidBrush}" BorderThickness="0.6" Margin="0,0,10,0">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="32" MaxHeight="32" Source="{Binding ItemImageUri}" ></Image>
</Border>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True" TextWrapping="WrapWithOverflow" Text="{Binding ItemName}" >
</TextBlock>
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True" TextWrapping="WrapWithOverflow" Text="{Binding ItemName}"/>
<TextBlock Grid.Row="1" Foreground="LightGray" Text="{Binding PriceString}">
<TextBlock.FontSize>
<Binding ElementName="ItemName" Path="FontSize" ConverterParameter="0.7">
@@ -166,10 +164,9 @@
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0"></materialDesign:PackIcon>
<TextBlock VerticalAlignment="Center" Grid.Column="1">
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market}"/>
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market, IsDynamic=False}"/>
<Run Text="{Binding Confirmations.Count, Mode=OneWay}"/>
<Run> </Run>
<Run Text="{Tr Common.Abbreviations.Count, IsDynamic=False}"/>
<Run Text="{Tr Common.Abbreviations.Count.Items, IsDynamic=False}"/>
</TextBlock>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
+12 -11
View File
@@ -7,6 +7,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:model="clr-namespace:NebulaAuth.Model"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
mc:Ignorable="d"
d:DesignHeight="250" d:DesignWidth="800"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
@@ -15,25 +16,25 @@
Background="{DynamicResource WindowBackground}">
<Grid MinHeight="100" MinWidth="300" Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left">
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<CheckBox IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Margin="10" Grid.Row="2" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
<Grid Grid.Row="3">
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<CheckBox Grid.Row="2" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
<Grid Grid.Row="3" Margin="10,10,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button IsDefault="True" Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
<Button IsCancel="True" Grid.Column="1" Margin="5,5,10,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}"/>
<Button IsDefault="True" IsEnabled="{Binding IsFormValid}" Margin="0,0,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
<Button IsCancel="True" Grid.Column="1" Margin="5,0,0,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}"/>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,53 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.LoginAgainOnImportDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:model="clr-namespace:NebulaAuth.Model"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
mc:Ignorable="d"
d:DesignHeight="250" d:DesignWidth="800"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
Foreground="WhiteSmoke" Cursor="Hand"
d:DataContext="{d:DesignInstance other:LoginAgainOnImportVM}"
Background="{DynamicResource WindowBackground}">
<Grid MinHeight="100" MinWidth="300" Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left">
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<ComboBox ToolTip="{Tr LoginAgainDialog.ProxyToolTip}" Grid.Row="2" Margin="10,18,10,0" materialDesign:HintAssist.Hint="{Tr Common.Proxy}" ItemsSource="{Binding Proxies}" SelectedItem="{Binding SelectedProxy}" >
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<UIElement.InputBindings>
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
</UIElement.InputBindings>
</ComboBox>
<CheckBox Grid.Row="3" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
<CheckBox Grid.Row="4" Margin="10,10,10,0" IsEnabled="{Binding MafileHasProxy}" Content="{Tr LoginAgainDialog.UseMafileProxy}" IsChecked="{Binding UseMafileProxy}"/>
<Grid Grid.Row="5" Margin="10,10,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button IsEnabled="{Binding IsFormValid}" IsDefault="True" Margin="0,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
<Button IsCancel="True" Grid.Column="1" Margin="5,5,0,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}"/>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs
{
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainOnImportDialog
{
public LoginAgainOnImportDialog()
{
InitializeComponent();
}
}
}
+41 -26
View File
@@ -16,12 +16,12 @@
d:DataContext="{d:DesignInstance other:ProxyManagerVM}"
Background="{DynamicResource WindowBackground}">
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<Style TargetType="UserControl">
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid Margin="15,10,15,15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
@@ -29,39 +29,39 @@
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}"/>
<Button IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static md:DialogHost.CloseDialogCommand}">
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></md:PackIcon>
</Button>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="10" FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}"/>
<Button Margin="0,0,10,0" IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static md:DialogHost.CloseDialogCommand}">
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></md:PackIcon>
</Button>
</Grid>
<Separator Grid.Row="1"></Separator>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch"
Margin="15" FontSize="16">
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}"/>
<Run Text="{Binding DefaultProxy.Key, StringFormat='&#x0a;0:', FallbackValue='X', Mode=OneWay}"/>
<Run Text="{Binding DefaultProxy.Value.Address, Mode=OneWay, FallbackValue=''}"/>
<Run Text="{Binding DefaultProxy.Key, StringFormat='&#x0a;0:', FallbackValue='-', Mode=OneWay}"/>
<Run Text="{Binding DefaultProxy.Value, Converter='{StaticResource ProxyDataTextConverter}', Mode=OneWay, FallbackValue=''}"/>
</TextBlock>
<Button Grid.Column="1" Command="{Binding SetDefaultCommand}">
<md:PackIcon Kind="HeartBoxOutline" Width="20" Height="20"></md:PackIcon>
</Button>
<Button Grid.Column="2" Command="{Binding RemoveDefaultCommand}" Cursor="Hand">
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}" Cursor="Hand">
<md:PackIcon Kind="ClearBox" Width="20" Height="20"></md:PackIcon>
</Button>
</Grid>
<md:Card Grid.Row="3" Margin="10">
<ListBox SelectedValue="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
<ListBox VirtualizingStackPanel.VirtualizationMode="Recycling" FontSize="14" SelectedValue="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
<ListBox.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DataContext.RemoveProxyCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</ListBox.InputBindings>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DataContext}"/>
@@ -77,22 +77,37 @@
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Key, Mode=OneWay}"/><Run Text=": "/>
<Run Text="{Binding Value.Address, Mode=OneWay}"/>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">
<Run Text="{Binding Key, Mode=OneWay}"/><Run Text=": "/>
<Run Text="{Binding Value, Mode=OneWay, Converter={StaticResource ProxyDataTextConverter}}"/>
</TextBlock>
</TextBlock>
<Button Style="{StaticResource MaterialDesignIconButton}" Padding="0" Width="24" Height="24" md:RippleAssist.IsDisabled="True" Grid.Column="1" HorizontalAlignment="Right"
Command="{Binding DataContext.SetDefaultCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}">
<md:PackIcon Height="16" Width="16" Kind="Heart"></md:PackIcon>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</md:Card>
<Grid Grid.Row="4">
<Grid Grid.Row="4" Margin="5,0,15,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
+8 -5
View File
@@ -44,10 +44,10 @@
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16" ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding BackgroundMode}" materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" ></ComboBox>
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16" ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" ></ComboBox>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding DisableTimersOnChange}" Content="{Tr SettingsDialog.DisableTimersOnSwitch}"/>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}"/>
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding UseBackground}" Content="{Tr SettingsDialog.UseCustomColor}"/>
<CheckBox Margin="0,10,0,5" FontSize="16" IsChecked="{Binding UseBackground}" Content="{Tr SettingsDialog.UseCustomColor}"/>
<materialDesign:ColorPicker Height="100" IsEnabled="{Binding UseBackground}" Color="{Binding BackgroundColor, Delay=50}" />
<Grid>
@@ -61,8 +61,11 @@
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}"></PasswordBox>
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom" Grid.Column="1"> <materialDesign:PackIcon Kind="ContentSave"/></Button>
</Grid>
<CheckBox Margin="0,25,0,0" FontSize="16" IsChecked="{Binding LegacyMode}" Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}"/>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding LegacyMode}" Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseAccountNameAsMafileName}" >
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
</CheckBox>
</StackPanel>
+58
View File
@@ -0,0 +1,58 @@
<UserControl x:Class="NebulaAuth.View.UpdaterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NebulaAuth.View"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
d:DesignHeight="700"
d:DesignWidth="500"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
d:DataContext="{d:DesignInstance other:UpdaterVM}"
Background="{DynamicResource WindowBackground}"
Padding="10"
MaxWidth="500">
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock FontSize="24" Text="Обновления"/>
<Separator Grid.Row="1"></Separator>
<TextBlock FontSize="16" Margin="5,10,0,10" TextWrapping="WrapWithOverflow" Grid.Row="2" >
<Run Text="Доступна новая версия:"/>
<Run FontWeight="Bold" Text="{Binding UpdateInfoEventArgs.CurrentVersion}"/>
<Run Text="&#x0a;Вы используете версию"/>
<Run FontWeight="Bold" Text="{Binding UpdateInfoEventArgs.InstalledVersion}"/>
<Run Text="Хотите обновить программу?"/>
</TextBlock>
<Expander Header="Что изменилось?" Grid.Row="3">
<!--<wpf:WebView2 HorizontalAlignment="Stretch" Height="300"
Source="{Binding UpdateInfoEventArgs.ChangelogURL}"
/>-->
</Expander>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="OK" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" />
<Button IsCancel="True" Grid.Column="1" Content="Cancel" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" />
</Grid>
</Grid>
</UserControl>
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace NebulaAuth.View
{
/// <summary>
/// Логика взаимодействия для UpdaterView.xaml
/// </summary>
public partial class UpdaterView : UserControl
{
public UpdaterView()
{
InitializeComponent();
}
}
}
+11 -2
View File
@@ -23,6 +23,8 @@ public partial class MainVM : ObservableObject
private ObservableCollection<Mafile> _maFiles = Storage.MaFiles;
public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
public Mafile? SelectedMafile
{
get => _selectedMafile;
@@ -42,9 +44,15 @@ public partial class MainVM : ObservableObject
SessionHandler.LoginStarted += SessionHandlerOnLoginStarted;
SessionHandler.LoginCompleted += SessionHandlerOnLoginCompleted;
UpdateManager.CheckForUpdates();
if (Storage.DuplicateFound > 0)
{
SnackbarController.SendSnackbar(
GetLocalizationOrDefault("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
TimeSpan.FromSeconds(4));
}
}
private void SetMafile(Mafile? mafile)
{
if (mafile != SelectedMafile)
@@ -73,6 +81,7 @@ public partial class MainVM : ObservableObject
{
return;
}
var password = loginAgainVm.Password;
var waitDialog = new WaitLoginDialog();
var wait = DialogHost.Show(waitDialog);
@@ -138,7 +147,7 @@ public partial class MainVM : ObservableObject
{
if (await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmRemovingAuthenticator")))
{
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(SelectedMafile), SelectedMafile);
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(selectedMafile), selectedMafile);
SnackbarController.SendSnackbar(
result.Success ? GetLocalizationOrDefault("AuthenticatorRemoved") : GetLocalizationOrDefault("AuthenticatorNotRemoved"));
+58 -24
View File
@@ -15,6 +15,8 @@ using System.Windows;
using AchiesUtilities.Extensions;
using NebulaAuth.Model.Entities;
using SteamLib.Exceptions;
using NebulaAuth.Utility;
using NebulaAuth.View.Dialogs;
namespace NebulaAuth.ViewModel;
@@ -25,19 +27,29 @@ public partial class MainVM //File //TODO: Refactor
[RelayCommand]
private void OpenMafileFolder()
{ var mafile = SelectedMafile;
{
var mafile = SelectedMafile;
var path = Storage.MafileFolder;
if (mafile?.SessionData != null)
string? mafilePath = null;
if (mafile != null)
{
var mafPath = Path.Combine(Storage.MafileFolder, mafile.SessionData.SteamId.Steam64 + ".maFile");
if (File.Exists(mafPath))
{
path = $"/e, /select, \"{mafPath}\""; ;
}
mafilePath = Storage.TryFindMafilePath(mafile);
}
if (mafilePath != null)
{
path = $"/select, \"{mafilePath}\""; ;
}
Process.Start("explorer", path);
try
{
var processStartInfo = new ProcessStartInfo("explorer.exe", path);
Process.Start(processStartInfo);
}
catch (Exception ex)
{
SnackbarController.SendSnackbar(ex.Message);
}
}
@@ -127,24 +139,46 @@ public partial class MainVM //File //TODO: Refactor
private async Task<bool> HandleAddMafileWithoutSession(Mafile data)
{
var oldMafile = SelectedMafile;
SelectedMafile = data;
await LoginAgain();
var loginAgainVm = await DialogsController.ShowLoginAgainOnImportDialog(data, Proxies);
if (loginAgainVm == null)
{
return false;
}
var password = loginAgainVm.Password;
if (!loginAgainVm.UseMafileProxy)
{
data.Proxy = loginAgainVm.SelectedProxy;
}
var waitDialog = new WaitLoginDialog();
var wait = DialogHost.Show(waitDialog);
try
{
await MaClient.LoginAgain(data, password, loginAgainVm.SavePassword, waitDialog);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
}
catch (LoginException ex)
{
SnackbarController.SendSnackbar(ErrorTranslatorHelper.TranslateLoginError(ex.Error), TimeSpan.FromSeconds(1.5));
}
catch (Exception ex)
when (ExceptionHandler.Handle(ex))
{
Shell.Logger.Error(ex);
}
finally
{
DialogsController.CloseDialog();
await wait;
}
var result = data.SessionData != null;
if (result)
if (!result) return result;
Storage.SaveMafile(data);
{
var existed = MaFiles.FirstOrDefault(m => m.AccountName == data.AccountName); //TODO: more elegant way to handle overwrite
if (existed != null)
{
MaFiles.Remove(existed);
}
MaFiles.Add(data);
ResetQuery();
SearchText = data.AccountName ?? string.Empty;
SelectedMafile = data;
}
else
{
SelectedMafile = oldMafile;
}
} //As this operation used only for 1 mafile at time, we can safely assume that we can select it for convenience
return result;
}
+24 -10
View File
@@ -5,6 +5,7 @@ using NebulaAuth.Model.Entities;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace NebulaAuth.ViewModel;
@@ -20,7 +21,7 @@ public partial class MainVM //Groups
set
{
if (SetProperty(ref _selectedGroup, value))
PerformSearch();
PerformQuery();
}
}
@@ -32,7 +33,7 @@ public partial class MainVM //Groups
set
{
if(SetProperty(ref _searchText, value))
PerformSearch();
PerformQuery();
}
}
@@ -47,6 +48,8 @@ public partial class MainVM //Groups
var mafile = SelectedMafile;
if (mafile == null) return;
if (string.IsNullOrEmpty(value)) return;
if (!ValidateCanSaveAndWarn(mafile)) return;
mafile.Group = value;
Storage.UpdateMafile(mafile);
QueryGroups();
@@ -60,14 +63,16 @@ public partial class MainVM //Groups
{
if (value == null) return;
if (value.Length < 2) return;
var group = (string?)value[0];
var mafile = (Mafile?)value[1];
if (group == null || mafile == null) return;
if (!ValidateCanSaveAndWarn(mafile)) return;
mafile.Group = group;
Storage.UpdateMafile(mafile);
QueryGroups();
PerformSearch();
PerformQuery();
OnPropertyChanged(nameof(SelectedMafile));
}
@@ -75,6 +80,7 @@ public partial class MainVM //Groups
private void RemoveGroup(Mafile? mafile)
{
if (mafile?.Group == null) return;
if (!ValidateCanSaveAndWarn(mafile)) return;
var mafGroup = mafile.Group;
mafile.Group = null;
Storage.UpdateMafile(mafile);
@@ -84,7 +90,7 @@ public partial class MainVM //Groups
{
SelectedGroup = null;
}
PerformSearch();
PerformQuery();
}
@@ -99,7 +105,10 @@ public partial class MainVM //Groups
Groups = new ObservableCollection<string>(groups!);
}
private void PerformSearch()
private void PerformQuery()
{
if (string.IsNullOrWhiteSpace(SelectedGroup) && string.IsNullOrWhiteSpace(SearchText))
{
@@ -122,10 +131,9 @@ public partial class MainVM //Groups
}
var perform = query.ToList();
MaFiles = new ObservableCollection<Mafile>(perform);
if (SelectedMafile != null && !MaFiles.Contains(SelectedMafile))
{
SelectedMafile = MaFiles.FirstOrDefault();
}
SelectedMafile = MaFiles.FirstOrDefault();
return;
bool SearchPredicate(Mafile mafile)
{
if (!mafile.AccountName.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
@@ -135,5 +143,11 @@ public partial class MainVM //Groups
return true;
}
}
private void ResetQuery()
{
_selectedGroup = null;
_searchText = string.Empty;
PerformQuery();
}
}
+13
View File
@@ -98,7 +98,9 @@ public partial class MainVM
[RelayCommand]
private void RemoveProxy()
{
if(SelectedProxy == null) return;
if (SelectedMafile == null) return;
if (!ValidateCanSaveAndWarn(SelectedMafile)) return;
SelectedMafile.Proxy = null;
SelectedProxy = null;
Storage.UpdateMafile(SelectedMafile);
@@ -114,8 +116,19 @@ public partial class MainVM
}
if (SelectedMafile == null) return;
if (!ValidateCanSaveAndWarn(SelectedMafile)) return;
ProxyExist = true;
SelectedMafile.Proxy = SelectedProxy;
Storage.UpdateMafile(SelectedMafile);
}
private bool ValidateCanSaveAndWarn(Mafile data)
{
var canSave = Storage.ValidateCanSave(data);
if (!canSave)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRetrieveSteamIDToUpdate"));
}
return canSave;
}
}
+1
View File
@@ -136,5 +136,6 @@ public partial class MainVM //Timer
_timerCheckSeconds = value;
OnPropertyChanged(nameof(TimerCheckSeconds));
_confirmTimer.Change(value * 1000, value * 1000);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("TimerChanged"));
}
}
+3 -3
View File
@@ -26,6 +26,7 @@ using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using NebulaAuth.Core;
using SteamLib.Utility.MaFiles;
namespace NebulaAuth.ViewModel.Other;
@@ -140,7 +141,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
IsFieldVisible = false;
HintText = string.Empty;
_sessionData = (MobileSessionData)await LoginV2Executor.DoLogin(_loginV2ExecutorOptions, userName, pass);
Handler.CookieContainer.SetSteamMobileCookies(_sessionData);
Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(_sessionData);
IsEmailCode = true;
}
catch (EResultException ex)
@@ -326,8 +327,7 @@ public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNum
{
Directory.CreateDirectory("mafiles_backup");
}
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
var json = Storage.SerializeMafile(data, null);
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"), json);
}
@@ -0,0 +1,64 @@
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Model.Entities;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
namespace NebulaAuth.ViewModel.Other;
public partial class LoginAgainOnImportVM : ObservableObject
{
public ObservableCollection<MaProxy> Proxies { get; } = new();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsFormValid))]
private string _password = null!;
[ObservableProperty] private bool _savePassword;
[ObservableProperty] private string _userName = null!;
[ObservableProperty] private bool _mafileHasProxy;
public MaProxy? SelectedProxy
{
get => _selectedProxy;
set
{
if (SetProperty(ref _selectedProxy, value) && value != null)
{
UseMafileProxy = false;
}
}
}
public bool UseMafileProxy
{
get => _useMafileProxy;
set
{
if (SetProperty(ref _useMafileProxy, value) && value)
{
SelectedProxy = null;
}
}
}
public bool IsFormValid => !string.IsNullOrWhiteSpace(Password);
private MaProxy? _selectedProxy;
private bool _useMafileProxy;
public LoginAgainOnImportVM(Mafile mafile, IEnumerable<MaProxy> proxies)
{
UserName = mafile.AccountName;
MafileHasProxy = mafile.Proxy != null;
UseMafileProxy = MafileHasProxy;
Proxies = new(proxies);
}
public LoginAgainOnImportVM()
{ }
[RelayCommand]
private void RemoveProxy()
{
SelectedProxy = null;
}
}
+21 -4
View File
@@ -1,10 +1,27 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.ViewModel.Other;
public partial class LoginAgainVM : ObservableObject
{
[ObservableProperty] private string _password = null!;
[ObservableProperty] private bool _savePassword;
[ObservableProperty] private string _userName = null!;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsFormValid))]
private string _password = null!;
[ObservableProperty]
private bool _savePassword;
[ObservableProperty]
private string _userName = null!;
public bool IsFormValid => !string.IsNullOrWhiteSpace(Password);
public LoginAgainVM()
{ }
}
+45 -21
View File
@@ -1,5 +1,7 @@
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using AutoUpdaterDotNET;
using CodingSeb.Localization.WPF;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
@@ -40,7 +42,7 @@ public partial class ProxyManagerVM : ObservableObject
var idPresent = (bool?)null;
var proxies = new List<KeyValuePair<int?, ProxyData>>();
var i = 0;
foreach (var str in split)
foreach (var str in split.Where(s => string.IsNullOrWhiteSpace(s) == false))
{
i++;
int? id = null;
@@ -56,9 +58,8 @@ public partial class ProxyManagerVM : ObservableObject
try
if (ProxyStorage.DefaultScheme.TryParse(str, out var proxy))
{
var proxy = ProxyData.Parse(str, ProxyStorage.FORMAT);
if (id != null && proxies.Any(kvp => kvp.Key == id))
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
@@ -66,7 +67,7 @@ public partial class ProxyManagerVM : ObservableObject
}
proxies.Add(new KeyValuePair<int?, ProxyData>(id, proxy));
}
catch (FormatException)
else
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
return;
@@ -88,20 +89,15 @@ public partial class ProxyManagerVM : ObservableObject
input = IdRegex.Replace(input, "");
}
ProxyData data;
try
if (ProxyStorage.DefaultScheme.TryParse(input, out var data))
{
data = ProxyData.Parse(input, ProxyStorage.FORMAT);
ProxyStorage.SetProxy(id, data);
}
catch (FormatException)
else
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
return;
}
ProxyStorage.SetProxy(id, data);
}
AddProxyField = string.Empty;
CheckIfDefaultProxyStay();
@@ -117,17 +113,37 @@ public partial class ProxyManagerVM : ObservableObject
[RelayCommand]
private void RemoveProxy()
{
if (SelectedProxy == null) return;
ProxyStorage.RemoveProxy(SelectedProxy.Value.Key);
var selected = SelectedProxy;
if (selected == null) return;
var s = selected.Value;
KeyValuePair<int, ProxyData>? nextNeighbor = null;
KeyValuePair<int, ProxyData>? prevNeighbor = null;
foreach (var id in Proxies.Keys.Order())
{
if (id < s.Key)
{
prevNeighbor = KeyValuePair.Create(id, Proxies[id]);
}
else if (id > s.Key)
{
nextNeighbor = KeyValuePair.Create(id, Proxies[id]);
break;
}
}
ProxyStorage.RemoveProxy(s.Key);
SelectedProxy = nextNeighbor ?? prevNeighbor;
CheckIfDefaultProxyStay();
}
[RelayCommand]
private void SetDefault()
private void SetDefault(object? arg)
{
if (SelectedProxy == null) return;
DefaultProxy = SelectedProxy;
MaClient.DefaultProxy = SelectedProxy.Value.Value;
if (arg is not KeyValuePair<int, ProxyData> proxy) return;
DefaultProxy = proxy;
MaClient.DefaultProxy = proxy.Value;
ProxyStorage.Save();
}
@@ -140,10 +156,18 @@ public partial class ProxyManagerVM : ObservableObject
}
[RelayCommand]
private void CopyProxy(ProxyData? obj)
private void CopyProxy(ProxyData? data)
{
if (obj == null) return;
Clipboard.SetText(obj.ToString(ProxyStorage.FORMAT));
if (data == null) return;
try
{
Clipboard.SetText(ProxyStorage.GetProxyString(data));
}
catch (Exception ex)
{
Shell.Logger.Error(ex);
}
}
[RelayCommand]
+6
View File
@@ -109,6 +109,12 @@ public partial class SettingsVM : ObservableObject
set => Settings.AllowAutoUpdate = value;
}
public bool UseAccountNameAsMafileName
{
get => Settings.UseAccountNameAsMafileName;
set => Settings.UseAccountNameAsMafileName = value;
}
[ObservableProperty] private string _password;
+15
View File
@@ -0,0 +1,15 @@
using AutoUpdaterDotNET;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.VisualBasic.FileIO;
namespace NebulaAuth.ViewModel.Other;
public partial class UpdaterVM : ObservableObject
{
public UpdateInfoEventArgs UpdateInfoEventArgs { get; }
public UpdaterVM(UpdateInfoEventArgs args)
{
UpdateInfoEventArgs = args;
}
}
+87 -15
View File
@@ -35,6 +35,11 @@
"ru": "Отменено",
"ua": "Скасовано"
},
"Proxy": {
"en": "Proxy",
"ru": "Прокси",
"ua": "Проксі"
},
"Abbreviations": {
"Time": {
"Seconds": {
@@ -53,6 +58,23 @@
}
},
"MainWindow": {
"Global": {
"DragNDropHint": {
"en": "Release to import mafiles",
"ru": "Отпустите для импорта мафайлов",
"ua": "Відпустіть для імпорту мафайлів"
},
"LoadingHint": {
"en": "Loading...",
"ru": "Загрузка...",
"ua": "Завантаження..."
},
"StartTip": {
"ru": "Чтобы начать пользоваться программой вы можете привязать аккаунт через меню \"Аккаунт\", либо импортировать существующие мафайлы одним из способов:\n1. Скопировать их в папку mafiles и перезапустить приложение\n2. Перетянуть файлы прямо в окно программы\n3. Скопировать файлы и нажать CTRL+V в окне программы\n4. Через меню \"Файл\" - \"Импорт\"",
"en": "To start using the program, you can link an account through the \"Account\" menu, or import existing mafiles in one of the following ways:\n1. Copy them to the mafiles folder and restart the application\n2. Drag files directly into the program window\n3. Copy files and press CTRL+V in the program window\n4. Through the \"File\" - \"Import\" menu",
"ua": "Щоб почати користуватися програмою, ви можете прив'язати акаунт через меню \"Акаунт\", або імпортувати існуючі мафайли одним із способів:\n1. Скопіювати їх у папку mafiles та перезапустити програму\n2. Перетягнути файли безпосередньо в вікно програми\n3. Скопіювати файли та натиснути CTRL+V в вікні програми\n4. Через меню \"Файл\" - \"Імпорт\""
}
},
"Menu": {
"File": {
"Caption": {
@@ -85,7 +107,7 @@
"Caption": {
"en": "Account",
"ru": "Аккаунт",
"ua": "Аккаунт"
"ua": "Акаунт"
},
"Link": {
"en": "Link",
@@ -115,6 +137,11 @@
"ru": "Группы",
"ua": "Групи"
},
"GroupToolTip": {
"ru": "Введите новую группу и нажмите Enter. Управление группой - ПКМ на аккаунте",
"en": "Enter new group and press Enter. Group management - RMB on account",
"ua": "Введіть нову групу і натисніть Enter. Управління групою - ПКМ на акаунті"
},
"Proxy": {
"ProxyHint": {
"en": "Proxy",
@@ -126,6 +153,11 @@
"ru": "Открыть менеджер прокси",
"ua": "Відкрити менеджер проксі"
},
"ProxyManipulateToolTip": {
"en": "Right-click to open menu. DEL to remove",
"ru": "Правый клик для открытия меню. DEL для удаления",
"ua": "Правий клік для відкриття меню. DEL для видалення"
},
"ProxyAlert": {
"DefaultInUse": {
"en": "Default proxy is in use",
@@ -139,7 +171,6 @@
}
}
},
"TradeTimerHint": {
"en": "Trade",
"ru": "Трейд",
@@ -174,7 +205,7 @@
"Account": {
"en": "Account: ",
"ru": "Аккаунт: ",
"ua": "Аккаунт: "
"ua": "Акаунт: "
},
"Group": {
"en": "Group: ",
@@ -267,7 +298,7 @@
"DisableTimersOnSwitch": {
"en": "Disable timers on account switch",
"ru": "Отключать таймеры при смене аккаунта",
"ua": "Вимикати таймери при зміні аккаунта"
"ua": "Вимикати таймери при зміні акаунта"
},
"MinimizeToTray": {
"en": "Minimize to tray",
@@ -292,8 +323,8 @@
},
"Hint": {
"en": "Doesn't saved on disk",
"ru": "Не сохраняется в файл",
"ua": "Не зберігається у файлах"
"ru": "Не сохраняется в системе",
"ua": "Не зберігається у системі"
}
},
"LegacyMafileMode": {
@@ -310,6 +341,11 @@
"en": "Allow auto update",
"ru": "Разрешить автообновление",
"ua": "Дозволити автооновлення"
},
"UseAccountName": {
"en": "Use account name on mafiles",
"ru": "Использовать имя аккаунта на мафайлах",
"ua": "Використовувати ім'я акаунта на мафайлах"
}
},
"LoginAgainDialog": {
@@ -342,7 +378,17 @@
"en": "Save encrypted password to mafile",
"ru": "Сохранить зашифрованный пароль в мафайл",
"ua": "Зберегти зашифрований пароль в мафайлі"
}
},
"UseMafileProxy": {
"en": "Use mafile proxy",
"ru": "Использовать прокси из мафайла",
"ua": "Використовувати проксі з мафайла"
},
"ProxyToolTip": {
"en": "Press 'DEL' to remove",
"ru": "Нажмите 'DEL' для удаления",
"ua": "Натисніть 'DEL' для видалення"
}
},
"ProxyManagerDialog": {
"Title": {
@@ -354,8 +400,12 @@
"en": "Default proxy:",
"ru": "Прокси по умолчанию:",
"ua": "Проксі за замовчуванням:"
},
"UseRandomDefaultProxy": {
"en": "Use random default proxy",
"ru": "Cлучайный прокси по умолчанию",
"ua": "Випадковий проксі за замовчуванням"
}
},
"WaitLoginDialog": {
"Text": {
@@ -564,7 +614,22 @@
"ru": "Слишком быстрый таймер.",
"en": "Too fast timer.",
"ua": "Занадто швидкий таймер."
}
},
"DuplicateMafilesFound": {
"en": "Duplicate mafile(s) found and were not loaded. Check log to get lists of duplicate files.",
"ru": "Найдены дубликаты мафайлов, они не были загружены. Проверьте лог чтобы получить списки дубликатов.",
"ua": "Знайдено дублікати мафайлів, вони не були завантажені. Перевірте лог щоб отримати списки дублікатів."
},
"TimerChanged": {
"en": "Timer changed",
"ru": "Таймер изменен",
"ua": "Таймер змінено"
},
"CantRetrieveSteamIDToUpdate": {
"ru": "Не удалось получить SteamID из мафайла и сохранить изменения. Необходимо сделать релогин. Отменено",
"en": "Can't retrieve SteamID from mafile to save changes. Need to relogin. Canceled",
"ua": "Не вдалося отримати SteamID з мафайла та зберегти зміни. Потрібно зробити релогін. Скасовано"
}
},
"ErrorTranslator": {
"Login": {
@@ -626,7 +691,7 @@
"AccountNotFound": {
"ru": "Аккаунт не найден (18)",
"en": "Account not found (18)",
"ua": "Аккаунт не знайдено (18)"
"ua": "Акаунт не знайдено (18)"
},
"InvalidSteamID": {
"ru": "Неправильный SteamID (19)",
@@ -661,7 +726,7 @@
"AccountDisabled": {
"ru": "Аккаунт отключен (43)",
"en": "Account disabled (43)",
"ua": "Аккаунт відключено (43)"
"ua": "Акаунт відключено (43)"
},
"Suspended": {
@@ -684,7 +749,7 @@
"AccountLockedDown": {
"ru": "Аккаунт заблокирован (КТ 73)",
"en": "Account locked down (73)",
"ua": "Аккаунт заблоковано (КТ 73)"
"ua": "Акаунт заблоковано (КТ 73)"
},
"UnexpectedError": {
@@ -732,7 +797,7 @@
"AccountLimitExceeded": {
"ru": "Лимит аккаунта превышен (95)",
"en": "Account limit exceeded (95)",
"ua": "Ліміт аккаунта перевищено (95)"
"ua": "Ліміт акаунта перевищено (95)"
},
"EmailSendFailure": {
@@ -756,12 +821,12 @@
"LimitedUserAccount": {
"ru": "Аккаунт с лимитом (112)",
"en": "Limited user account (112)",
"ua": "Аккаунт з лімітом (112)"
"ua": "Акаунт з лімітом (112)"
},
"AccountDeleted": {
"ru": "Аккаунт удален (114)",
"en": "Account deleted (114)",
"ua": "Аккаунт видалено (114)"
"ua": "Акаунт видалено (114)"
},
"PhoneNumberIsVOIP": {
@@ -861,6 +926,13 @@
}
},
"SessionHandler": {
"SessionWasRefreshedAutomatically": {
"ru": "Сессия была обновлена автоматически",
"en": "Session was refreshed automatically",
"ua": "Сесія була оновлена автоматично"
}
},
"ProxyManagerVM": {
"WrongFormatSomeIdsMissing": {
"ru": "Неверный формат. Некоторые прокси не имеют ID",
+3 -3
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.4.4.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/Release/NebulaAuth.1.4.4.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.4.4.html</changelog>
<version>1.5.0.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.0/NebulaAuth.1.5.0.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.0.html</changelog>
<mandatory>false</mandatory>
</item>
+66
View File
@@ -0,0 +1,66 @@
# NebulaAuth
## Описание
NebulaAuth — это приложение для эмуляции действий из мобильного приложения Steam. Которая заменяет ваш смартфон при работе в Steam.
## Основные преимущества
- **Локализация на трёх языках**: английском, русском и украинском.
- **Полная функциональность Steam Desktop Authenticator** переосмысление [старого приложения](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Использование прокси**
- **Группировка мафайлов** для улучшения управления.
- **Автоматическое подтверждение трейдов/продаж на ТП** для экономии времени.
- **Массовый импорт файлов карт** с помощью Drag'n'Drop или CTRL+V для удобства.
- **Настройка дизайна** для персонализации интерфейса.
- **Возможность подтвердить вход в учетную запись без ввода кода** для облегчения доступа.
- **Автообновление** программы для использования новейших функций.
- **Автоматический повторный вход в случае проблем с сеансом** для непрерывной работы.
## Монтаж
1. Если приложение не запускается, необходимо установить [.net Desktop Runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.1-windows-x64-installer)
2. [Скачать программу из релизов этого репозитория на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
* *Для сохранности ваших данных скачивайте приложение только отсюда*
4. Распакуйте ZIP-файл в любую папку.
5. Запустите файл **NebulaAuth.exe**.
## Использование
![gh-main-window-rus](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/6a84f414-0e24-40a4-8adb-f1923fbe8719)
1. Панель управления.
- управление файлами и настройки
- управление аккаунтом (вход, привязка, отвязка)
- группировка
- выбор прокси
- индикатор с подсказкой об используемом прокси (светится либо желтым, либо красным, при наведении отобразит дополнительную информацию)
- таймеры для автоматического подтверждения трейдов/продаж на торговой площадке
- как часто проверять подтверждения при включенных таймерах (в секундах)
2. Список ваших аккаунтов
3. Код подтверждения входа (нажмите, чтобы скопировать)
4. Главное окно подтверждений
5. Поиск по логину или SteamID (7xxxxxxxxxxxxx)
6. Подтвердить вход с другого устройства.
7. Гиперссылка на официальную страницу приложения с указанием авторства.
## Настройки
![gh-settings-rus](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/33246ed1-1e3d-4310-88c5-085e5c50bc6b)
1. Режим фона. Переключите, если вы хотите отключить фон или установить собственный (поместите файл «Background.png» в папку приложения)
2. Язык локализации
3. Отключить таймеры при переключении между аккаунтами
4. Скрывать в трей при сворачивании
5. Индикатор с цветом. Маленький кружочек на значке панели задач с произвольным цветом. Полезно при использовании нескольких окон.
6. Пользовательский цвет приложения.
7. Текущий пароль шифрования. Если установлено, вы можете сохранять зашифрованные пароли в mafile, чтобы облегчить повторный вход в систему при проблемах с сеансом. (Не рекомендуется)
8. Режим устаревших файлов. Режим совместимости Mafile. Если установлено, приложение будет сохранять файлы в старом стандартном формате (по умолчанию: включено).
9. Разрешить автообновление без подтверждения
## [Лицензия](/LICENSE.md)
Коммерческое использование запрещено. При распространении измененного кода необходимо указывать оригинальное авторство.
+64
View File
@@ -0,0 +1,64 @@
# NebulaAuth
## Опис
NebulaAuth - це програма для емуляції дій з мобільного додатку Steam. Яка замінює ваш смартфон під час роботи в Steam.
## Основні переваги
- **Локалізація трьома мовами**: англійською, російською та українською.
- **Повна функціональність Steam Desktop Authenticator** переосмислення [старої програми](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Використання проксі**
- **Угруповання мафайлів** для покращення керування.
- **Автоматичне підтвердження трейдів/продаж на маркеті** для економії часу.
- **Масовий імпорт файлів карт** за допомогою Drag'n'Drop або CTRL+V для зручності.
- **Налаштування дизайну** для персоналізації інтерфейсу.
- **Можливість підтвердити вхід до облікового запису без введення коду** для полегшення доступу.
- **Автооновлення** програми для використання новітніх функцій.
- **Автоматичний повторний вхід у разі проблем із сеансом** для безперервної роботи.
## Монтаж
1. Якщо програма не запускається, необхідно встановити [.NET Desktop Runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.1-windows-x64-installer)
2. [Завантажити програму з релізів цього репозиторію на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
* *Для збереження ваших даних завантажуйте програму тільки звідси*
4. Розпакуйте ZIP-файл у будь-яку папку.
5. Запустіть файл **NebulaAuth.exe**.
## Використання
![gh-main-window-ua](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/bf61ac61-b21c-4589-9b5a-751a6d983120)
1. Панель керування.
- керування файлами та налаштування
- керування акаунтом (вхід, прив'язка, відв'язування)
- угруповання
- Вибір проксі
- індикатор з підказкою про проксі (світиться або жовтим, або червоним, при наведенні відобразить додаткову інформацію)
- таймери для автоматичного підтвердження трейдів/продажів на торговому майданчику
- як часто перевіряти підтвердження при увімкнених таймерах (у секундах)
2. Список ваших облікових записів
3. Код підтвердження входу (натисніть, щоб скопіювати)
4. Головне вікно підтвердження
5. Пошук за логіном або SteamID (7xxxxxxxxxxxxx)
6. Підтвердити вхід із іншого пристрою.
7. Гіперпосилання на офіційну сторінку додатку із зазначенням авторства.
## Налаштування
![gh-settings-ua](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/3f57e58d-d647-42a1-a4c9-ca53ab7b38a3)
1. Режим фону. Перемкніть, якщо ви хочете вимкнути фон або встановити власний (помістіть файл "Background.png" у папку програми)
2. Мова локалізації
3. Вимкнути таймери під час перемикання між обліковими записами
4. Приховувати у трей при згортанні
5. Індикатор із кольором. Маленький кружечок на піктограмі панелі завдань з довільним кольором. Корисно при використанні кількох вікон.
6. Власний колір програми.
7. Поточний пароль шифрування. Якщо встановлено, ви можете зберігати зашифровані паролі в mafile, щоб полегшити повторний вхід до системи у разі проблеми з сеансом. (Не рекомендується)
8. Режим застарілих файлів. Режим сумісності Mafile. Якщо встановлено, програма зберігатиме файли у старому стандартному форматі (за замовчуванням: увімкнено).
9. Дозволити автооновлення без підтвердження
## [Ліцензія](/LICENSE.md)
Комерційне використання заборонено. У разі поширення зміненого коду необхідно вказувати оригінальне авторство.
+73 -1
View File
@@ -1 +1,73 @@
# NebulaAuth
# NebulaAuth
* [Русский](README-RU.md)
* [Українська](README-UA.md)
## Description
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Latest release</a>
</h3>
<h3 align="center">NebulaAuth is an application for emulating actions from the Steam Mobile App. Which replaces your smartphone when operating on Steam. </h3>
## Main advantages
- **Localization in three languages**: English, Russian and Ukrainian.
- **Full functionality of Steam Desktop Authenticator** reimagining [old app](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Using a proxy**
- **Mafile grouping** for improved management.
- **Automatic confirmation of trades/trading platform** to save time.
- **Bulk import of map files** via Drag'n'Drop or CTRL+V for convenience.
- **Design customization** to personalize the interface.
- **Ability to confirm account login without entering a code** for easier access.
- **Auto-update** program to use the latest features.
- **Automatic relogin in case of problems with the session** for continuous operation.
## Installation
1. If the application does not start, you need to install [.net desktop runtime 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.1-windows-x64-installer)
2. [Download the program from the releases of this repository on Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
* *For the safety of your data, download the application only from here*
4. Unpack the .zip file to any folder
5. Run the file **NebulaAuth.exe**
## Usage
![gh-main-window-eng](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/15c0e870-1766-43a5-9e8c-2f34d5436beb)
1. Control panel.
- file management and settings
- account management (login, linking, unlinking)
- grouping
- proxy selection
- an indicator with a hint about the proxy used (lit either yellow or red, when hovered it will display additional information)
- timers for automatic confirmation of trade offers/sale offers on the marketplace
- how often to check confirmations when timers are enabled (in seconds)
2. List of your accounts
3. Login confirmation code (click to copy)
4. Main confirmation window
5. Search by login or SteamID (7xxxxxxxxxxxxx)
6. Confirm login from another device
7. Hyperlink to the official application page with attribution
## Settings
![gh-settings-eng](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/cd704495-d2df-45a7-a73e-40c19410eb72)
1. Background mode. Use it if you want to disable default or set custom background of application (put file 'Background.png' to your application folder)
2. Localization language
3. Disable timers when switching between accounts
4. Hide to tray on minimize
5. Indicator with color. Small ellipse on your task-bar icon with custom color. Useful when using multiple windows
6. Custom background color of application
7. Current encryption password. If set you can save encrypted passwords to mafile to help re-login on session troubles. (Not recommended)
8. Legacy mafile mode. Mafile compability mode. If set application will save mafiles with old standart format (Default: checked)
9. Allow auto-update without confirmation
## [License](/LICENSE.md)
Commercial use prohibited. When redistributing modified code, you must indicate the original authorship.
@@ -14,6 +14,7 @@ public static class AdmissionHelper
public const string ACCESS_COOKIE_NAME = "steamLoginSecure";
public const string REFRESH_COOKIE_NAME = "steamRefresh_steam";
public const string LANGUAGE_COOKIE_NAME = "Steam_Language";
public const string SESSION_ID_COOKIE_NAME = "sessionid";
#region Main
@@ -28,8 +29,8 @@ public static class AdmissionHelper
AddRefreshToken(container, sessionData.RefreshToken);
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(community, new Cookie("sessionid", sessionData.SessionId, "/"));
container.Add(community, new Cookie("Steam_Language", setLanguage, "/"));
container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, sessionData.SessionId, "/"));
container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage, "/"));
TransferCommunityCookies(container);
foreach (var domain in SteamDomains.AllDomains)
{
@@ -65,18 +66,52 @@ public static class AdmissionHelper
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(community, new Cookie("steamid", mobileSession.SteamId.Steam64.ToString()));
container.Add(community, new Cookie("sessionid", mobileSession.SessionId));
container.Add(community, new Cookie("Steam_Language", setLanguage));
container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, mobileSession.SessionId));
container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage));
TransferCommunityCookies(container);
foreach (var domain in SteamDomains.AllDomains)
{
var token = mobileSession.GetToken(domain);
if (token == null) continue;
if (token == null || token.Value.IsExpired) continue;
AddTokenCookie(container, token.Value);
}
}
/// <summary>
/// Clear and set new session. Not recommended. Uses <see cref="IMobileSessionData.GetMobileToken()"/> for domain <see cref="SteamDomain.Community"/> instead of its own cookie. It's okay to use it only for confirmations. But Market, Trading and other pages won't be authenticated
/// </summary>
public static void SetSteamMobileCookiesWithMobileToken(this CookieContainer container, IMobileSessionData mobileSession,
string setLanguage = "english")
{
container.ClearSteamCookies(setLanguage);
container.AddMinimalMobileCookies();
AddRefreshToken(container, mobileSession.RefreshToken);
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(community, new Cookie("steamid", mobileSession.SteamId.Steam64.ToString()));
container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, mobileSession.SessionId));
container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage));
TransferCommunityCookies(container);
var domainCookieSet = false;
foreach (var domain in SteamDomains.AllDomains)
{
var token = mobileSession.GetToken(domain);
if (token == null || token.Value.IsExpired) continue;
if(domain == SteamDomain.Community )
domainCookieSet = true;
AddTokenCookie(container, token.Value);
}
var mobileToken = mobileSession.GetMobileToken();
if (domainCookieSet == false && mobileToken is {IsExpired: false})
AddTokenCookie(container, SteamDomain.Community, mobileToken.Value);
}
public static void AddMinimalMobileCookies(this CookieContainer container)
{
@@ -120,10 +155,15 @@ public static class AdmissionHelper
if (cookie.Domain.Contains("steamcommunity.com") == false || cookie.Expired || cookie.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)) continue;
container.Add(SteamDomains.GetDomainUri(SteamDomain.Store), new Cookie(cookie.Name, cookie.Value, cookie.Path) { Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly });
container.Add(SteamDomains.GetDomainUri(SteamDomain.Help), new Cookie(cookie.Name, cookie.Value, cookie.Path) { Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly });
container.Add(SteamDomains.GetDomainUri(SteamDomain.TV), new Cookie(cookie.Name, cookie.Value, cookie.Path) { Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly });
container.Add(SteamDomains.GetDomainUri(SteamDomain.Store), CloneCookie(cookie));
container.Add(SteamDomains.GetDomainUri(SteamDomain.Help), CloneCookie(cookie));
container.Add(SteamDomains.GetDomainUri(SteamDomain.TV), CloneCookie(cookie));
}
return;
static Cookie CloneCookie(Cookie cookie)
=> new(cookie.Name, cookie.Value, cookie.Path) { Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly };
}
public static void AddRefreshToken(CookieContainer container, SteamAuthToken token)
@@ -146,7 +186,7 @@ public static class AdmissionHelper
}
private static void AddTokenCookie(CookieContainer container, SteamDomain domain, SteamAuthToken token)
{
var domainUri = SteamDomains.GetDomainUri(token.Domain);
var domainUri = SteamDomains.GetDomainUri(domain);
container.Add(domainUri, new Cookie(ACCESS_COOKIE_NAME, token.SignedToken)
{
HttpOnly = true,
@@ -164,7 +204,7 @@ public static class AdmissionHelper
{
var cookies = container.GetAllCookies();
return cookies
.FirstOrDefault(c => c.Name.Equals("sessionid", StringComparison.InvariantCultureIgnoreCase)
.FirstOrDefault(c => c.Name.Equals(SESSION_ID_COOKIE_NAME, StringComparison.InvariantCultureIgnoreCase)
&& c.Expired == false
&& c.Domain.Contains(domain, StringComparison.InvariantCultureIgnoreCase))?
.Value;
+1 -1
View File
@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="AchiesUtilities.Newtonsoft.JSON" Version="1.2.1" />
<PackageReference Include="AchiesUtilities.Web" Version="1.0.10" />
<PackageReference Include="AchiesUtilities.Web" Version="1.0.11" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.58" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
@@ -4,7 +4,7 @@ public class MafileCredits : IMafileCredits
{
internal static readonly MafileCredits Instance = new();
private const string ORIGINAL_AUTHOR = "Achies";
private const string MOBILE_APP = "https://github.com/achiez/NebulaAuth";
private const string MOBILE_APP = "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies";
public string OriginalAuthor => ORIGINAL_AUTHOR;
public string BestOpenSourceMobileApp => MOBILE_APP;
@@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using AchiesUtilities.Models;
using Newtonsoft.Json.Linq;
using SteamLib.Account;
using SteamLib.Authentication;
using SteamLib.Core.Enums;
@@ -14,7 +15,7 @@ public partial class MafileSerializer //SessionData
"refresh", "OAuthToken");
SteamAuthToken refreshToken;
SteamAuthToken? refreshToken = null;
if (refreshTokenToken == null || refreshTokenToken.Type == JTokenType.Null) return null;
if (refreshTokenToken.Type == JTokenType.String && SteamTokenHelper.TryParse(refreshTokenToken.Value<string>()!, out var parsed))
{
@@ -28,19 +29,10 @@ public partial class MafileSerializer //SessionData
}
catch
{
return null;
//Ignored
}
}
else
{
return null;
}
if (refreshToken.IsExpired || refreshToken.Type != SteamAccessTokenType.MobileRefresh)
{
result = DeserializedMafileSessionResult.Expired;
return null;
}
var sessionId = GetString(j, "sessionid", "session_id", "session");
var accessTokenToken = GetToken(j, "accesstoken", "access_token", "access");
@@ -71,11 +63,58 @@ public partial class MafileSerializer //SessionData
}
var sessionData = new MobileSessionData(sessionId, refreshToken.SteamId, refreshToken, accessToken, new Dictionary<SteamDomain, SteamAuthToken>());
var steamId = refreshToken?.SteamId ?? GetSessionSteamId(j);
if (steamId == null)
{
result = DeserializedMafileSessionResult.Invalid;
return null;
}
refreshToken ??= CreateInvalid(steamId.Value);
var sessionData = new MobileSessionData(sessionId, steamId.Value, refreshToken.Value, accessToken, new Dictionary<SteamDomain, SteamAuthToken>());
sessionData.IsValid = SessionDataValidator.Validate(null, sessionData).Succeeded;
if(sessionData.IsValid == false)
return null;
result = DeserializedMafileSessionResult.Valid;
if (refreshToken.Value.IsExpired || refreshToken.Value.Type != SteamAccessTokenType.MobileRefresh)
{
result = DeserializedMafileSessionResult.Expired;
}
else
{
result = DeserializedMafileSessionResult.Valid;
}
return sessionData;
}
private static SteamId? GetSessionSteamId(JObject j)
{
var token = GetToken(j, "steamid");
if (token == null || token.Type == JTokenType.Null)
return null;
if(token.Type == JTokenType.Integer)
return SteamId.FromSteam64(token.Value<long>());
if (token.Type == JTokenType.String && long.TryParse(token.Value<string>()!, out var steamId))
{
return SteamId.FromSteam64(steamId);
}
return null;
}
//Workaround to avoid session being invalidated due to missing a valid token.
//The reason for this change is the inability to proxy/change group for old mafiles, which creates more problems than benefits.
//A temporary solution until I decide how to read the SteamID correctly without invalidating the entire session.
//It also makes the LoginAgainOnImport mechanism useless, which is good outcome.
//Most likely I need to reconsider the reaction to an “invalid” session and simply feed it to the software as “expired”.
//Also, when deciding not to validate RefreshToken, I need to reconsider the entire validation method in the Validator class and think through the consequences in the rest of the code.
//FIXME: Refactor code to avoid this workaround and make it more organic.
//TODO: after fixing the issue, reflect changes in the original library
private static SteamAuthToken CreateInvalid(SteamId steamId)
{
return new SteamAuthToken("invalid", steamId, UnixTimeStamp.FromDateTime(DateTime.Now - TimeSpan.FromSeconds(1)), SteamDomain.Community, SteamAccessTokenType.MobileRefresh);
}
}
@@ -8,7 +8,7 @@ public partial class MafileSerializer //Utility
{
foreach (var name in aliases)
{
if (j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token))
if (j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token))
{
return token;
}
@@ -21,7 +21,7 @@ public partial class MafileSerializer //Utility
{
foreach (var name in aliases)
{
if (!j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token)) continue;
if (!j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token)) continue;
var parent = token.Parent as JProperty;
removeFrom.Remove(parent!.Name);
return token;
@@ -34,7 +34,7 @@ public partial class MafileSerializer //Utility
{
foreach (var name in aliases)
{
if (!j.TryGetValue(name, StringComparison.InvariantCultureIgnoreCase, out var token)) continue;
if (!j.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out var token)) continue;
if (token.Type == JTokenType.Null)
{
throw new ArgumentException($"Required property {propertyName} is null");
@@ -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 _) == false)
throw new ArgumentException($"{name} is not valid base64 string");
}
@@ -63,7 +63,6 @@ public partial class MafileSerializer //Validate
if (d.SessionData.RefreshToken.IsExpired)
{
sessionResult = DeserializedMafileSessionResult.Expired;
return null;
}
d.SessionData.IsValid = SessionDataValidator.Validate(null, d.SessionData).Succeeded;
@@ -1,7 +1,5 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Xml;
using Formatting = Newtonsoft.Json.Formatting;
namespace SteamLib.Utility.MaFiles;
@@ -47,7 +45,7 @@ public static partial class MafileSerializer //Write
}
public static string SerializeLegacy(MobileData mobileData, Formatting formatting, Dictionary<string, object?>? additionalProperties = null, bool sign = true, MafileCredits? credits = null)
public static string SerializeLegacy(MobileData mobileData, Formatting formatting, Dictionary<string, object?>? additionalProperties = null, bool sign = true, MafileCredits? credits = null)
{
var result = new LegacyMafile
{
+1 -1
View File
@@ -29,7 +29,7 @@ public static class ClientBuilder
}
else
{
container.SetSteamMobileCookies(sessionData);
container.SetSteamMobileCookiesWithMobileToken(sessionData);
}
ConfigureCommon(handler, client);
+81
View File
@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.4.5</div>
<div class="date">2024-02-01</div>
<div class="description">
- Fixed crash when loading market confirmations cause <br>
- Fixed session handling <br>
- Small localization fixes <br>
- Small UI improvements
</div>
</div>
</div>
</body>
</html>
+158
View File
@@ -0,0 +1,158 @@
<<<<<<< HEAD
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.4.6</div>
<div class="date">2024-02-04</div>
<div class="description">
- Added proxy support for log-in proccess while importing mafile with outdated/unsupported session<br>
</div>
</div>
</div>
</body>
=======
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.4.6</div>
<div class="date">2024-02-04</div>
<div class="description">
- Added proxy support for log-in process while importing mafile with outdated/unsupported session<br>
</div>
</div>
</div>
</body>
>>>>>>> 1.4.7
</html>
+83
View File
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.4.7</div>
<div class="date">16.02.2024</div>
<div class="description">
- Fixed account list was missing imported mafile when import was through login dialog<br>
- Duplicate mafiles now will be not loaded to application and shows warning at start. Previously, it could cause crashes<br>
- Added support to save mafile with Account name instead of SteamId. (This may creates duplicates if mafile already saved with SteamId. Not critical')<br/>
- Mafile backups now will be serialized in current compability mode (legacy or Nebula specific)<br/>
- Icon updated to more elegant<br/>
- Red proxy indicator now displays proxy IP and port from mafile<br/>
</div>
</div>
</div>
</body>
</html>
+84
View File
@@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.4.8</div>
<div class="date">10.04.2024</div>
<div class="description">
- Fixed crash when attempting to update/save mafile without SessionData on proxy and group change<br/>
- The account found through the search will be selected automatically<br>
- Added proxy support types: without authentication (no user and password), domain proxy (localhost, mydomain.com), with "http://" scheme<br>
- Added "Timer changed" snackbar to indicate that preferences was updated<br>
- Now the port is visible in the proxy text<br>
- Few tooltips added on "Groups" and "Proxy" fields"<br/>
- Small UI improvements<br/>
</div>
</div>
</div>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.4.9</div>
<div class="date">25.04.2024</div>
<div class="description">
- Improved compatibility with old mafiles<br/>
- FIX: Now 'Login' button is disabled if password is empty (caused crash before)<br />
- FIX: The tutorial for beginners is no longer shown after ever hiding (for example, when searching without suitable mafiles)<br />
- UI/UX improvements in proxy manager: <br />
&nbsp;&nbsp;&nbsp;&nbsp;- Favourite button is placed on proxy item<br />
&nbsp;&nbsp;&nbsp;&nbsp;- Pressing 'DEL' now removes selected proxy<br />
&nbsp;&nbsp;&nbsp;&nbsp;- After proxy is removed next to it is selected (allows to remove proxies quickly)<br />
&nbsp;&nbsp;&nbsp;&nbsp;- Empty lines on import now ignored (previously entire import was cancelling)<br />
- Mafile credits now corresponds to the actual github repository<br />
</div>
</div>
</div>
</body>
</html>
+78
View File
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.5.0</div>
<div class="date">13.05.2024</div>
<div class="description">
- FIX: Fixed proxy 407 error when using proxy with different credentials
</div>
</div>
</div>
</body>
</html>