mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 22:31:42 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 769f102d16 | |||
| 80d7c09b0d | |||
| e6ae762d5a | |||
| 89ca08f6cf | |||
| e401bcccfd | |||
| 195ac95b36 | |||
| d1f660381e | |||
| c8aa7ba8e7 | |||
| 50a7d21d52 | |||
| aa092bdd67 | |||
| 6a1b03163c | |||
| 9a277d07db | |||
| fcd4056619 | |||
| 4cc69e9a57 | |||
| 672ca22662 | |||
| a9fdb58cac | |||
| 939fca31e9 | |||
| c5191d6a40 | |||
| 79f62f266c | |||
| b7a971b37e | |||
| 6c11d80508 | |||
| 4724a4c094 | |||
| 3297546ac2 | |||
| 760cfbf4f1 | |||
| 11a13174b8 | |||
| 7a8bdc5073 | |||
| bbcfe59f01 | |||
| e7c9473eb1 | |||
| 06f28b4f76 | |||
| dd331bf18d |
@@ -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
|
||||
@@ -12,6 +12,20 @@ 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
|
||||
changelog\1.5.1.html = changelog\1.5.1.html
|
||||
changelog\1.5.2.html = changelog\1.5.2.html
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.View;
|
||||
@@ -36,7 +37,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,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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,8 +17,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);
|
||||
//}
|
||||
}
|
||||
+30
-10
@@ -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=" "/><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,14 +129,15 @@
|
||||
<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.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}">
|
||||
@@ -146,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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
+121
-25
@@ -1,6 +1,9 @@
|
||||
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;
|
||||
@@ -8,8 +11,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Exceptions;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
@@ -23,6 +24,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 +38,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 +100,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 +128,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 +168,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 +186,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 +202,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 +215,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -8,42 +8,41 @@
|
||||
<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.2</AssemblyVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Theme\1140x641.jpg" />
|
||||
<None Remove="Theme\Background.jpg" />
|
||||
<None Remove="Theme\nebula lock.ico" />
|
||||
<None Remove="Theme\nebula.ico" />
|
||||
<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="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" />
|
||||
<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>
|
||||
<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 +50,16 @@
|
||||
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SplashScreen Include="Theme\SplashScreen.png" />
|
||||
</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: 413 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 490 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 906 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 213 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 126 KiB |
@@ -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}"/>
|
||||
|
||||
@@ -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,13 @@
|
||||
namespace NebulaAuth.View.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для LoginAgainDialog.xaml
|
||||
/// </summary>
|
||||
public partial class LoginAgainOnImportDialog
|
||||
{
|
||||
public LoginAgainOnImportDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}">
|
||||
|
||||
@@ -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='
0:', FallbackValue='X', Mode=OneWay}"/>
|
||||
<Run Text="{Binding DefaultProxy.Value.Address, Mode=OneWay, FallbackValue=''}"/>
|
||||
<Run Text="{Binding DefaultProxy.Key, StringFormat='
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"/>
|
||||
|
||||
@@ -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}" ToolTip="{Tr SettingsDialog.UseIndicatorHint}"/>
|
||||
<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>
|
||||
|
||||
@@ -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="
Вы используете версию"/>
|
||||
<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>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace NebulaAuth.View
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для UpdaterView.xaml
|
||||
/// </summary>
|
||||
public partial class UpdaterView : UserControl
|
||||
{
|
||||
public UpdaterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@ using System.Windows;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using SteamLib.Exceptions;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
using AutoUpdaterDotNET;
|
||||
|
||||
namespace NebulaAuth.ViewModel;
|
||||
|
||||
@@ -25,19 +28,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 +140,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;
|
||||
|
||||
}
|
||||
@@ -204,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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public partial class MainVM //Groups
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedGroup, value))
|
||||
PerformSearch();
|
||||
PerformQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public partial class MainVM //Groups
|
||||
set
|
||||
{
|
||||
if(SetProperty(ref _searchText, value))
|
||||
PerformSearch();
|
||||
PerformQuery();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,14 +62,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 +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);
|
||||
@@ -84,7 +89,7 @@ public partial class MainVM //Groups
|
||||
{
|
||||
SelectedGroup = null;
|
||||
}
|
||||
PerformSearch();
|
||||
PerformQuery();
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +104,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 +130,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 +142,11 @@ public partial class MainVM //Groups
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ResetQuery()
|
||||
{
|
||||
_selectedGroup = null;
|
||||
_searchText = string.Empty;
|
||||
PerformQuery();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -140,7 +139,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 +325,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,63 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
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]
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,19 @@ 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()
|
||||
{ }
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using AutoUpdaterDotNET;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public partial class UpdaterVM : ObservableObject
|
||||
{
|
||||
|
||||
public UpdateInfoEventArgs UpdateInfoEventArgs { get; }
|
||||
public UpdaterVM(UpdateInfoEventArgs args)
|
||||
{
|
||||
UpdateInfoEventArgs = args;
|
||||
}
|
||||
}
|
||||
@@ -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: ",
|
||||
@@ -211,6 +242,11 @@
|
||||
},
|
||||
"ContextMenus": {
|
||||
"Mafile": {
|
||||
"CopyLogin": {
|
||||
"en": "Copy login",
|
||||
"ru": "Скопировать логин",
|
||||
"ua": "Скопіювати логін"
|
||||
},
|
||||
"AddToGroup": {
|
||||
"en": "Add to group",
|
||||
"ru": "Добавить в группу",
|
||||
@@ -225,13 +261,13 @@
|
||||
"Proxy": {
|
||||
"Copy": {
|
||||
"en": "Copy",
|
||||
"ru": "Копировать",
|
||||
"ua": "Копіювати"
|
||||
"ru": "Скопировать",
|
||||
"ua": "Скопіювати"
|
||||
},
|
||||
"CopyAddress": {
|
||||
"en": "Copy address",
|
||||
"ru": "Копировать адрес",
|
||||
"ua": "Копіювати адресу"
|
||||
"ru": "Скопировать адрес",
|
||||
"ua": "Скопіювати адресу"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,7 +303,7 @@
|
||||
"DisableTimersOnSwitch": {
|
||||
"en": "Disable timers on account switch",
|
||||
"ru": "Отключать таймеры при смене аккаунта",
|
||||
"ua": "Вимикати таймери при зміні аккаунта"
|
||||
"ua": "Вимикати таймери при зміні акаунта"
|
||||
},
|
||||
"MinimizeToTray": {
|
||||
"en": "Minimize to tray",
|
||||
@@ -279,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": "Свой цвет окна",
|
||||
@@ -292,8 +333,8 @@
|
||||
},
|
||||
"Hint": {
|
||||
"en": "Doesn't saved on disk",
|
||||
"ru": "Не сохраняется в файл",
|
||||
"ua": "Не зберігається у файлах"
|
||||
"ru": "Не сохраняется в системе",
|
||||
"ua": "Не зберігається у системі"
|
||||
}
|
||||
},
|
||||
"LegacyMafileMode": {
|
||||
@@ -310,6 +351,11 @@
|
||||
"en": "Allow auto update",
|
||||
"ru": "Разрешить автообновление",
|
||||
"ua": "Дозволити автооновлення"
|
||||
},
|
||||
"UseAccountName": {
|
||||
"en": "Use account name on mafiles",
|
||||
"ru": "Использовать имя аккаунта на мафайлах",
|
||||
"ua": "Використовувати ім'я акаунта на мафайлах"
|
||||
}
|
||||
},
|
||||
"LoginAgainDialog": {
|
||||
@@ -342,7 +388,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 +410,12 @@
|
||||
"en": "Default proxy:",
|
||||
"ru": "Прокси по умолчанию:",
|
||||
"ua": "Проксі за замовчуванням:"
|
||||
},
|
||||
"UseRandomDefaultProxy": {
|
||||
"en": "Use random default proxy",
|
||||
"ru": "Cлучайный прокси по умолчанию",
|
||||
"ua": "Випадковий проксі за замовчуванням"
|
||||
}
|
||||
|
||||
},
|
||||
"WaitLoginDialog": {
|
||||
"Text": {
|
||||
@@ -526,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": "Не удалось переместить мафайл, возможно в папке уже существует мафайл с таким именем.",
|
||||
@@ -564,7 +624,27 @@
|
||||
"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 з мафайла та зберегти зміни. Потрібно зробити релогін. Скасовано"
|
||||
},
|
||||
"LoginCopied": {
|
||||
"en": "Login copied",
|
||||
"ru": "Логин скопирован",
|
||||
"ua": "Логін скопійовано"
|
||||
}
|
||||
},
|
||||
"ErrorTranslator": {
|
||||
"Login": {
|
||||
@@ -626,7 +706,7 @@
|
||||
"AccountNotFound": {
|
||||
"ru": "Аккаунт не найден (18)",
|
||||
"en": "Account not found (18)",
|
||||
"ua": "Аккаунт не знайдено (18)"
|
||||
"ua": "Акаунт не знайдено (18)"
|
||||
},
|
||||
"InvalidSteamID": {
|
||||
"ru": "Неправильный SteamID (19)",
|
||||
@@ -661,7 +741,7 @@
|
||||
"AccountDisabled": {
|
||||
"ru": "Аккаунт отключен (43)",
|
||||
"en": "Account disabled (43)",
|
||||
"ua": "Аккаунт відключено (43)"
|
||||
"ua": "Акаунт відключено (43)"
|
||||
|
||||
},
|
||||
"Suspended": {
|
||||
@@ -684,7 +764,7 @@
|
||||
"AccountLockedDown": {
|
||||
"ru": "Аккаунт заблокирован (КТ 73)",
|
||||
"en": "Account locked down (73)",
|
||||
"ua": "Аккаунт заблоковано (КТ 73)"
|
||||
"ua": "Акаунт заблоковано (КТ 73)"
|
||||
|
||||
},
|
||||
"UnexpectedError": {
|
||||
@@ -732,7 +812,7 @@
|
||||
"AccountLimitExceeded": {
|
||||
"ru": "Лимит аккаунта превышен (95)",
|
||||
"en": "Account limit exceeded (95)",
|
||||
"ua": "Ліміт аккаунта перевищено (95)"
|
||||
"ua": "Ліміт акаунта перевищено (95)"
|
||||
|
||||
},
|
||||
"EmailSendFailure": {
|
||||
@@ -756,12 +836,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 +941,13 @@
|
||||
}
|
||||
|
||||
},
|
||||
"SessionHandler": {
|
||||
"SessionWasRefreshedAutomatically": {
|
||||
"ru": "Сессия была обновлена автоматически",
|
||||
"en": "Session was refreshed automatically",
|
||||
"ua": "Сесія була оновлена автоматично"
|
||||
}
|
||||
},
|
||||
"ProxyManagerVM": {
|
||||
"WrongFormatSomeIdsMissing": {
|
||||
"ru": "Неверный формат. Некоторые прокси не имеют ID",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?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.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>
|
||||
@@ -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**.
|
||||
|
||||
## Использование
|
||||
|
||||

|
||||
|
||||
|
||||
1. Панель управления.
|
||||
- управление файлами и настройки
|
||||
- управление аккаунтом (вход, привязка, отвязка)
|
||||
- группировка
|
||||
- выбор прокси
|
||||
- индикатор с подсказкой об используемом прокси (светится либо желтым, либо красным, при наведении отобразит дополнительную информацию)
|
||||
- таймеры для автоматического подтверждения трейдов/продаж на торговой площадке
|
||||
- как часто проверять подтверждения при включенных таймерах (в секундах)
|
||||
2. Список ваших аккаунтов
|
||||
3. Код подтверждения входа (нажмите, чтобы скопировать)
|
||||
4. Главное окно подтверждений
|
||||
5. Поиск по логину или SteamID (7xxxxxxxxxxxxx)
|
||||
6. Подтвердить вход с другого устройства.
|
||||
7. Гиперссылка на официальную страницу приложения с указанием авторства.
|
||||
|
||||
## Настройки
|
||||

|
||||
|
||||
|
||||
1. Режим фона. Переключите, если вы хотите отключить фон или установить собственный (поместите файл «Background.png» в папку приложения)
|
||||
2. Язык локализации
|
||||
3. Отключить таймеры при переключении между аккаунтами
|
||||
4. Скрывать в трей при сворачивании
|
||||
5. Индикатор с цветом. Маленький кружочек на значке панели задач с произвольным цветом. Полезно при использовании нескольких окон.
|
||||
6. Пользовательский цвет приложения.
|
||||
7. Текущий пароль шифрования. Если установлено, вы можете сохранять зашифрованные пароли в mafile, чтобы облегчить повторный вход в систему при проблемах с сеансом. (Не рекомендуется)
|
||||
8. Режим устаревших файлов. Режим совместимости Mafile. Если установлено, приложение будет сохранять файлы в старом стандартном формате (по умолчанию: включено).
|
||||
9. Разрешить автообновление без подтверждения
|
||||
|
||||
|
||||
|
||||
## [Лицензия](/LICENSE.md)
|
||||
|
||||
Коммерческое использование запрещено. При распространении измененного кода необходимо указывать оригинальное авторство.
|
||||
@@ -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**.
|
||||
|
||||
## Використання
|
||||
|
||||

|
||||
|
||||
1. Панель керування.
|
||||
- керування файлами та налаштування
|
||||
- керування акаунтом (вхід, прив'язка, відв'язування)
|
||||
- угруповання
|
||||
- Вибір проксі
|
||||
- індикатор з підказкою про проксі (світиться або жовтим, або червоним, при наведенні відобразить додаткову інформацію)
|
||||
- таймери для автоматичного підтвердження трейдів/продажів на торговому майданчику
|
||||
- як часто перевіряти підтвердження при увімкнених таймерах (у секундах)
|
||||
2. Список ваших облікових записів
|
||||
3. Код підтвердження входу (натисніть, щоб скопіювати)
|
||||
4. Головне вікно підтвердження
|
||||
5. Пошук за логіном або SteamID (7xxxxxxxxxxxxx)
|
||||
6. Підтвердити вхід із іншого пристрою.
|
||||
7. Гіперпосилання на офіційну сторінку додатку із зазначенням авторства.
|
||||
|
||||
## Налаштування
|
||||

|
||||
|
||||
1. Режим фону. Перемкніть, якщо ви хочете вимкнути фон або встановити власний (помістіть файл "Background.png" у папку програми)
|
||||
2. Мова локалізації
|
||||
3. Вимкнути таймери під час перемикання між обліковими записами
|
||||
4. Приховувати у трей при згортанні
|
||||
5. Індикатор із кольором. Маленький кружечок на піктограмі панелі завдань з довільним кольором. Корисно при використанні кількох вікон.
|
||||
6. Власний колір програми.
|
||||
7. Поточний пароль шифрування. Якщо встановлено, ви можете зберігати зашифровані паролі в mafile, щоб полегшити повторний вхід до системи у разі проблеми з сеансом. (Не рекомендується)
|
||||
8. Режим застарілих файлів. Режим сумісності Mafile. Якщо встановлено, програма зберігатиме файли у старому стандартному форматі (за замовчуванням: увімкнено).
|
||||
9. Дозволити автооновлення без підтвердження
|
||||
|
||||
|
||||
|
||||
## [Ліцензія](/LICENSE.md)
|
||||
|
||||
Комерційне використання заборонено. У разі поширення зміненого коду необхідно вказувати оригінальне авторство.
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
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
|
||||

|
||||
|
||||
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.
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ public static class ClientBuilder
|
||||
}
|
||||
else
|
||||
{
|
||||
container.SetSteamMobileCookies(sessionData);
|
||||
container.SetSteamMobileCookiesWithMobileToken(sessionData);
|
||||
}
|
||||
|
||||
ConfigureCommon(handler, client);
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 />
|
||||
- Favourite button is placed on proxy item<br />
|
||||
- Pressing 'DEL' now removes selected proxy<br />
|
||||
- After proxy is removed next to it is selected (allows to remove proxies quickly)<br />
|
||||
- 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user