Compare commits

...

16 Commits

Author SHA1 Message Date
Achies 769f102d16 Merge pull request #2 from achiez/pre-release
Pre release -> Release master
2024-09-18 14:22:43 +03:00
Давид Чернопятов 80d7c09b0d Prepared for 1.5.2 release
HOTFIX: Resolved an issue introduced by the September 18, 2024, Steam update, where an error occurred during session refresh due to changes in Steam's response model.
2024-09-18 14:16:32 +03:00
Давид Чернопятов e6ae762d5a - Added splashscreen
- Added Copy Login to Context Menu
- UI and localization improvements
2024-09-15 16:45:20 +03:00
Давид Чернопятов 89ca08f6cf Pre-release performance update.
- Potentially fixed memory leak in code progress bar
- Added using statement in SteamGuardCodeGenerator on disposable HMACSHA1
- Most of packages updated to the last version
2024-09-01 02:51:35 +03:00
Давид Чернопятов e401bcccfd 1.5.1
Commit just for executing actions (see previous commit with merge for real changes)
2024-07-08 16:35:56 +03:00
Achies 195ac95b36 Merge pull request #1 from achiez/pre-release
Release 1.5.1
2024-07-08 16:30:15 +03:00
Achies d1f660381e Update build-and-release.yml 2024-07-08 16:24:38 +03:00
Давид Чернопятов c8aa7ba8e7 1.5.1 2024-07-08 16:20:28 +03:00
Achies 50a7d21d52 Update build-and-release.yml 2024-07-08 16:10:15 +03:00
Achies aa092bdd67 Create build-and-release.yml
Action to build, publish and validate release
2024-07-08 15:41:15 +03:00
Давид Чернопятов 6a1b03163c 1.5.1 progress
fixed new 407 error caused by parsing {id} as password part
2024-07-08 12:58:26 +03:00
Давид Чернопятов 9a277d07db AchiesUtilities.Web updated to 1.0.11. This version contains proxy credentials fix (407 error) 2024-05-13 18:05:57 +03:00
Давид Чернопятов 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
54 changed files with 1164 additions and 376 deletions
+108
View File
@@ -0,0 +1,108 @@
name: Build and Release
on:
push:
tags:
- '*'
jobs:
build:
if: github.ref == 'refs/heads/master'
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: "8.x"
- name: Restore dependencies
run: dotnet restore NebulaAuth.sln
- name: Build
run: dotnet build NebulaAuth.sln --configuration Release
- name: Get version from assembly
id: get-version
shell: pwsh
run: |
$content = Get-Content -Path "NebulaAuth/NebulaAuth.csproj" -Raw
$version = [regex]::Match($content, '<AssemblyVersion>(.*?)<\/AssemblyVersion>').Groups[1].Value
Write-Output "VERSION=$version" >> $env:GITHUB_ENV
- name: Check if tag exists
id: tag_exists
run: |
if (git tag -l | Select-String -Pattern "^${env:VERSION}$") {
Write-Output "Version $env:VERSION already exists."
exit 1
}
- name: Check changelog
run: |
if (-not (Test-Path "changelog/${env:VERSION}.html")) {
Write-Output "Changelog file changelog/${env:VERSION}.html does not exist."
exit 1
}
- name: Insert date into changelog
run: |
$date = Get-Date -Format "dd.MM.yyyy"
(Get-Content "changelog/${env:VERSION}.html") -replace '(?<=<div class="date">).*?(?=</div>)', $date | Set-Content "changelog/${env:VERSION}.html"
- name: Extract changelog description
id: extract_description
run: |
$description = (Get-Content "changelog/${env:VERSION}.html" | Select-String -Pattern '(?<=<div class="description">).*?(?=</div>)' | ForEach-Object { $_.Matches.Value }) -replace '<br\/>', "`n"
Write-Output "DESCRIPTION=$description" >> $env:GITHUB_ENV
- name: Create ZIP
run: |
New-Item -ItemType Directory -Path release -Force
Compress-Archive -Path NebulaAuth/bin/Release -DestinationPath release/NebulaAuth.${env:VERSION}.zip
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.VERSION }}
release_name: NebulaAuth ${{ env.VERSION }}
body: |
${{ env.DESCRIPTION }}
draft: false
prerelease: false
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: release/NebulaAuth.${{ env.VERSION }}.zip
asset_name: NebulaAuth.${{ env.VERSION }}.zip
asset_content_type: application/zip
- name: Update XML and Changelog html
shell: pwsh
run: |
$xmlContent = @"
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>${env:VERSION}.0</version>
<url>https://github.com/${env:GITHUB_REPOSITORY}/releases/download/${env:VERSION}/NebulaAuth.${env:VERSION}.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/${env:VERSION}.html</changelog>
<mandatory>false</mandatory>
</item>
"@
$xmlContent | Out-File -FilePath update.xml -Encoding UTF8 -Force
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git add changelog/${env:VERSION}.html update.xml
git commit -m "Update version to ${env:VERSION} and add changelog"
git push origin master
+5
View File
@@ -19,6 +19,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
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
changelog\1.5.1.html = changelog\1.5.1.html
changelog\1.5.2.html = changelog\1.5.2.html
EndProjectSection
EndProject
Global
+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"/>
+1 -1
View File
@@ -22,7 +22,7 @@ public partial class App : Application
}
catch (Exception ex)
{
var msg = ex.Message;
var msg = ex.ToString();
if (ex is CantAlignTimeException)
{
msg = Loc.Tr(LocManager.GetCodeBehind("CantAlignTimeError"));
@@ -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)
-1
View File
@@ -5,7 +5,6 @@ using NebulaAuth.Model.Entities;
using NebulaAuth.View;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel.Other;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
namespace NebulaAuth.Core;
+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);
}
+42 -49
View File
@@ -1,14 +1,7 @@
using AutoUpdaterDotNET;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model;
using NebulaAuth.View;
using NebulaAuth.ViewModel.Other;
using System;
using System.IO;
using System.Net;
using System.Windows.Forms;
using System.Windows.Threading;
using Application = System.Windows.Application;
namespace NebulaAuth.Core;
@@ -29,55 +22,55 @@ public static class UpdateManager
}
static UpdateManager()
{
//AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
//static UpdateManager()
//{
// //AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
}
//}
private static async void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args.Error == null)
{
if (args.IsUpdateAvailable)
{
DialogResult dialogResult;
var dialog = new UpdaterView()
{
DataContext = new UpdaterVM(args)
};
//private static async void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
//{
// if (args.Error == null)
// {
// if (args.IsUpdateAvailable)
// {
// DialogResult dialogResult;
// var dialog = new UpdaterView()
// {
// DataContext = new UpdaterVM(args)
// };
await DialogHost.Show(dialog);
Application.Current.Shutdown();
// await DialogHost.Show(dialog);
// Application.Current.Shutdown();
}
else
{
// }
// else
// {
}
}
else
{
if (args.Error is WebException)
{
// }
// }
// else
// {
// if (args.Error is WebException)
// {
}
else
{
// }
// else
// {
}
}
// }
// }
}
//}
private static void RunUpdate(UpdateInfoEventArgs args)
{
Application.Current.Dispatcher.Invoke(() =>
{
if (AutoUpdater.DownloadUpdate(args))
{
Application.Current.Shutdown();
}
}, DispatcherPriority.ContextIdle);
}
//private static void RunUpdate(UpdateInfoEventArgs args)
//{
// Application.Current.Dispatcher.Invoke(() =>
// {
// if (AutoUpdater.DownloadUpdate(args))
// {
// Application.Current.Shutdown();
// }
// }, DispatcherPriority.ContextIdle);
//}
}
+16 -2
View File
@@ -68,7 +68,11 @@
</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="md:PackIcon">
@@ -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}" />
@@ -133,6 +137,7 @@
<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.CopyLogin}" Command="{Binding CopyLoginCommand}" CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}"></MenuItem>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}" ItemsSource="{Binding Groups}">
<ItemsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignMenuItem}" TargetType="{x:Type MenuItem}">
@@ -152,6 +157,15 @@
</ContextMenu>
</FrameworkElement.ContextMenu>
</ListBox>
<Border Grid.Row="0" Margin="10" Padding="5" Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}, Mode=OneWay}">
<Border.Background>
<SolidColorBrush Color="DarkGray" Opacity="0.5"/>
</Border.Background>
<TextBlock TextWrapping="WrapWithOverflow" FontSize="16" Text="{Tr MainWindow.Global.StartTip}">
</TextBlock>
</Border>
<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">
+5 -7
View File
@@ -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,7 +116,7 @@ public static class MaClient
var newToken = SteamTokenHelper.Parse(communityTokenString);
mafile.SessionData.SetToken(SteamDomain.Community, newToken);
}
Storage.UpdateMafile(mafile);
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
}
@@ -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;
+70 -22
View File
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using System.Linq;
using AchiesUtilities.Web.Proxy.Parsing;
using NebulaAuth.Core;
using Newtonsoft.Json;
@@ -10,20 +12,29 @@ namespace NebulaAuth.Model;
public static class ProxyStorage
{
public const string FORMAT = ADDRESS_FORMAT + ":{USER}:{PASS}";
public const string ADDRESS_FORMAT = "{IP}:{PORT}";
public static readonly ProxyScheme DefaultScheme = new ProxyScheme(
ProxyDefaultFormats.UniversalHostFirstColonDelimiter, false, ProxyProtocol.HTTP,
ProxyPatternProtocol.HTTP | ProxyPatternProtocol.HTTPs,
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4, PatternRequirement.Optional,
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
{
var json = File.ReadAllText("proxies.json");
var proxies = JsonConvert.DeserializeObject<Proxies>(json) ?? throw new NullReferenceException();
var proxies = JsonConvert.DeserializeObject<ProxiesSchema>(json) ?? throw new NullReferenceException();
Proxies = proxies.ProxiesData;
Proxies = new ObservableDictionary<int, ProxyData>(
Proxies.OrderBy(p => p.Key)
@@ -34,21 +45,18 @@ 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)
{
if (id == null)
{
if (Proxies.Any() == false)
if (Proxies.Count == 0)
{
id = 0;
}
@@ -59,16 +67,52 @@ public static class ProxyStorage
}
Proxies[id] = proxyData;
Save();
}
public static void SetProxies(IEnumerable<KeyValuePair<int?, ProxyData>> proxies)
{
foreach (var (key, proxyData) in proxies)
{
var id = key;
if (id == null)
{
if (Proxies.Count == 0)
{
id = 0;
}
else
{
id = Proxies.Keys.Max() + 1;
}
}
Proxies[id] = proxyData;
}
Save();
}
public static void OrderCollection() //RETHINK: maybe there is better way to handle it
{
var proxies = Proxies.OrderBy(p => p.Key)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Proxies.Clear();
foreach (var kvp in proxies)
{
Proxies.Add(kvp.Key, kvp.Value);
}
}
public static void RemoveProxy(int id)
{
Proxies.Remove(id);
Save();
}
public static bool CompareProxy(ProxyData proxyData1, ProxyData proxyData2)
{
return proxyData1.ToString(FORMAT) == proxyData2.ToString(FORMAT);
{
return proxyData1.Equals(proxyData2);
}
@@ -79,29 +123,33 @@ public static class ProxyStorage
File.WriteAllText("proxies.json", json);
}
private static Proxies Create()
public static string GetProxyString(ProxyData proxyData)
{
return proxyData.AuthEnabled ? proxyData.ToString(FORMAT) : proxyData.ToString(ADDRESS_FORMAT);
}
private static ProxiesSchema 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
return new ProxiesSchema
{
ProxiesData = Proxies,
DefaultProxy = def
};
}
}
public class Proxies
{
public ObservableDictionary<int, ProxyData> ProxiesData { get; set; }
public int? DefaultProxy { get; set; }
private class ProxiesSchema
{
public ObservableDictionary<int, ProxyData> ProxiesData = new();
public int? DefaultProxy;
}
}
+3 -2
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)
-1
View File
@@ -32,7 +32,6 @@ public static class Shell
throw new CantAlignTimeException("", ex);
}
ExtensionsLogger.LogDebug("Application started");
Logger.Info("Test");
}
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
+16 -8
View File
@@ -23,6 +23,7 @@ public static class Storage
public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F);
public static ObservableCollection<Mafile> MaFiles { get; } = new();
public static readonly int DuplicateFound;
static Storage()
{
@@ -48,7 +49,7 @@ public static class Storage
{
var data = ReadMafile(file);
if (hashNames.Contains(data.AccountName) || (data.SessionData != null && hashIds.Contains(data.SessionData.SteamId)))
if (hashNames.Contains(data.AccountName) || (data.SessionData != null && hashIds.Contains(data.SessionData.SteamId)))
{
DuplicateFound++;
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
@@ -217,16 +218,18 @@ public static class Storage
private static string CreatePathForMafile(Mafile data)
{
if (data.SessionData == null)
throw new NullReferenceException("SessionData was null can't retrieve SteamId"); //TODO: handle with login
var useAccountName = Settings.Instance.UseAccountNameAsMafileName;
if (!useAccountName && (data.SessionData.SteamId.Steam64 == 0 || data.SessionData.SteamId.Steam64 == SteamId64.SEED))
string fileName;
if (Settings.Instance.UseAccountNameAsMafileName)
{
useAccountName = true;
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
var fileName = useAccountName ? CreateFileNameWithAccountName(data.AccountName) : CreateFileNameWithSteamId(data.SessionData.SteamId);
fileName = CreateFileNameWithSteamId(data.SessionData.SteamId);
}
return Path.Combine(MafileFolder, fileName);
}
@@ -257,6 +260,11 @@ public static class Storage
}
return null;
}
public static bool ValidateCanSave(Mafile data)
{
return Settings.Instance.UseAccountNameAsMafileName || data.SessionData != null;
}
}
internal class MafileNameComparer : IComparer<string>
-58
View File
@@ -1,58 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<LangVersion>latest</LangVersion>
<SatelliteResourceLanguages>ru</SatelliteResourceLanguages>
<ApplicationIcon>Theme\nebula lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.4.0</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Theme\Background.jpg" />
<None Remove="Theme\nebula lock.ico" />
<None Remove="Theme\nebula.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="NLog" Version="5.1.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.2" />
</ItemGroup>
<ItemGroup>
<Resource Include="Theme\Background.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Theme\nebula lock.ico" />
<Resource Include="Theme\nebula.ico" />
</ItemGroup>
<ItemGroup>
<Folder Include="Model\Utility\" />
<Folder Include="Model\Exceptions\" />
<Folder Include="Theme\Fonts\Новая папка\" />
<Folder Include="ViewModel\Other\" />
<Folder Include="Utility\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SteamLib\SteamLib\SteamLib.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+15 -9
View File
@@ -10,26 +10,28 @@
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.4.7</AssemblyVersion>
<AssemblyVersion>1.5.2</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Theme\1140x641.jpg" />
<None Remove="Theme\Background.jpg" />
<None Remove="Theme\lock.ico" />
<None Remove="Theme\SplashScreen.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.4" />
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.0" />
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.0" />
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.1" />
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.3" />
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.0" />
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
<PackageReference Include="MaterialDesignExtensions" Version="4.0.0-a02" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.8" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
<PackageReference Include="NLog" Version="5.3.3" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.12" />
</ItemGroup>
<ItemGroup>
@@ -48,6 +50,10 @@
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
</ItemGroup>
<ItemGroup>
<SplashScreen Include="Theme\SplashScreen.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="View\Dialogs\LoginAgainOnImportDialog.xaml.cs">
<SubType>Code</SubType>
Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

@@ -26,14 +26,14 @@
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></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="0,0,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
<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>
@@ -28,22 +28,25 @@
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"/>
<Run FontWeight="Bold" Text="{Binding UserName}"/>
</TextBlock>
<TextBox Text="{Binding Password}" Margin="10,10,10,0" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<ComboBox Grid.Row="2" Margin="10,10,10,0" materialDesign:HintAssist.Hint="{Tr Common.Proxy}" ItemsSource="{Binding Proxies}" SelectedItem="{Binding SelectedProxy}" >
<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>
</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,0,10,0">
<Grid Grid.Row="5" Margin="10,10,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button IsDefault="True" Margin="0,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}"/>
<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>
@@ -1,6 +1,4 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs
namespace NebulaAuth.View.Dialogs
{
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
@@ -5,7 +5,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
theme:FontScaleWindow.ResizeFont="True" theme:FontScaleWindow.Scale="1"
Foreground="WhiteSmoke"
Background="{DynamicResource WindowBackground}">
+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"/>
+1 -1
View File
@@ -45,7 +45,7 @@
<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,10,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}"/>
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}" ToolTip="{Tr SettingsDialog.UseIndicatorHint}"/>
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
<CheckBox Margin="0,10,0,5" FontSize="16" IsChecked="{Binding UseBackground}" Content="{Tr SettingsDialog.UseCustomColor}"/>
+1 -14
View File
@@ -1,17 +1,4 @@
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;
using System.Windows.Controls;
namespace NebulaAuth.View
{
+3 -1
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;
@@ -46,7 +48,7 @@ public partial class MainVM : ObservableObject
{
SnackbarController.SendSnackbar(
GetLocalizationOrDefault("DuplicateMafilesFound") + " " + Storage.DuplicateFound,
TimeSpan.FromSeconds(3));
TimeSpan.FromSeconds(4));
}
}
+12 -8
View File
@@ -1,8 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using SteamLib.SteamMobile;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
namespace NebulaAuth.ViewModel;
@@ -14,30 +16,32 @@ public partial class MainVM
[MemberNotNull(nameof(_codeTimer))]
[MemberNotNull(nameof(_code))]
private void CreateCodeTimer()
{
var currentTime = TimeAligner.GetSteamTime();
_codeTimer = new Timer(UpdateCode, null, 0, 1000);
}
private void UpdateCode(object? state = null)
{
var currentTime = TimeAligner.GetSteamTime();
var untilChange = currentTime - currentTime / 30L * 30L;
var codeProgress = untilChange / 30D * 100;
string? code = null;
if (untilChange == 0 && SelectedMafile != null)
{
code = SteamGuardCodeGenerator.GenerateCode(SelectedMafile!.SharedSecret);
}
if(Application.Current == null) return;
Application.Current.Dispatcher.BeginInvoke(() =>
if (Application.Current == null) return;
Application.Current.Dispatcher.Invoke((string? c) =>
{
if (Application.Current.MainWindow?.WindowState == WindowState.Minimized) return;
CodeProgress = codeProgress;
if (code != null) Code = code;
});
if (c != null) Code = c;
}, DispatcherPriority.Background, code);
}
}
+27
View File
@@ -17,6 +17,7 @@ using NebulaAuth.Model.Entities;
using SteamLib.Exceptions;
using NebulaAuth.Utility;
using NebulaAuth.View.Dialogs;
using AutoUpdaterDotNET;
namespace NebulaAuth.ViewModel;
@@ -238,4 +239,30 @@ public partial class MainVM //File //TODO: Refactor
await AddMafile(arr);
}
[RelayCommand]
private void CopyLogin(object? mafile)
{
if(mafile is not Mafile maf) return;
var i = 0;
while (i < 20)
{
try
{
Clipboard.SetText(maf.AccountName);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("LoginCopied"));
return;
}
catch (Exception ex)
{
if (i == 19)
{
Shell.Logger.Error(ex);
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
}
}
i++;
}
}
}
+6 -5
View File
@@ -47,6 +47,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,10 +62,12 @@ 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();
@@ -75,6 +79,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);
@@ -125,11 +130,7 @@ 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)
+14 -1
View File
@@ -76,7 +76,7 @@ public partial class MainVM
var selectedId = SelectedProxy.Id;
ProxyExist = ProxyStorage.Proxies.TryGetValue(selectedId, out var existedProxy)
&& ProxyStorage.CompareProxy(SelectedProxy.Data, existedProxy);
&& SelectedProxy.Data.Equals(existedProxy); //Id is not important in 'Equals()' as we extract it from the dictionary
}
[RelayCommand]
@@ -98,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"));
}
}
@@ -6,7 +6,6 @@ using MaterialDesignThemes.Wpf;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using Newtonsoft.Json;
using NLog;
using SteamLib;
using SteamLib.Account;
@@ -26,7 +25,6 @@ using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using NebulaAuth.Core;
using SteamLib.Utility.MaFiles;
namespace NebulaAuth.ViewModel.Other;
@@ -2,13 +2,16 @@
using NebulaAuth.Model.Entities;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Input;
namespace NebulaAuth.ViewModel.Other;
public partial class LoginAgainOnImportVM : ObservableObject
{
public ObservableCollection<MaProxy> Proxies { get; } = new();
[ObservableProperty] private string _password = null!;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsFormValid))]
private string _password = null!;
[ObservableProperty] private bool _savePassword;
[ObservableProperty] private string _userName = null!;
[ObservableProperty] private bool _mafileHasProxy;
@@ -36,7 +39,7 @@ public partial class LoginAgainOnImportVM : ObservableObject
}
}
public bool IsFormValid => !string.IsNullOrWhiteSpace(Password);
private MaProxy? _selectedProxy;
@@ -51,4 +54,10 @@ public partial class LoginAgainOnImportVM : ObservableObject
public LoginAgainOnImportVM()
{ }
[RelayCommand]
private void RemoveProxy()
{
SelectedProxy = null;
}
}
+13 -9
View File
@@ -1,17 +1,21 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using CommunityToolkit.Mvvm.ComponentModel;
namespace NebulaAuth.ViewModel.Other;
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()
{ }
+87 -68
View File
@@ -21,7 +21,7 @@ public partial class ProxyManagerVM : ObservableObject
[ObservableProperty] private KeyValuePair<int, ProxyData>? _defaultProxy;
public ObservableDictionary<int, ProxyData> Proxies => ProxyStorage.Proxies;
private static readonly Regex IdRegex = new(@"(?:\{(\d+)\})");
private static readonly Regex IdRegex = new(@"\{(\d+)\}$", RegexOptions.Compiled);
public ProxyManagerVM()
@@ -33,80 +33,71 @@ public partial class ProxyManagerVM : ObservableObject
[RelayCommand]
private void AddProxy()
{
if (string.IsNullOrEmpty(AddProxyField)) return;
if (AddProxyField.Contains(Environment.NewLine))
{
var split = AddProxyField.Split(Environment.NewLine);
var idPresent = (bool?)null;
var proxies = new List<KeyValuePair<int?, ProxyData>>();
var i = 0;
foreach (var str in split)
{
i++;
int? id = null;
var match = IdRegex.Match(str);
if (match.Success) id = int.Parse(match.Groups[1].Value);
idPresent ??= match.Success;
if (idPresent.Value != match.Success)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
return;
}
try
{
var proxy = ProxyData.Parse(str, ProxyStorage.FORMAT);
if (id != null && proxies.Any(kvp => kvp.Key == id))
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
return;
}
proxies.Add(new KeyValuePair<int?, ProxyData>(id, proxy));
}
catch (FormatException)
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
return;
}
}
foreach (var kvp in proxies)
{
ProxyStorage.SetProxy(kvp.Key, kvp.Value);
}
}
else
var input = AddProxyField;
if (string.IsNullOrEmpty(input)) return;
var split = input
.Split(Environment.NewLine)
.Where(s => string.IsNullOrWhiteSpace(s) == false)
.ToArray();
if (split.Length == 0) return;
bool? idPresent = null;
var proxies = new List<KeyValuePair<int?, ProxyData>>();
var i = 0;
foreach (var s in split.Where(s => string.IsNullOrWhiteSpace(s) == false))
{
i++;
var str = s;
int? id = null;
var input = AddProxyField;
if (IdRegex.IsMatch(AddProxyField))
var idMatch = IdRegex.Match(str);
if (idMatch.Success)
{
id = int.Parse(IdRegex.Match(AddProxyField).Groups[1].Value);
input = IdRegex.Replace(input, "");
id = int.Parse(idMatch.Groups[1].Value);
str = IdRegex.Replace(str, "");
}
ProxyData data;
try
idPresent ??= idMatch.Success;
if (idPresent.Value != idMatch.Success)
{
data = ProxyData.Parse(input, ProxyStorage.FORMAT);
}
catch (FormatException)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
return;
}
ProxyStorage.SetProxy(id, data);
if (ProxyStorage.DefaultScheme.TryParse(str, out var proxy))
{
if (id != null && proxies.Any(kvp => kvp.Key == id))
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
return;
}
proxies.Add(KeyValuePair.Create(id, proxy));
}
else
{
if (split.Length == 1)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
return;
}
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
return;
}
}
ProxyStorage.SetProxies(proxies);
ProxyStorage.OrderCollection();
AddProxyField = string.Empty;
CheckIfDefaultProxyStay();
}
private void CheckIfDefaultProxyStay()
{
if (!DefaultProxy.HasValue || Proxies.Any(kvp => kvp.Equals(DefaultProxy.Value))) return;
@@ -117,17 +108,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 +151,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]
-1
View File
@@ -1,6 +1,5 @@
using AutoUpdaterDotNET;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.VisualBasic.FileIO;
namespace NebulaAuth.ViewModel.Other;
+61 -13
View File
@@ -64,11 +64,16 @@
"ru": "Отпустите для импорта мафайлов",
"ua": "Відпустіть для імпорту мафайлів"
},
"LoadingHint":{
"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": {
@@ -132,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",
@@ -143,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",
@@ -156,7 +171,6 @@
}
}
},
"TradeTimerHint": {
"en": "Trade",
"ru": "Трейд",
@@ -228,6 +242,11 @@
},
"ContextMenus": {
"Mafile": {
"CopyLogin": {
"en": "Copy login",
"ru": "Скопировать логин",
"ua": "Скопіювати логін"
},
"AddToGroup": {
"en": "Add to group",
"ru": "Добавить в группу",
@@ -242,13 +261,13 @@
"Proxy": {
"Copy": {
"en": "Copy",
"ru": "Копировать",
"ua": "Копіювати"
"ru": "Скопировать",
"ua": "Скопіювати"
},
"CopyAddress": {
"en": "Copy address",
"ru": "Копировать адрес",
"ua": "Копіювати адресу"
"ru": "Скопировать адрес",
"ua": "Скопіювати адресу"
}
}
}
@@ -296,6 +315,11 @@
"ru": "Использовать индикатор",
"ua": "Використовувати індикатор"
},
"UseIndicatorHint": {
"en": "Small color indicator in windows toolbar to identify program instance",
"ru": "Маленький цветной индикатор в панели задач для идентификации экземпляра программы",
"ua": "Малий кольоровий індикатор в панелі завдань для ідентифікації екземпляра програми"
},
"UseCustomColor": {
"en": "Custom window color",
"ru": "Свой цвет окна",
@@ -369,7 +393,12 @@
"en": "Use mafile proxy",
"ru": "Использовать прокси из мафайла",
"ua": "Використовувати проксі з мафайла"
}
},
"ProxyToolTip": {
"en": "Press 'DEL' to remove",
"ru": "Нажмите 'DEL' для удаления",
"ua": "Натисніть 'DEL' для видалення"
}
},
"ProxyManagerDialog": {
"Title": {
@@ -381,8 +410,12 @@
"en": "Default proxy:",
"ru": "Прокси по умолчанию:",
"ua": "Проксі за замовчуванням:"
},
"UseRandomDefaultProxy": {
"en": "Use random default proxy",
"ru": "Cлучайный прокси по умолчанию",
"ua": "Випадковий проксі за замовчуванням"
}
},
"WaitLoginDialog": {
"Text": {
@@ -553,9 +586,9 @@
"ua": "помилки:"
},
"RemoveMafileConfirmation": {
"ru": "Удалить мафайл? Это действие переместит мафайл в mafiles_removed",
"en": "Remove mafile? This action will move mafile to mafiles_removed",
"ua": "Видалити мафайл? Ця дія перемістить мафайл в mafiles_removed"
"ru": "Удалить мафайл? Это действие переместит мафайл в папку 'mafiles_removed' (Это действие не удаляет Steam Guard с аккаунта)",
"en": "Remove mafile? This action will move mafile to 'mafiles_removed' (This action does not remove the Steam Guard from the account)",
"ua": "Видалити мафайл? Ця дія перемістить мафайл у каталог 'mafiles_removed' (Ця дія не видаляє автентифікатор з облікового запису)"
},
"CantRemoveAlreadyExist": {
"ru": "Не удалось переместить мафайл, возможно в папке уже существует мафайл с таким именем.",
@@ -596,7 +629,22 @@
"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 з мафайла та зберегти зміни. Потрібно зробити релогін. Скасовано"
},
"LoginCopied": {
"en": "Login copied",
"ru": "Логин скопирован",
"ua": "Логін скопійовано"
}
},
"ErrorTranslator": {
"Login": {
+4 -3
View File
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.4.7.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.4.7/NebulaAuth.1.4.7.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.4.7.html</changelog>
<version>1.5.2.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.2/NebulaAuth.1.5.2.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.2.html</changelog>
<mandatory>false</mandatory>
</item>
+39 -4
View File
@@ -1,9 +1,12 @@
using Newtonsoft.Json;
using AchiesUtilities.Models;
using AchiesUtilities.Newtonsoft.JSON.Converters.Special;
using Newtonsoft.Json;
using SteamLib.Authentication;
using SteamLib.Core;
using SteamLib.Core.Enums;
using SteamLib.Core.StatusCodes;
using SteamLib.Exceptions;
using SteamLib.Exceptions.General;
namespace SteamLib.Api;
@@ -27,7 +30,17 @@ public static class SteamGlobalApi
var cont = new FormUrlEncodedContent(data);
var resp = await client.PostAsync("https://login.steampowered.com/jwt/ajaxrefresh", cont, cancellationToken);
var respStr = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
var jwtRefresh = JsonConvert.DeserializeObject<JwtRefreshJson>(respStr);
JwtRefreshJson? jwtRefresh;
try
{
jwtRefresh = JsonConvert.DeserializeObject<JwtRefreshJson>(respStr);
}
catch (Exception ex)
{
throw new UnsupportedResponseException(respStr, ex);
}
if (jwtRefresh?.Success != true)
{
Exception? inner = null;
@@ -52,14 +65,26 @@ public static class SteamGlobalApi
cont = new FormUrlEncodedContent(data);
var update = await client.PostAsync(jwtRefresh.LoginUrl, cont, cancellationToken);
var updateResp = await update.Content.ReadAsStringAsync(cancellationToken);
if (updateResp != "{\"result\":1}")
JwtUpdateJson result;
try
{
result = JsonConvert.DeserializeObject<JwtUpdateJson>(updateResp) ?? throw new NullReferenceException();
}
catch (Exception ex)
{
throw new UnsupportedResponseException(updateResp, ex);
}
var resultStatus = SteamStatusCode.Translate<SteamStatusCode>(result.Result, out _);
if (resultStatus.Equals(SteamStatusCode.Ok) == false)
{
throw new SessionInvalidException(
"AjaxRefresh (set-token) response was not successful. Response string stored in Exception.Data")
{
Data = {{"Response", updateResp}}
Data = { { "Response", updateResp } }
};
}
return SteamTokenHelper.ExtractJwtFromSetCookiesHeader(update.Headers);
}
@@ -74,5 +99,15 @@ public static class SteamGlobalApi
[JsonProperty("auth")] public string Auth { get; set; } = string.Empty;
[JsonProperty("error")] public int? Error { get; set; }
}
private class JwtUpdateJson
{
[JsonProperty("result")]
public int Result { get; set; }
[JsonProperty("rtExpiry")]
[JsonConverter(typeof(UnixTimeStampConverter))]
public UnixTimeStamp RtExpiry { get; set; }
}
}
@@ -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,8 +66,8 @@ 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)
@@ -91,8 +92,8 @@ 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);
var domainCookieSet = false;
@@ -154,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)
@@ -198,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" />
@@ -44,10 +44,8 @@ public class SteamGuardCodeGenerator : ISteamGuardProvider
time >>= 8;
}
HMACSHA1 hmacGenerator = new()
{
Key = sharedSecret
};
using HMACSHA1 hmacGenerator = new();
hmacGenerator.Key = sharedSecret;
var hashedData = hmacGenerator.ComputeHash(timeArray);
var codeArray = new byte[5];
+1 -3
View File
@@ -46,9 +46,8 @@ public static class TimeAligner
var req = new HttpRequestMessage(HttpMethod.Post, SteamConstants.STEAM_API + TIME_ALIGN_ENDPOINT);
var response = client.Send(req).EnsureSuccessStatusCode();
sw.Stop();
var stream = new StreamReader(response.Content.ReadAsStream());
using var stream = new StreamReader(response.Content.ReadAsStream());
var respStr = stream.ReadToEnd();
stream.Dispose();
var j = JObject.Parse(respStr);
var time = j["response"]!["server_time"]!.Value<long>();
var now = UtcNow - sw.Elapsed;
@@ -80,7 +79,6 @@ public static class TimeAligner
}
finally
{
sw.Stop();
client.Dispose();
}
}
@@ -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;
+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>
+80
View File
@@ -0,0 +1,80 @@
<!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.1</div>
<div class="date">DATE</div>
<div class="description">
- FIX: Fixed proxy error (407) when {id} was parsed as a part of password when using 'ip:port:username:password{id}' proxy format <br />
- IMPROVEMENT: Now proxies always ordered by ID <br />
- FIX: Small fix in the proxy comparison method (sometimes proxy red indicator was not shown)
</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.5.2</div>
<div class="date">18.09.2024</div>
<div class="description">
- HOTFIX: Resolved an issue introduced by the September 18, 2024, Steam update, where an error occurred during session refresh due to changes in Steam's response model.<br />
- FIX: Potentially resolved a memory leak issue on some devices (Windows 10, 11 Home/Professional).<br />
- FIX: Minor performance improvements.<br />
- UI: Added a splash screen on application startup.<br />
- UI: Added "Copy Login" option in the account context menu on right-click.<br />
- UI: Minor localization updates.<br />
- UI: Added a tooltip to the "Use Indicator" setting.<br />
- UI: Minor design improvements.<br />
- UPDATE: Most libraries have been updated to their latest versions.
</div>
</div>
</div>
</body>
</html>