Compare commits

...

39 Commits

Author SHA1 Message Date
github-actions 5de26381bb chore(release): 1.8.1 2026-01-25 17:25:47 +00:00
Achies 16fd4992f8 Merge pull request #14 from achiez/1.8.1
Release 1.8.1 → Merge into master
2026-01-25 19:25:39 +02:00
achiez 7835a53717 Prepare 1.8.1
- Added changelog/1.8.1.html with detailed update notes and links
- Improved MAAC auto-confirmation logic: now uses IsReady() for smarter account readiness checks and retries
- Added GetReadyAccounts() to MultiAccountAutoConfirmer for flexible account selection
- Fixed localization lookup in GetPortableMaClientStatus
- Set PortableMaClient initial status to Ok instead of test error
2026-01-25 19:21:50 +02:00
achiez f15632f2d0 Refactored MAAC error/status handling and UI feedback
- Introduced centralized MAACRequestHandler for error tracking, retries, and status escalation (Ok/Warning/Error) per account
- PortableMaClient now uses new Status property (PortableMaClientStatus) instead of IsError; error history tracked in PortableMaClientErrorData
- All MAAC requests now routed through MAACRequestHandler, providing unified error handling and retry logic
- MultiAccountAutoConfirmer now filters accounts by Status.StatusType == Ok
- Updated MainWindow UI: account name color reflects status (Success/Warning/Error); added "Unattach proxy" to context menu
- Removed obsolete PortableMaClientStatusToColorConverter and related bindings
- Settings: removed IgnorePatchTuesdayErrors, added MaacErrorThreshold and MaacRetryInterval for error escalation control
- Refactored SessionHandler: split into logic, API, and helpers; improved exception propagation and session management
- AdmissionHelper methods now handle null sessions and always transfer community cookies if session is missing
- Mafile export: always preserves SteamId, even if session data is not exported
- ViewModel: RemoveProxy command now parameterized and only enabled if Mafile has a proxy
- Enabled concurrent log writing in NLog config
- Minor code cleanups and improved exception signatures
2026-01-25 18:48:11 +02:00
achiez b56c0e578d Fix checkbox margin accordingly to new MaterialDesignInXaml update 2026-01-19 21:36:42 +02:00
achiez 969590d9f2 Add MafileExport feature and Perform Resharper Cleanup 2026-01-19 21:28:44 +02:00
achiez 3e87a0d50e Improve localization structure and add missing translations
Thanks to @77155331 for Chinese translation (Issue #11)
Thanks to @klNuno for French localization (PR #13)
2026-01-02 22:43:24 +02:00
achiez 690c98527f Update mafiles removal logic and changelog URL generation
- Fixed crash: MaFiles collection in MainVM is now a new ObservableCollection. Manual removal from collection is omitted
- Improved logic for moving mafiles to "removed": file extension is now always correct and name conflicts are handled more reliably
- Github workflow: Changelog URL in update.xml now uses repository name only for correct links
- On MaFiles collection change, PerformQuery is called to update search results
- Fixed parameter order in mafiles renaming result string to match localization
2026-01-02 22:09:31 +02:00
achiez 5242b0009b Refactor mafile storage: async, modular, bulk rename
- Fix crash: Updated SaveMafileAsync to properly update or add to MaFiles collection
- Fix naming logic: Added Filename reset logic to adding new mafile to prevent inconsistent and duplicating import
- Extracted mafile path and naming logic to MafilesStorageHelper for better modularity
- Refactored Storage methods to use MafilesStorageHelper
- Replaced all sync mafile save/add calls with async/await versions throughout the codebase
- Performed code cleanups and improved maintainability for mafile-related operations
2026-01-02 21:23:11 +02:00
Achies 1f14bd77e7 chore(update.xml) Fix changelog URL in update.xml 2025-11-14 22:35:39 +02:00
achiez f2b1cdfcc9 Fix MAACStorage.cs
- fixed collection was untracked if no `maac.json` file was created
- fixed incorrect change tracking for persisted accounts after reboot
2025-11-07 21:58:52 +02:00
github-actions ad1df3227b chore(release): 1.8.0 2025-11-07 14:32:28 +00:00
Achies 05f132ab1b Merge pull request #10 from achiez/1.8.0
1.8.0
2025-11-07 16:32:21 +02:00
achiez ee858921dc (chore) Prepare 1.8.0 Release 2025-11-07 16:31:37 +02:00
achiez 750292bfba Prepare for 1.8.0 release: Simplified CI/CD and localization updates
- Replaced old build-and-release.yml with a streamlined process in build-release.yml, simplifying release creation with dotnet publish, changelog conversion, and GitHub release automation.
- Added prepare-release.yml to automate release preparation, including version extraction, update.xml generation, and tagging.
- Introduced changelog for version 1.8.0 with a new HTML file (1.8.0.html) and external links to detailed updates.
- Updated NebulaAuth.sln to include the new changelog file.
- Refactored SetAccountPasswordsVM.cs to use localization for success messages and added a helper method for retrieving localized strings.
- Enhanced localization.loc.json with new strings for SetAccountPasswordsVM and improved formatting by removing duplicates.
- Improved changelog HTML structure and styles for better readability and mobile responsiveness.
- Performed minor code cleanups and formatting adjustments.
2025-11-07 16:31:01 +02:00
achiez 982bb4d0c8 [chore] Perform ReSharper cleanup 2025-11-07 10:35:10 +02:00
achiez 8e5fea54cd Added MAAC persistence support and password management features
- Introduced `MAACStorage` for managing multi-account auto-confirmers persistence
- Added `SetAccountPasswordsView` dialog for managing account passwords setup
- Enhanced `Storage` with async methods for file operations
- Added `BoolToValueConverter` for flexible XAML data bindings
- Enhanced user experience in dialogs with `IsDefault` and `IsCancel` properties
2025-11-07 10:27:16 +02:00
achiez 882d39b8f3 Refactor and enhance UI, localization, and file name handling
- Refactored file handling: added backup directories, improved mafile naming strategy, and introduced renaming functionality.
- Enhanced UI: updated `MainWindow`, `SettingsView`, and dialogs with new controls, commands, and improved layouts.
- Introduced `TextFieldDialog` for user input with localization support.
- Improved localization: added new strings for buttons, errors, and tooltips. Removed all user-observed not localized strings
- Fixed clipboard handling logic in `ClipboardHelper`.
- Added 'Create Group' button in mafile's context menu and corresponding dialog view
- Implemented `RequiresAdminAccess` check before updating program and requesting previlegies
2025-11-06 18:44:43 +02:00
achiez ffcc7405a7 Refactor and enhance proxy and mafile handling
- Introduced `Filename` property in `Mafile` for better file management.
- Refactored `Storage` methods to support `Filename` and simplify `.mafile` handling.
- Enhanced `ProxyManagerView` with new controls for protocol and credentials display.
- Implemented `HintBox` control for displaying errors and tips in UI.
- Updated `MainWindow` with improved UI elements and event handling.
- Improved command execution checks in `MainVM` for better UX.
- Added localization strings for new features like proxy display options.
- General code cleanups and formatting improvements.
2025-11-03 22:44:46 +02:00
achiez b3e7436e95 Remove FontScaleWindow.cs and residual related code 2025-10-29 11:17:25 +02:00
achiez d319fc19f9 Refine UX, fix auth crash, and revert to .NET 8.0
- Reverted target framework to .NET 8.0 for stability.
- Added autofocus to all dialog input fields (e.g. password dialogs).
- Fixed crash when relogging into accounts without Steam Guard and added proper error message.
- Unified and improved “Confirm Action” dialog design and text clarity.
- Added setting to disable ripple effect animations for better performance.
- Reduced minimum auto-confirmation timer interval from 10s to 5s.
- App now auto-selects the most appropriate language on first launch.
- Added trimming to proxy parse input
- Fixed incorrect hint about maFile binding when using login-based mode.
- Added ESC key behavior to remove focus from account search
- Minor UI polish and cleanup across dialogs.
2025-10-08 18:08:38 +03:00
achiez a56757302e Resolved issue where a "Confirmation Error" notification appeared despite the confirmation being successful 2025-07-19 00:11:42 +03:00
achiez 1ad30ba49d 1.7.3 Add automatic ack on confirming Trades accordingly to new update 2025-07-17 22:49:22 +03:00
achiez 04e2188437 bump version to 1.7.2 2025-07-10 13:35:56 +03:00
achiez b4c4f52fd1 Fix: reintroduce bug from incorrect merge, now fully resolved
Previously fixed bug was unintentionally reintroduced due to merging the wrong branch. This commit properly resolves the issue.
2025-07-10 13:31:44 +03:00
achiez f079488840 temporary return old update.xml path 2025-07-09 22:39:08 +03:00
achiez 4bae3f0250 Merge 1.7.1 to master 2025-07-09 22:33:33 +03:00
achiez 4c9ec85d6f bump version to 1.7.1 2025-07-09 22:27:49 +03:00
achiez 4761fc70fe 1.7.1: Merge 1.7.0 version to release versions 2025-07-09 22:26:00 +03:00
achiez 3abc12f4bc Add support to new confirmation type (Purchase) and improve localization and snackbars 2025-07-09 22:19:21 +03:00
achiez 99982cacb6 Fix "Load Confirmations" crash caused by wrong order in resource dictionaries 2025-07-02 00:56:28 +03:00
achiez 344ab81728 Comment "Debug" button for release purposes 2025-06-23 16:46:18 +03:00
achiez 2e8862be8b feat(ui): massive UI overhaul and theming support for 1.7.0 pre-release
- Reworked UI color scheme and added 5 new color themes
- Replaced app icon and redesigned splash screen
- Introduced "Theme" settings tab with support for transparency, blur, and background dimming
- Removed legacy "Window Color" setting
- Added helpful links window via "by achies" label
- Updated fonts and added iconography across UI

feat(account): full redesign of account linking flow

- New UX with input validation and retry logic
- Improved mobile device linking process and error handling

feat(security): Steam Guard transfer implemented
perf(startup): parallel mafile loading on app startup for large file counts

fix(core): resolved password prompt on minimized window
fix(ui): fixed splash screen blinking on startup
fix(network): mitigated effect of Steam error 429 (IP ban) on login/session

chore: upgraded codebase to .NET 9.0
2025-06-23 16:41:15 +03:00
achiez 9a43166d49 Add SplashScreen base 2025-05-08 00:21:34 +03:00
achiez 3f54df252c Move projects to ./src 2025-05-03 17:41:33 +03:00
Achies 2054bd12d5 Merge pull request #7 from achiez/master-temp
Reflect 1.5.6 updates in 2.0
2025-05-03 00:42:48 +03:00
Achies 8ddec3cdfe Merge branch '2.0' into master-temp 2025-05-03 00:42:36 +03:00
achiez 89d7fcb816 perform resharper cleanups 2025-05-03 00:09:07 +03:00
achiez 31c38ac8ad initial 2025-05-02 19:57:11 +03:00
327 changed files with 12559 additions and 7050 deletions
-108
View File
@@ -1,108 +0,0 @@
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
+78
View File
@@ -0,0 +1,78 @@
name: Publish release
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
release:
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract version
id: ver
run: echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
shell: bash
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Build NebulaAuth
run: dotnet publish src/NebulaAuth/NebulaAuth.csproj -c Release -o build
- name: Package release
run: |
cd build
powershell Compress-Archive * "../NebulaAuth.${env:GITHUB_REF_NAME}.zip"
shell: pwsh
- name: Install pandoc
run: |
choco install pandoc -y
- name: Convert changelog HTML to Markdown (ul-only)
id: changelog
shell: bash
run: |
version="${{ steps.ver.outputs.version }}"
file="changelog/${version}.html"
echo "Looking for file: $file"
if [ -f "$file" ]; then
echo "✅ File found, extracting <ul> section"
sed -n '/<ul>/,/<\/ul>/p' "$file" > body.html
echo "Converting with pandoc..."
pandoc body.html -f html -t markdown --wrap=none -o changelog.md
echo "-------- Converted changelog.md --------"
cat changelog.md
echo "----------------------------------------"
echo "body<<EOF" >> $GITHUB_OUTPUT
cat changelog.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "❌ Changelog not found for version $version"
echo "body=No changelog found for $version" >> $GITHUB_OUTPUT
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.ver.outputs.version }}
name: Release ${{ env.RELEASE_NAME }}
body: ${{ steps.changelog.outputs.body }}
files: |
NebulaAuth.${{ steps.ver.outputs.version }}.zip
env:
RELEASE_NAME: ${{ steps.ver.outputs.version }}
+65
View File
@@ -0,0 +1,65 @@
name: Prepare release
on:
push:
branches: [ master ]
paths:
- 'src/NebulaAuth/NebulaAuth.csproj'
workflow_dispatch:
jobs:
prepare:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.PAT_TOKEN }}
- name: Read version from csproj
id: ver
shell: bash
run: |
VERSION=$(grep -oPm1 "(?<=<AssemblyVersion>)[^<]+" src/NebulaAuth/NebulaAuth.csproj)
echo "Detected version $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Generate update.xml
run: |
cat > NebulaAuth/update.xml <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>${{ steps.ver.outputs.version }}</version>
<url>https://github.com/${{ github.repository }}/releases/download/${{ steps.ver.outputs.version }}/NebulaAuth.${{ steps.ver.outputs.version }}.zip</url>
<changelog>https://achiez.github.io/${{ github.event.repository.name }}/changelog/${{ steps.ver.outputs.version }}.html</changelog>
<mandatory>false</mandatory>
</item>
EOF
echo "Generated NebulaAuth/update.xml:"
cat NebulaAuth/update.xml
- name: Check if update.xml changed
id: diff
run: |
if git diff --exit-code NebulaAuth/update.xml >/dev/null 2>&1; then
echo "no_changes=true" >> $GITHUB_OUTPUT
else
echo "no_changes=false" >> $GITHUB_OUTPUT
fi
- name: Commit and tag
if: steps.diff.outputs.no_changes == 'false'
run: |
git config user.name "github-actions"
git config user.email "actions@github.com"
git add NebulaAuth/update.xml
git commit -m "chore(release): ${{ steps.ver.outputs.version }}"
git tag -a "${{ steps.ver.outputs.version }}" -m "Release ${{ steps.ver.outputs.version }}"
git push origin HEAD:master --tags
- name: Skip message
if: steps.diff.outputs.no_changes == 'true'
run: echo "No changes in version or update.xml - skipping release preparation."
+3 -1
View File
@@ -360,4 +360,6 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
todo/
+24 -22
View File
@@ -3,15 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33205.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAuth", "NebulaAuth\NebulaAuth.csproj", "{0FD01700-6D5C-451B-93BA-0860647E8F13}"
ProjectSection(ProjectDependencies) = postProject
{09F02072-F91D-4DAA-87BC-A34D3E150570} = {09F02072-F91D-4DAA-87BC-A34D3E150570}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAuth.LegacyConverter", "NebulaAuth.LegacyConverter\NebulaAuth.LegacyConverter.csproj", "{2D78A7D9-986A-4890-8A91-7ABD57A91830}"
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
@@ -28,27 +19,38 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
changelog\1.5.4.html = changelog\1.5.4.html
changelog\1.5.5.html = changelog\1.5.5.html
changelog\1.5.6.html = changelog\1.5.6.html
changelog\1.5.7.html = changelog\1.5.7.html
changelog\1.7.1.html = changelog\1.7.1.html
changelog\1.7.2.html = changelog\1.7.2.html
changelog\1.7.3.html = changelog\1.7.3.html
changelog\1.7.4.html = changelog\1.7.4.html
changelog\1.8.0.html = changelog\1.8.0.html
changelog\1.8.1.html = changelog\1.8.1.html
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamLibForked", "src\SteamLibForked\SteamLibForked.csproj", "{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NebulaAuth.LegacyConverter", "src\NebulaAuth.LegacyConverter\NebulaAuth.LegacyConverter.csproj", "{C8235305-E5C4-155B-C718-C0F239CA3AB7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NebulaAuth", "src\NebulaAuth\NebulaAuth.csproj", "{C42F63B6-32F7-ED08-5B86-CD51989761AD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Release|Any CPU.Build.0 = Release|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Release|Any CPU.Build.0 = Release|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Release|Any CPU.Build.0 = Release|Any CPU
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Release|Any CPU.Build.0 = Release|Any CPU
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Release|Any CPU.Build.0 = Release|Any CPU
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
-51
View File
@@ -1,51 +0,0 @@
<Application x:Class="NebulaAuth.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:background="clr-namespace:NebulaAuth.Converters.Background"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="MaterialDesignPaper">#1E2025</SolidColorBrush>
<FontFamily x:Key="Roboto">pack://application:,,,/Fonts/Roboto/#Roboto</FontFamily>
<FontFamily x:Key="RobotoSymbols">pack://application:,,,/Fonts/Roboto/#Roboto Symbols</FontFamily>
<converters:CoefficientConverter x:Key="CoefficientConverter" />
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter" />
<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" />
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter" />
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter" />
<system:Boolean x:Key="True">True</system:Boolean>
<system:Boolean x:Key="False">False</system:Boolean>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="LightGreen" SecondaryColor="Purple" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.snackbar.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.Dark.xaml" />
<!-- Theme-->
<ResourceDictionary Source="Theme/Brushes.xaml" />
<ResourceDictionary Source="Theme/WindowStyle/WindowStyle.xaml" />
<!--Controls-->
<ResourceDictionary Source="View/ConfirmationTemplates.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
@@ -1,43 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.Converters;
public class ProxyTextConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not MaProxy p)
{
return string.Empty;
}
return $"{p.Id}: {p.Data.Address}:{p.Data.Port}";
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
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)
{
throw new NotSupportedException();
}
}
-401
View File
@@ -1,401 +0,0 @@
<w:FontScaleWindow x:Class="NebulaAuth.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:w="clr-namespace:NebulaAuth.Theme"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:windowStyle="clr-namespace:NebulaAuth.Theme.WindowStyle"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:viewModel="clr-namespace:NebulaAuth.ViewModel"
WindowStartupLocation="CenterScreen"
MinHeight="500" MinWidth="500" DefaultFontSize="18" ScaleCoefficient="0.4"
Title="NebulaAuth" Height="800" Width="730" Style="{StaticResource MainWindow}"
RenderOptions.BitmapScalingMode="HighQuality" Foreground="#FFF5F5F5"
FontFamily="{md:MaterialDesignFont}"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModel:MainVM}">
<b:Interaction.Behaviors>
<windowStyle:WindowChromeRenderedBehavior />
</b:Interaction.Behaviors>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=PasteMafilesFromClipboardCommand}"
Key="V"
Modifiers="Control" />
</Window.InputBindings>
<Border Name="DragNDropBorder" Panel.ZIndex="3" Opacity="1" AllowDrop="True"
DragDrop.DragEnter="Rectangle_DragEnter" DragLeave="Rectangle_DragLeave" Drop="Rectangle_Drop">
<md:DialogHost DialogOpened="DialogHost_DialogOpened" DialogClosed="DialogHost_DialogClosed">
<Grid ZIndex="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.RowSpan="2" Opacity="0.6" Stretch="UniformToFill"
RenderOptions.BitmapScalingMode="LowQuality" HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}"
Source="{Binding Settings.BackgroundMode, Mode=OneWay, Converter={StaticResource BackgroundSourceConverter}}" />
<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" Text="{Tr MainWindow.Global.DragNDropHint}" />
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36"
Kind="FileReplaceOutline" />
</StackPanel>
<ToolBarTray IsLocked="True" Grid.Row="0">
<ToolBarTray.Background>
<SolidColorBrush Opacity="0.6" Color="#FF1A1C25" />
</ToolBarTray.Background>
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}"
w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.File.Import}"
Command="{Binding AddMafileCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}"
Header="{Tr MainWindow.Menu.File.Remove}"
Command="{Binding RemoveMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}"
Command="{Binding OpenMafileFolderCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}"
Command="{Binding OpenSettingsDialogCommand}" />
</MenuItem>
</Menu>
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}"
Command="{Binding LinkAccountCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}"
Command="{Binding RemoveAuthenticatorCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem IsEnabled="{Binding IsMafileSelected}"
Header="{Tr MainWindow.Menu.Account.RefreshSession}"
Command="{Binding RefreshSessionCommand}" />
<MenuItem IsEnabled="{Binding IsMafileSelected}"
Header="{Tr MainWindow.Menu.Account.LoginAgain}"
Command="{Binding LoginAgainCommand}" />
</MenuItem>
</Menu>
<Separator />
<ComboBox ToolTip="{Tr MainWindow.AppBar.GroupToolTip}"
md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}"
MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
ItemsSource="{Binding Groups}"
SelectedValue="{Binding SelectedGroup}">
<FrameworkElement.Resources>
<ResourceDictionary>
<Style TargetType="md:PackIcon">
<Setter Property="Width" Value="15" />
<Setter Property="Height" Value="15" />
</Style>
</ResourceDictionary>
</FrameworkElement.Resources>
<UIElement.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}"
CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
</UIElement.InputBindings>
</ComboBox>
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}"
MinWidth="80"
Margin="8,0,8,0"
VerticalAlignment="Center"
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
md:ComboBoxAssist.ShowSelectedItem="False"
SelectedValue="{Binding SelectedProxy}"
ItemsSource="{Binding Proxies}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
<TextBlock
Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.Proxy.ProxyOpenManagerHint}"
Command="{Binding OpenProxyManagerCommand}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
<UIElement.InputBindings>
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
</UIElement.InputBindings>
</ComboBox>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
Foreground="#FFFF0000" Margin="3" ToolTipService.InitialShowDelay="300">
<md:PackIcon.ToolTip>
<TextBlock>
<Run
Text="{Tr MainWindow.AppBar.Proxy.ProxyAlert.MafileProxyInUse, IsDynamic=False}" />
<Run Text="&#10;" />
<Run Text="{Binding SelectedMafile.Proxy.Data, FallbackValue='', Mode=OneWay}" />
</TextBlock>
</md:PackIcon.ToolTip>
<UIElement.Visibility>
<Binding Path="ProxyExist">
<Binding.Converter>
<converters:ValueConverterGroup>
<converters:ReverseBooleanConverter />
<BooleanToVisibilityConverter />
</converters:ValueConverterGroup>
</Binding.Converter>
</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="300"
Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}"
Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}"
IsEnabled="{Binding IsMafileSelected}" Margin="2"
Style="{StaticResource MaterialDesignFlatToggleButton}"
IsChecked="{Binding MarketTimerEnabled}">
<md:PackIcon Kind="ShoppingCart" />
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}"
Command="{Binding Path=SwitchMAACOnGroupCommand}"
CommandParameter="{StaticResource True}" />
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}"
Command="{Binding Path=SwitchMAACOnAllCommand}"
CommandParameter="{StaticResource True}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
</ToggleButton>
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}"
Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}"
IsEnabled="{Binding IsMafileSelected}" Margin="2"
Style="{StaticResource MaterialDesignFlatToggleButton}"
IsChecked="{Binding TradeTimerEnabled}">
<md:PackIcon Kind="AccountArrowRight" />
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}"
Command="{Binding Path=SwitchMAACOnGroupCommand}"
CommandParameter="{StaticResource False}" />
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}"
Command="{Binding Path=SwitchMAACOnAllCommand}"
CommandParameter="{StaticResource False}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
</ToggleButton>
<TextBox 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" />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}"
HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}"
Foreground="WhiteSmoke" VerticalAlignment="Center"
Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
<md:PackIcon Kind="Accounts" />
</ToggleButton>
</ToolBar>
</ToolBarTray>
<Grid Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.4*" />
<ColumnDefinition Width="0.6*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Grid.Row="0"
Margin="10,15,0,0" ItemsSource="{Binding MaFiles}"
SelectedValue="{Binding SelectedMafile}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch" Text="{Binding AccountName}">
<TextBlock.Foreground>
<Binding Mode="OneWay" Path="LinkedClient.IsError"
Converter="{StaticResource PortableMaClientStatusToColorConverter}">
<Binding.FallbackValue>
<SolidColorBrush Color="WhiteSmoke" />
</Binding.FallbackValue>
</Binding>
</TextBlock.Foreground>
</TextBlock>
<StackPanel
Visibility="{Binding LinkedClient, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Column="1" Orientation="Horizontal">
<md:PackIcon Kind="ShoppingCart"
Visibility="{Binding LinkedClient.AutoConfirmMarket, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
<md:PackIcon Kind="AccountArrowRight"
Visibility="{Binding LinkedClient.AutoConfirmTrades, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.InputBindings>
<KeyBinding Key="C" Modifiers="Control"
Command="{Binding DataContext.CopyLoginCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
<KeyBinding Key="X" Modifiers="Control"
Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
</ListBox.InputBindings>
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem InputGestureText="Ctrl+C"
Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}"
Command="{Binding CopyLoginCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}"
Command="{Binding CopySteamIdCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem InputGestureText="Ctrl+X"
Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}"
Command="{Binding CopyMafileCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}"
ItemsSource="{Binding Groups}">
<ItemsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignMenuItem}"
TargetType="{x:Type MenuItem}">
<Setter Property="Command"
Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding
Converter="{StaticResource MultiCommandParameterConverter}">
<Binding />
<Binding Path="PlacementTarget.SelectedValue"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}"
Command="{Binding Path=RemoveGroupCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
Command="{Binding Path=CopyPasswordCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</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}" />
</Border>
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7"
w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10,5,10,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">
<Control.Background>
<SolidColorBrush Color="#FF1A1D25" Opacity="0.6" />
</Control.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Row="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True"
IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF"
Style="{StaticResource MaterialDesignFilledTextBox}"
Text="{Binding Code, FallbackValue=Code}">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
</TextBox.InputBindings>
</TextBox>
<Grid Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar Margin="5" Foreground="#FF9932CC" Height="15"
md:TransitionAssist.DisableTransitions="True"
Value="{Binding CodeProgress}" />
</Grid>
</Grid>
<Button IsEnabled="{Binding IsMafileSelected}" w:FontScaleWindow.Scale="0.7"
w:FontScaleWindow.ResizeFont="True" Grid.Row="1"
Style="{StaticResource MaterialDesignOutlinedButton}" Margin="10"
Command="{Binding GetConfirmationsCommand}"
Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}">
<TextBlock TextWrapping="Wrap" TextTrimming="WordEllipsis"
Text="{Tr MainWindow.RightPart.LoadConfirmations}" />
</Button>
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2"
w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True"
Margin="10"
Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
ItemsSource="{Binding Confirmations}" />
<Button Grid.Row="3" Margin="1,0,1,3" w:FontScaleWindow.Scale="0.8"
w:FontScaleWindow.ResizeFont="True" Command="{Binding ConfirmLoginCommand}"
Content="{Tr MainWindow.RightPart.ConfirmLogin}" />
</Grid>
</md:Card>
</Grid>
<Border Grid.Row="2" BorderBrush="#FF696969">
<Border.Background>
<SolidColorBrush Opacity="0.5" Color="DimGray" />
</Border.Background>
<Grid>
<ToolBarPanel Orientation="Horizontal" Margin="5" w:FontScaleWindow.Scale="0.73"
w:FontScaleWindow.ResizeFont="True">
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Account}" />
<TextBlock Text="{Binding SelectedMafile.AccountName}" />
<TextBlock Text="|" Margin="10,0,10,0" />
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Group}" />
<TextBlock FontWeight="Normal"
Text="{Binding SelectedMafile.Group, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
</ToolBarPanel>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" w:FontScaleWindow.Scale="0.7"
w:FontScaleWindow.ResizeFont="True" Margin="0,0,10,0">
<Hyperlink
NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies"
Foreground="{DynamicResource PrimaryHueMidBrush}"
RequestNavigate="Hyperlink_OnRequestNavigate">
by achies
</Hyperlink>
</TextBlock>
</Grid>
</Border>
<md:Snackbar Grid.Row="1" FontSize="20" MessageQueue="{Binding MessageQueue}" />
</Grid>
</md:DialogHost>
</Border>
</w:FontScaleWindow>
-213
View File
@@ -1,213 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using AchiesUtilities.Web.Models;
using AchiesUtilities.Web.Proxy;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using SteamLib.Api.Mobile;
using SteamLib.Authentication;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
using SteamLib.SteamMobile.Confirmations;
using SteamLib.Web;
namespace NebulaAuth.Model.MAAC;
public partial class PortableMaClient : ObservableObject, IDisposable
{
private const string LOC_PATH = "MAAC";
public Mafile Mafile { get; }
private HttpClient Client { get; }
private HttpClientHandler ClientHandler { get; }
private DynamicProxy Proxy { get; }
private readonly CancellationTokenSource _cts = new();
[ObservableProperty] private bool _autoConfirmMarket;
[ObservableProperty] private bool _autoConfirmTrades;
[ObservableProperty] private bool _isError;
public PortableMaClient(Mafile mafile)
{
Mafile = mafile;
Proxy = new DynamicProxy();
Proxy.SetData(mafile.Proxy?.Data);
var pair = ClientBuilder.BuildMobileClient(Proxy, mafile.SessionData);
Client = pair.Client;
ClientHandler = pair.Handler;
UpdateCookies(mafile.SessionData);
Mafile.PropertyChanged += Mafile_PropertyChanged;
}
private void Mafile_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Mafile.SessionData))
{
UpdateCookies(Mafile.SessionData);
}
}
private void UpdateCookies(IMobileSessionData? sessionData)
{
Application.Current.Dispatcher.Invoke(() => IsError = sessionData == null);
ClientHandler.CookieContainer.ClearAllCookies();
if (sessionData != null)
{
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData);
}
else
{
ClientHandler.CookieContainer.ClearSteamCookies();
ClientHandler.CookieContainer.AddMinimalMobileCookies();
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
}
}
public async Task<int> Confirm()
{
Proxy.SetData(Mafile.Proxy?.Data ?? MaClient.DefaultProxy);
List<Confirmation> conf;
try
{
conf = (await HandleTimerRequest(GetConfirmations)).ToList();
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "Timer {accountName}: Error GetConf in timer.", Mafile.AccountName);
return 0;
}
var toConfirm = new List<Confirmation>();
if (AutoConfirmMarket)
{
var market = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction);
toConfirm.AddRange(market);
}
if (AutoConfirmTrades)
{
var trade = conf.Where(c => c.ConfType == ConfirmationType.Trade);
toConfirm.AddRange(trade);
}
if (toConfirm.Count == 0) return 0;
try
{
Shell.Logger.Debug("Timer {accountName}: Sending confirmations. Count: {count}", Mafile.AccountName,
toConfirm.Count);
var success = await HandleTimerRequest(() => SendConfirmations(toConfirm));
Shell.Logger.Debug("Timer {accountName}: Confirmation sent: {confirmResult}", Mafile.AccountName, success);
return toConfirm.Count;
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "Timer {accountName}: MultiConf error in Timer.", Mafile.AccountName);
return 0;
}
}
private async Task<IEnumerable<Confirmation>> GetConfirmations()
{
return await SteamMobileConfirmationsApi.GetConfirmations(Client, Mafile, Mafile.SessionData!.SteamId,
_cts.Token);
}
private async Task<bool> SendConfirmations(IEnumerable<Confirmation> confirmations)
{
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, confirmations,
Mafile.SessionData!.SteamId, Mafile, true, _cts.Token);
}
private async Task<T> HandleTimerRequest<T>(Func<Task<T>> func)
{
Exception innerException;
try
{
return await SessionHandler.Handle(func, Mafile, Chp(), GetTimerPrefix());
}
catch (OperationCanceledException ex)
{
innerException = ex; //Ignored
}
catch (SessionInvalidException ex)
{
if (IgnoreTimerErrors())
{
Shell.Logger.Warn(
"Timer {accountName}: Session error while requesting in timer. Ignored due to IgnorePatchTuesdayErrors setting",
Mafile.AccountName);
}
else
{
Shell.Logger.Warn("Timer {accountName}: Session error while requesting in timer. Timer disabled",
Mafile.AccountName);
SetError();
}
innerException = ex;
}
catch (Exception ex) when (ExceptionHandler.Handle(ex, GetTimerPrefix()))
{
innerException = ex;
}
throw new ApplicationException("Swallowed", innerException);
}
private bool IgnoreTimerErrors()
{
if (Settings.Instance.IgnorePatchTuesdayErrors == false) return false;
var pstZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
var pstNow = TimeZoneInfo.ConvertTime(DateTime.UtcNow, pstZone);
var startTime = pstNow.Date.AddHours(15).AddMinutes(55); // 15:55 PST //22:55 GMT //00:55 GMT+2
var endTime = pstNow.Date.AddHours(17).AddMinutes(15); // 17:15 PST //00:15 GMT //02:15 GMT+2
return pstNow.DayOfWeek == DayOfWeek.Tuesday && pstNow >= startTime && pstNow <= endTime;
}
private HttpClientHandlerPair Chp()
{
return new HttpClientHandlerPair(Client, ClientHandler);
}
private static string GetLocalization(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
}
private string GetTimerPrefix()
{
return GetLocalization("TimerPrefix") + Mafile.AccountName + ": ";
}
public void SetError()
{
Application.Current.Dispatcher.Invoke(() => IsError = true);
Shell.Logger.Warn("Timer {accountName}: disabled due to error.", Mafile.AccountName);
SnackbarController.SendSnackbar(GetTimerPrefix() + GetLocalization("TimerSessionError"));
}
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
Mafile.PropertyChanged -= Mafile_PropertyChanged;
Client.Dispose();
ClientHandler.Dispose();
}
}
-73
View File
@@ -1,73 +0,0 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Core;
using Newtonsoft.Json;
namespace NebulaAuth.Model;
public partial class Settings : ObservableObject
{
public static Settings Instance { get; }
static Settings()
{
if (File.Exists("settings.json") == false)
{
Instance = new Settings();
Instance.PropertyChanged += SettingsOnPropertyChanged;
return;
}
try
{
var json = File.ReadAllText("settings.json");
var settings = JsonConvert.DeserializeObject<Settings>(json) ?? throw new NullReferenceException();
Instance = settings;
}
catch (Exception ex)
{
SnackbarController.SendSnackbar("Ошибка при загрузке настроек. Настройки были сброшены");
SnackbarController.SendSnackbar(ex.Message);
Instance = new Settings();
}
Instance.PropertyChanged += SettingsOnPropertyChanged;
}
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Save();
}
public static void Save()
{
var json = JsonConvert.SerializeObject(Instance, Formatting.Indented);
File.WriteAllText("settings.json", json);
}
#region Properties
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
[ObservableProperty] private bool _hideToTray;
[ObservableProperty] private int _timerSeconds = 60;
[ObservableProperty] private Color? _backgroundColor;
[ObservableProperty] private Color? _iconColor;
[ObservableProperty] private bool _isPasswordSet;
[ObservableProperty] private LocalizationLanguage _language = LocalizationLanguage.English;
[ObservableProperty] private bool _legacyMode = true;
[ObservableProperty] private bool _allowAutoUpdate;
[ObservableProperty] private bool _useAccountNameAsMafileName;
[ObservableProperty] private bool _ignorePatchTuesdayErrors;
#endregion
}
public enum BackgroundMode
{
Default,
Custom,
Color
}
-242
View File
@@ -1,242 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using AchiesUtilities.Extensions;
using NebulaAuth.Model.Entities;
using NebulaAuth.Model.Exceptions;
using SteamLib.Core.Models;
using SteamLib.Exceptions;
using SteamLib.SteamMobile;
namespace NebulaAuth.Model;
//RETHINK
public static class Storage
{
public const string MAFILE_F = "maFiles";
public const string REMOVED_F = "maFiles_removed";
public static readonly int DuplicateFound;
public static string MafileFolder { get; } = Path.GetFullPath(MAFILE_F);
public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F);
public static ObservableCollection<Mafile> MaFiles { get; } = new();
static Storage()
{
if (Directory.Exists(MafileFolder) == false)
{
Directory.CreateDirectory(MafileFolder);
}
if (Directory.Exists(RemovedMafileFolder) == false)
{
Directory.CreateDirectory(RemovedMafileFolder);
}
var files = Directory.GetFiles(MafileFolder);
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.Where(file => Path.GetExtension(file).EqualsIgnoreCase(".mafile")))
{
try
{
var data = ReadMafile(file);
if (hashNames.Contains(data.AccountName) || hashIds.Contains(data.SteamId))
{
DuplicateFound++;
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
continue;
}
hashNames.Add(data.AccountName);
if (data.SessionData != null) hashIds.Add(data.SteamId);
MaFiles.Add(data);
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Can't load mafile {file}", Path.GetFileName(file));
}
}
MaFiles = new ObservableCollection<Mafile>(MaFiles.OrderBy(m => m.AccountName));
}
/// <summary>
/// </summary>
/// <param name="path"></param>
/// <param name="overwrite"></param>
/// <exception cref="FormatException"></exception>
/// <exception cref="SessionInvalidException"></exception>
/// <exception cref="IOException"></exception>
public static void AddNewMafile(string path, bool overwrite)
{
Mafile data;
try
{
data = ReadMafile(path);
}
catch (Exception ex)
when (ex is not MafileNeedReloginException)
{
Shell.Logger.Warn(ex, "Can't load mafile");
throw new FormatException("File data is not valid", ex);
}
if (string.IsNullOrWhiteSpace(data.AccountName))
throw new FormatException("File data is not valid. Missing AccountName");
try
{
_ = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
}
catch (Exception ex)
{
throw new FormatException("Can't generate code on this mafile", ex);
}
if (overwrite == false && File.Exists(CreatePathForMafile(data)))
{
throw new IOException("File already exist and overwrite is False");
}
SaveMafile(data);
}
public static Mafile ReadMafile(string path)
{
var str = File.ReadAllText(path);
return NebulaSerializer.Deserialize(str);
}
public static void SaveMafile(Mafile data)
{
var path = CreatePathForMafile(data);
var str = NebulaSerializer.SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
if (existed != null)
{
var index = MaFiles.IndexOf(existed);
MaFiles[index] = data;
}
else
{
MaFiles.Add(data);
}
}
public static void UpdateMafile(Mafile data)
{
var path = CreatePathForMafile(data);
var str = NebulaSerializer.SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
}
public static void MoveToRemoved(Mafile data)
{
var path = CreatePathForMafile(data);
var copyPath = Path.Combine(REMOVED_F, data.SteamId + ".mafile");
var copyPathCompleted = copyPath;
var i = 0;
while (File.Exists(copyPathCompleted))
{
i++;
copyPathCompleted = copyPath + $" ({i})";
}
File.Copy(path, copyPathCompleted, false);
File.Delete(path);
MaFiles.Remove(data);
}
private static string CreatePathForMafile(Mafile data)
{
var fileName = Settings.Instance.UseAccountNameAsMafileName
? CreateFileNameWithAccountName(data.AccountName)
: CreateFileNameWithSteamId(data.SteamId);
return Path.Combine(MafileFolder, fileName);
}
private static string CreateFileNameWithAccountName(string accountName)
{
return accountName + ".mafile";
}
private static string CreateFileNameWithSteamId(SteamId steamId)
{
return steamId.Steam64.Id + ".mafile";
}
public static string? TryFindMafilePath(Mafile data)
{
var pathFileName = Path.Combine(MafileFolder, CreateFileNameWithAccountName(data.AccountName));
string? pathSteamId = null;
if (data.SessionData != null)
{
pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.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;
}
}
//TODO: Refactor
internal class MafileNameComparer : IComparer<string>
{
private const string MAF_64_START = "765";
private static readonly IComparer<string> DefaultComparer = Comparer<string>.Default;
public bool MafileNameMode { get; }
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;
var xisSteamId = Path.GetFileName(x).StartsWith(MAF_64_START);
var yisSteamId = Path.GetFileName(y).StartsWith(MAF_64_START);
if (xisSteamId ^ yisSteamId)
{
if (MafileNameMode)
{
return xisSteamId ? 1 : -1;
}
return yisSteamId ? 1 : -1;
}
return DefaultComparer.Compare(x, y);
}
}
-68
View File
@@ -1,68 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<LangVersion>latest</LangVersion>
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.5.7</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Remove="Theme\1140x641.jpg" />
<None Remove="Theme\Background.jpg" />
<None Remove="Theme\lock.ico" />
<None Remove="Theme\SplashScreen.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.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.2" />
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.14" />
</ItemGroup>
<ItemGroup>
<Resource Include="Theme\Background.jpg">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
<Resource Include="Theme\lock.ico" />
</ItemGroup>
<ItemGroup>
<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">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="localization.loc.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
-9
View File
@@ -1,9 +0,0 @@
• Есть люди которым нужна функция просмотра и завершения сессий аккаунта. Скорее всего в следующей версии это будет реализовано
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
• Ускорить появление подсказок в интерфейсе
• Добавить запоминание пароля при привязке мафайла
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
• Добавить полное шифрование мафайлов по аналогии с SDA
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
-9
View File
@@ -1,9 +0,0 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--Card-->
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="#26292E" />
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1" />
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5" />
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1" />
</ResourceDictionary>
-220
View File
@@ -1,220 +0,0 @@
using System;
using System.Collections.Generic;
using System.Windows;
namespace NebulaAuth.Theme;
public class FontScaleWindow : Window
{
// Using a DependencyProperty as the backing store for ScaleCoefficient. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleCoefficientProperty =
DependencyProperty.Register("ScaleCoefficient", typeof(double), typeof(FontScaleWindow),
new PropertyMetadata(1d));
public static readonly DependencyProperty DefaultFontSizeProperty =
DependencyProperty.Register("DefaultFontSize", typeof(double), typeof(FontScaleWindow),
new PropertyMetadata(20d));
private static readonly DependencyPropertyKey ParentScaleWindowKey
= DependencyProperty.RegisterAttachedReadOnly(
"ParentScaleWindow",
typeof(FontScaleWindow), typeof(FontScaleWindow),
new FrameworkPropertyMetadata(default(FontScaleWindow),
FrameworkPropertyMetadataOptions.None));
public static readonly DependencyProperty ParentScaleWindowProperty
= ParentScaleWindowKey.DependencyProperty;
public static readonly DependencyProperty ResizeFontProperty = DependencyProperty.RegisterAttached(
"ResizeFont", typeof(bool), typeof(FontScaleWindow), new FrameworkPropertyMetadata(false, ResizeCallBack));
//<-------------------Window-------------------->
//<-------------------Scaling-------------------->
public static readonly DependencyProperty ScaleProperty = DependencyProperty.RegisterAttached(
"Scale", typeof(double), typeof(FontScaleWindow),
new FrameworkPropertyMetadata(0.9999d, FrameworkPropertyMetadataOptions.Inherits, ScalePropertyCallback));
//<-------------------Window-------------------->
public double DefaultFontSize
{
get => (double) GetValue(DefaultFontSizeProperty);
set => SetValue(DefaultFontSizeProperty, value);
}
public double ScaleCoefficient
{
get => (double) GetValue(ScaleCoefficientProperty);
set => SetValue(ScaleCoefficientProperty, value);
}
private readonly HashSet<FrameworkElement> _cachedObjects = new();
private readonly Func<double, double, double> _diagonal = (h, w) => Math.Sqrt(h * h + w * w);
private double _currentDiagonal;
private double _defaultDiagonal = 1;
private bool _loaded;
public FontScaleWindow()
{
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var w = (FontScaleWindow) sender;
w._defaultDiagonal = _diagonal(MinHeight, MinWidth);
w.SizeChanged += OnSizeChanged;
w.Loaded -= OnLoaded;
_loaded = true;
w.Width += 1;
w.Width -= 1;
}
private static FontScaleWindow? GetParentScaleWindow(DependencyObject element)
{
return (FontScaleWindow) element.GetValue(ParentScaleWindowProperty);
}
private static void SetParentScaleWindow(DependencyObject element, FontScaleWindow value)
{
element.SetValue(ParentScaleWindowKey, value);
}
public static void SetResizeFont(DependencyObject element, bool value)
{
element.SetValue(ResizeFontProperty, value);
}
public static bool GetResizeFont(DependencyObject element)
{
return (bool) element.GetValue(ResizeFontProperty);
}
private static bool SetWindow(FrameworkElement el)
{
var w = GetWindow(el);
if (w is not FontScaleWindow fsWindow) return false;
SetParentScaleWindow(el, fsWindow);
if (fsWindow._cachedObjects.Contains(el)) return true;
fsWindow._cachedObjects.Add(el);
el.Unloaded += ObjOnUnloaded;
return true;
}
private static void ObjOnLoaded(object sender, RoutedEventArgs e)
{
var el = (FrameworkElement) sender;
if (GetParentScaleWindow(el) == null)
{
if (!SetWindow(el))
{
el.Loaded -= ObjOnLoaded;
return;
}
}
ResizeCallBack(sender, new DependencyPropertyChangedEventArgs());
}
private static void ResizeCallBack(object? sender, DependencyPropertyChangedEventArgs eventArgs)
{
if (sender is not FrameworkElement obj) return;
var window = GetParentScaleWindow(obj);
if (window != null && !window._cachedObjects.Contains(obj))
{
if (!SetWindow(obj)) return;
}
else if (window == null)
{
obj.Loaded += ObjOnLoaded;
}
if (window is not {_loaded: true}) return;
CalculateFontSizeResized(obj);
}
private static void ObjOnUnloaded(object sender, RoutedEventArgs e)
{
var obj = (FrameworkElement) sender;
var w = GetParentScaleWindow(obj)!;
w._cachedObjects.Remove(obj);
obj.Unloaded -= ObjOnUnloaded;
obj.Loaded -= ObjOnLoaded;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
_currentDiagonal = _diagonal(e.NewSize.Width, e.NewSize.Height);
foreach (var cached in _cachedObjects)
{
CalculateFontSizeResized(cached);
}
}
public static void SetScale(DependencyObject element, double value)
{
element.SetValue(ScaleProperty, value);
}
public static double GetScale(DependencyObject element)
{
return (double) element.GetValue(ScaleProperty);
}
private static void ScalePropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = GetParentScaleWindow(d);
if (window == null)
{
var w = GetWindow(d);
if (w is FontScaleWindow fsWindow)
{
SetParentScaleWindow(d, fsWindow);
}
}
ResizeElement(d);
}
public static void ResizeElement(DependencyObject d)
{
var set = d.ReadLocalValue(ScaleProperty) != DependencyProperty.UnsetValue;
if (set)
{
if (GetResizeFont(d))
{
CalculateFontSizeResized(d);
}
else
{
CalculateFontSize(d);
}
}
}
public static void CalculateFontSize(DependencyObject d)
{
var window = GetParentScaleWindow(d);
if (window == null) return;
var fontSize = GetScale(d) * window._defaultDiagonal;
d.SetValue(FontSizeProperty, fontSize);
}
public static void CalculateFontSizeResized(DependencyObject d)
{
var window = GetParentScaleWindow(d);
if (window == null) return;
var windowsScale = Math.Pow(window._currentDiagonal / window._defaultDiagonal, window.ScaleCoefficient);
var elScale = GetScale(d);
var fontSize = window.DefaultFontSize * elScale * windowsScale;
d.SetValue(FontSizeProperty, fontSize);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 KiB

-60
View File
@@ -1,60 +0,0 @@
using System;
using System.Collections.Specialized;
using System.Windows;
using NebulaAuth.Core;
using NebulaAuth.Model;
namespace NebulaAuth.Utility;
public class ClipboardHelper
{
public static bool Set(string text)
{
var i = 0;
while (i < 20)
{
try
{
Clipboard.SetText(text);
return true;
}
catch (Exception ex)
{
if (i == 19)
{
Shell.Logger.Error(ex);
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
}
}
i++;
}
return false;
}
public static bool SetFiles(StringCollection files)
{
var i = 0;
while (i < 20)
{
try
{
Clipboard.SetFileDropList(files);
return true;
}
catch (Exception ex)
{
if (i == 19)
{
Shell.Logger.Error(ex);
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error"));
}
}
i++;
}
return false;
}
}
@@ -1,45 +0,0 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.ConfirmCancelDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
mc:Ignorable="d"
Height="Auto" Width="Auto" MaxWidth="500"
Background="{DynamicResource WindowBackground}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock theme:FontScaleWindow.Scale="0.8"
theme:FontScaleWindow.ResizeFont="True"
HorizontalAlignment="Center" Margin="10,10,10,15"
Text="Подтвердите действие"
x:Name="ConfirmTextBlock"
TextWrapping="WrapWithOverflow" />
<Grid Grid.Row="1" Margin="10,0,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button DockPanel.Dock="Left"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource True}"
FontSize="{Binding ElementName=ConfirmTextBlock, Path=FontSize,
Converter={StaticResource CoefficientConverter},
ConverterParameter=0.85}">
ОК
</Button>
<Button Grid.Column="1" DockPanel.Dock="Right"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource False}"
FontSize="{Binding ElementName=ConfirmTextBlock, Path=FontSize,
Converter={StaticResource CoefficientConverter},
ConverterParameter=0.85}">
Отмена
</Button>
</Grid>
</Grid>
</UserControl>
@@ -1,48 +0,0 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.LoginAgainDialog"
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"
mc:Ignorable="d"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
Foreground="WhiteSmoke"
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
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>
<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}" />
<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" 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>
@@ -1,65 +0,0 @@
<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"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
Foreground="WhiteSmoke"
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}" />
<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>
@@ -1,42 +0,0 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.SetCryptPasswordDialog"
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:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Foreground="WhiteSmoke"
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
Background="{DynamicResource WindowBackground}">
<Grid MinHeight="100" MinWidth="300" MaxWidth="400" Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal"
Text="{Tr SetEncryptedPasswordDialog.DialogText}" theme:FontScaleWindow.Scale="0.8"
theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left" />
<PasswordBox FontSize="16"
materialDesign:PasswordBoxAssist.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}" />
<Grid Grid.Row="3">
<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 SetEncryptedPasswordDialog.Ok}" />
<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 SetEncryptedPasswordDialog.Cancel}" />
</Grid>
</Grid>
</UserControl>
@@ -1,71 +0,0 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
namespace NebulaAuth.View.Dialogs;
/// <summary>
/// Логика взаимодействия для WaitLoginDialog.xaml
/// </summary>
public partial class WaitLoginDialog : ICaptchaResolver
{
private TaskCompletionSource<string> _tcs = new();
public WaitLoginDialog()
{
InitializeComponent();
}
public async Task<string> Resolve(Uri imageUrl, HttpClient client)
{
CaptchaGrid.Visibility = Visibility.Visible;
var stream = await client.GetStreamAsync(imageUrl);
return await Application.Current.Dispatcher.Invoke(async () =>
{
var image = await LoadImage(stream);
CaptchaImage.Source = image;
try
{
return await _tcs.Task;
}
catch (TaskCanceledException)
{
throw new LoginException(LoginError.CaptchaRequired);
}
});
}
private async Task<BitmapImage> LoadImage(Stream stream)
{
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
ms.Position = 0;
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
await stream.DisposeAsync();
return image;
}
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
{
_tcs.SetCanceled();
}
private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return;
var oldTcs = _tcs;
_tcs = new TaskCompletionSource<string>();
oldTcs.SetResult(CaptchaTB.Text);
}
}
-166
View File
@@ -1,166 +0,0 @@
<UserControl x:Class="NebulaAuth.View.LinkerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
MinHeight="640"
MinWidth="400"
Background="{DynamicResource WindowBackground}"
d:DataContext="{d:DesignInstance other:LinkAccountVM}"
FontSize="18">
<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" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="materialDesign:PackIcon">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}, Path=Tag}"
Value="False">
<Setter Property="Foreground" Value="IndianRed" />
<Setter Property="Kind" Value="CheckBoxOutlineBlank" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}, Path=Tag}"
Value="True">
<Setter Property="Foreground" Value="Green" />
<Setter Property="Kind" Value="CheckBoxOutline" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}, Path=Tag}"
Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}" />
<TextBlock Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12">
<Hyperlink Command="{Binding OpenTroubleshootingCommand}">
<Run Text="{Tr LinkerDialog.GotErrorHyperlinkText, IsDynamic=False}" />
</Hyperlink>
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Background="DarkGray" Grid.Row="1" />
<Grid Grid.Row="2" Margin="10,20,10,10">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox Padding="10" FontSize="16" Style="{StaticResource MaterialDesignOutlinedComboBox}"
SelectedValue="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}"
materialDesign:HintAssist.Hint="{Tr LinkerDialog.Proxy}"
IsEnabled="{Binding IsPasswordFieldVisible}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Key, Mode=OneWay}" /><Run Text=":" />
<Run Text="{Binding Value.Address, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Column="1" IsEnabled="{Binding IsPasswordFieldVisible}" Margin="5,0,0,0"
Command="{Binding ResetProxyCommand}" Content="{materialDesign:PackIcon Trash}" />
</Grid>
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="7" Tag="{Binding IsLogin}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Authorization}" />
</StackPanel>
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailCode}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailCode}" />
</StackPanel>
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="7" Tag="{Binding IsPhoneNumber}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
<TextBlock FontSize="16" Text="{Tr LinkerDialog.PhoneNumber}" />
</StackPanel>
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailConfirmation}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailLink}" />
</StackPanel>
<StackPanel Grid.Row="7" Orientation="Horizontal" Margin="7" Tag="{Binding IsLinkCode}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
<TextBlock FontSize="16" Text="{Tr LinkerDialog.SmsOrCode}" />
</StackPanel>
<StackPanel Grid.Row="8" Orientation="Horizontal" Margin="7" Tag="{Binding IsCompleted}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Completed}" />
</StackPanel>
<Grid Grid.Row="9" Margin="10,20,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Padding="7" Text="{Binding FieldText}"
Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10" />
<TextBox Grid.Row="1" Padding="7" Text="{Binding PassFieldText}"
Visibility="{Binding IsPasswordFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" />
<GroupBox Grid.Row="2" MaxWidth="400" BorderBrush="{StaticResource PrimaryHueMidBrush}"
VerticalAlignment="Bottom" BorderThickness="1" Margin="0,10,0,0" Padding="5">
<GroupBox.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Tr LinkerDialog.Message}" FontSize="16" Foreground="GhostWhite" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Foreground="GhostWhite" FontSize="16" Text="{Binding HintText}"
HorizontalAlignment="Stretch" TextWrapping="Wrap" />
<Button Command="{Binding CopyCodeCommand}"
Visibility="{Binding IsCompleted, Converter={StaticResource BooleanToVisibilityConverter}}"
Grid.Row="1">
<materialDesign:PackIcon Kind="ContentCopy" />
</Button>
</Grid>
</GroupBox>
</Grid>
<Button Grid.Row="10" Height="35" FontSize="17" Command="{Binding ProceedCommand}"
Content="{Tr LinkerDialog.ProceedButton}" />
</Grid>
</UserControl>
-132
View File
@@ -1,132 +0,0 @@
<UserControl x:Class="NebulaAuth.View.ProxyManagerView"
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:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="400"
Foreground="WhiteSmoke"
FontFamily="{md:MaterialDesignFont}"
MinHeight="500"
MinWidth="400"
MaxHeight="550"
d:DataContext="{d:DesignInstance other:ProxyManagerVM}"
Background="{DynamicResource WindowBackground}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<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" />
</Button>
</Grid>
<Separator Grid.Row="1" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch"
Margin="15" FontSize="16">
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}" />
<Run Text="{Binding DefaultProxy.Key, StringFormat='&#x0a;0:', FallbackValue='-', Mode=OneWay}" />
<Run
Text="{Binding DefaultProxy.Value, Converter='{StaticResource ProxyDataTextConverter}', Mode=OneWay, FallbackValue=''}" />
</TextBlock>
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
</Button>
</Grid>
<md:Card Grid.Row="3" Margin="10">
<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}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.ContextMenus.Proxy.Copy}"
Command="{Binding PlacementTarget.Tag.CopyProxyCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding Value}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Proxy.CopyAddress}"
Command="{Binding PlacementTarget.Tag.CopyProxyAddressCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding Value}" />
</ContextMenu>
</Setter.Value>
</Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<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>
<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" />
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</md:Card>
<Grid Grid.Row="4" Margin="5,0,15,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True"
AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}"
Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}" />
<Button IsDefault="True" Grid.Column="1" Command="{Binding AddProxyCommand}">
<md:PackIcon Kind="Add" />
</Button>
<Button Grid.Column="2" Command="{Binding RemoveProxyCommand}">
<md:PackIcon Kind="Trash" />
</Button>
</Grid>
</Grid>
</UserControl>
-12
View File
@@ -1,12 +0,0 @@
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для ProxyManagerView.xaml
/// </summary>
public partial class ProxyManagerView
{
public ProxyManagerView()
{
InitializeComponent();
}
}
-90
View File
@@ -1,90 +0,0 @@
<UserControl x:Class="NebulaAuth.View.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
d:DesignHeight="650"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
MinHeight="600"
MinWidth="400"
MaxWidth="200"
d:DataContext="{d:DesignInstance other:SettingsVM}"
Background="{DynamicResource WindowBackground}">
<Grid>
<Grid Margin="15,10,15,15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
<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 SettingsDialog.Title}" />
<Button IsCancel="True" Grid.Column="1" Width="30" Height="30"
Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
</Button>
</Grid>
<Separator Grid.Row="1" Margin="0,10,0,0" />
<StackPanel Grid.Row="2" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16"
ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key"
SelectedValue="{Binding BackgroundMode}"
materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" />
<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}" />
<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,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>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<PasswordBox MinWidth="250" materialDesign:PasswordBoxAssist.Password="{Binding Password}"
Height="Auto" VerticalAlignment="Center" FontSize="16" Margin="0,10,0,0"
Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
materialDesign:HintAssist.Hint="{Tr SettingsDialog.PasswordBox.CurrentCryptPassword}"
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" />
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom"
Grid.Column="1">
<materialDesign:PackIcon Kind="ContentSave" />
</Button>
</Grid>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding LegacyMode}"
Content="{Tr SettingsDialog.LegacyMafileMode}"
ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}" />
<!--<CheckBox IsEnabled="False" 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>
<CheckBox IsChecked="{Binding IgnorePatchTuesdayErrors}" Margin="0,10,0,0" FontSize="16"
ToolTip="{Tr SettingsDialog.IgnorePatchTuesdayErrorsHint}">
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.IgnorePatchTuesdayErrors}" />
</CheckBox>
</StackPanel>
</Grid>
</Grid>
</UserControl>
-12
View File
@@ -1,12 +0,0 @@
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для SettingsView.xaml
/// </summary>
public partial class SettingsView
{
public SettingsView()
{
InitializeComponent();
}
}
-457
View File
@@ -1,457 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using NLog;
using SteamLib;
using SteamLib.Account;
using SteamLib.Authentication;
using SteamLib.Authentication.LoginV2;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
using SteamLib.Exceptions.Mobile;
using SteamLib.ProtoCore.Exceptions;
using SteamLib.SteamMobile.AuthenticatorLinker;
using SteamLib.Web;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace NebulaAuth.ViewModel.Other;
public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNumberProvider, ISmsCodeProvider
{
private const string LOCALIZATION_KEY = "LinkVM";
private static Logger Logger => Shell.Logger;
private static ILogger Logger2 => Shell.ExtensionsLogger;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public LinkAccountVM()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
if (MaClient.DefaultProxy != null)
{
var def = Proxies.FirstOrDefault(p => p.Value.Equals(MaClient.DefaultProxy));
if (def.Value != null!)
{
SelectedProxy = def;
}
}
}
[RelayCommand(AllowConcurrentExecutions = true, CanExecute = nameof(CanProceed))]
public async Task Proceed()
{
if (IsCompleted)
DialogHost.Close(null);
CanProceed = false;
#region Login
if (IsLogin == false)
{
SetProxy();
ClearCookies();
_loginV2ExecutorOptions = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, Client)
{
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
WebsiteId = "Mobile",
EmailAuthProvider = this,
Logger = Logger2
};
try
{
IsLogin = true;
var userName = FieldText;
var pass = PassFieldText;
_password = pass;
FieldText = string.Empty;
IsFieldVisible = false;
HintText = string.Empty;
_sessionData =
(MobileSessionData) await LoginV2Executor.DoLogin(_loginV2ExecutorOptions, userName, pass);
Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(_sessionData);
IsEmailCode = true;
}
catch (EResultException ex)
{
Logger.Error(ex, "Link exception on login");
HintText = GetLocalizationOrDefault("CantLogin") + ErrorTranslatorHelper.TranslateEResult(ex.Result);
InvokeOnDispatcher(ResetState);
return;
}
catch (LoginException ex)
{
Logger.Error(ex, "Link exception on login");
HintText = GetLocalizationOrDefault("CantLogin") + ErrorTranslatorHelper.TranslateLoginError(ex.Error);
InvokeOnDispatcher(ResetState);
return;
}
catch (Exception ex)
{
Logger.Error(ex, "Link exception on login");
HintText = GetLocalizationOrDefault("CantLogin") + ex.Message;
InvokeOnDispatcher(ResetState);
return;
}
}
if (IsEmailCode == false)
{
_emailCodeTcs.SetResult(FieldText);
HintText = string.Empty;
FieldText = string.Empty;
_emailCodeTcs = new TaskCompletionSource<string>();
IsFieldVisible = false;
return;
}
#endregion
if (_isLinkStarted)
goto linkStarted;
try
{
_isLinkStarted = true;
var linkOptions = new LinkOptions(Client, LoginV2Executor.NullConsumer, this,
null, this, this, Backup, Logger2);
_linker = new SteamAuthenticatorLinker(linkOptions);
var result = await _linker.LinkAccount(_sessionData);
IsLinkCode = true;
IsCompleted = true;
var mafile = Mafile.FromMobileDataExtended(result);
try
{
if (SelectedProxy.HasValue)
mafile.Proxy = new MaProxy(SelectedProxy.Value.Key, SelectedProxy.Value.Value);
if (Settings.Instance.IsPasswordSet)
mafile.Password = PHandler.Encrypt(_password);
}
catch (Exception ex)
{
Logger.Error(ex, "Error during saving Nebula data to mafile");
}
Storage.SaveMafile(mafile);
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
HintText =
string.Format(GetLocalizationOrDefault("MafileLinked"),
mafile.RevocationCode,
mafile.SessionData?.SteamId.Steam64);
_rCode = mafile.RevocationCode ?? string.Empty;
CanProceed = true;
return;
}
catch (AuthenticatorLinkerException ex)
{
Logger.Error(ex, "Link exception");
HintText = $"{GetLocalizationCommon("Error")}: {ErrorTranslatorHelper.TranslateLinkerError(ex.Error)}";
InvokeOnDispatcher(ResetState);
return;
}
catch (HttpRequestException ex)
{
var msg = ex.StatusCode?.ToString() ?? ex.Message;
HintText = $"{GetLocalizationCommon("RequestError")}: {msg}";
InvokeOnDispatcher(ResetState);
return;
}
catch (EResultException ex)
{
Logger.Error(ex, "Link exception");
HintText = GetLocalizationOrDefault("ErrorWithCode") + ErrorTranslatorHelper.TranslateEResult(ex.Result);
InvokeOnDispatcher(ResetState);
return;
}
catch (Exception ex)
{
Logger.Error(ex, "Link exception");
HintText = GetLocalizationOrDefault("UnknownError") + ex.Message;
InvokeOnDispatcher(ResetState);
return;
}
linkStarted:
if (IsPhoneNumber == false)
{
var phoneText = FieldText;
FieldText = string.Empty;
if (string.IsNullOrWhiteSpace(phoneText))
{
HintText = string.Empty;
IsFieldVisible = false;
_phoneNumberTcs.SetResult(null);
_phoneNumberTcs = new TaskCompletionSource<long?>();
return;
}
if (!string.IsNullOrWhiteSpace(phoneText) && phoneText.Length >= 4 &&
long.TryParse(phoneText, out var phone))
{
HintText = string.Empty;
IsFieldVisible = false;
_phoneNumberTcs.SetResult(phone);
_phoneNumberTcs = new TaskCompletionSource<long?>();
return;
}
HintText = GetLocalizationOrDefault("PleaseEnterCorrectPhone");
CanProceed = true;
return;
}
if (IsEmailConfirmation == false)
{
HintText = string.Empty;
_emailConfTcs.SetResult();
_emailConfTcs = new TaskCompletionSource();
CanProceed = false;
return;
}
if (IsLinkCode == false)
{
var linkCode = FieldText;
FieldText = string.Empty;
if (!string.IsNullOrWhiteSpace(linkCode) && linkCode.Length >= 4)
{
HintText = string.Empty;
IsFieldVisible = false;
_linkCodeTcs.SetResult(linkCode);
_linkCodeTcs = new TaskCompletionSource<string>();
}
else
{
HintText = GetLocalizationOrDefault("PleaseEnterCorrectCode");
CanProceed = true;
}
}
}
[RelayCommand]
public void ResetProxy()
{
if (IsPasswordFieldVisible == false) return;
SelectedProxy = null;
}
private void InvokeOnDispatcher(Action action)
{
Application.Current.Dispatcher.BeginInvoke(action, null);
}
private void ResetState()
{
PassFieldText = string.Empty;
IsLogin = false;
IsFieldVisible = true;
IsEmailCode = false;
_isLinkStarted = false;
IsPhoneNumber = false;
IsEmailConfirmation = false;
CanProceed = true;
_emailCodeTcs = new TaskCompletionSource<string>();
_rCode = string.Empty;
_password = string.Empty;
}
private void Backup(MobileDataExtended data)
{
if (Directory.Exists("mafiles_backup") == false)
{
Directory.CreateDirectory("mafiles_backup");
}
var json = NebulaSerializer.SerializeMafile(data, null);
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"),
json); //TODO: Move logic to Storage
}
[RelayCommand]
private void OpenTroubleshooting()
{
const string troubleshootingURI =
"https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/docs/{0}/LinkingTroubleshooting";
var localized = string.Format(troubleshootingURI, LocManager.GetCurrentLanguageCode());
Process.Start(new ProcessStartInfo(new Uri(localized).ToString())
{
UseShellExecute = true
});
}
[RelayCommand]
private void CopyCode()
{
try
{
Clipboard.SetText(_rCode);
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Error whily copying RCode");
}
}
private static string GetLocalizationOrDefault(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOCALIZATION_KEY, key);
}
private static string GetLocalizationCommon(string key)
{
return LocManager.GetCommonOrDefault(key, key);
}
#region Properties
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsPasswordFieldVisible))]
private bool _isLogin;
[ObservableProperty] private bool _isEmailCode;
[ObservableProperty] private bool _isPhoneNumber;
[ObservableProperty] private bool _isEmailConfirmation;
[ObservableProperty] private bool _isLinkCode;
[ObservableProperty] private bool _isCompleted;
[ObservableProperty] private bool _isFieldVisible = true;
[ObservableProperty] private string _fieldText;
[ObservableProperty] private string _passFieldText;
[ObservableProperty] private string _hintText = GetLocalizationOrDefault("EnterLoginAndPassword");
private TaskCompletionSource<string> _emailCodeTcs = new();
private TaskCompletionSource<long?> _phoneNumberTcs = new();
private TaskCompletionSource _emailConfTcs = new();
private TaskCompletionSource<string> _linkCodeTcs = new();
private bool _isLinkStarted;
private string _rCode = string.Empty;
private string _password = string.Empty;
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ProceedCommand))]
private bool _canProceed = true;
public bool IsPasswordFieldVisible => !IsLogin;
private LoginV2ExecutorOptions _loginV2ExecutorOptions;
private SteamAuthenticatorLinker _linker;
private MobileSessionData _sessionData;
#endregion
#region HttpClient
private static HttpClient Client { get; }
private static HttpClientHandler Handler { get; }
private static DynamicProxy Proxy { get; }
public ObservableDictionary<int, ProxyData> Proxies => ProxyStorage.Proxies;
public KeyValuePair<int, ProxyData>? SelectedProxy
{
get => _selectedProxy;
set
{
SetProperty(ref _selectedProxy, value);
SetProxy();
}
}
private KeyValuePair<int, ProxyData>? _selectedProxy;
#endregion
#region Providers
public int MaxRetryCount { get; }
public Task<string> GetEmailAuthCode(ILoginConsumer caller)
{
CanProceed = true;
HintText = GetLocalizationOrDefault("EnterEmailCode");
IsFieldVisible = true;
return _emailCodeTcs.Task;
}
public Task<string> GetAddAuthenticatorCode(ILoginConsumer caller)
{
IsPhoneNumber = true;
IsEmailConfirmation = true;
CanProceed = true;
HintText = GetLocalizationOrDefault("EnterEmailCode");
IsFieldVisible = true;
return _linkCodeTcs.Task;
}
public Task ConfirmEmailLink(ILoginConsumer caller, EmailConfirmationType confirmationType)
{
IsPhoneNumber = true;
CanProceed = true;
HintText = GetLocalizationOrDefault("ClickOnEmailLink");
return _emailConfTcs.Task;
}
public Task<long?> GetPhoneNumber(ILoginConsumer caller)
{
CanProceed = true;
HintText = GetLocalizationOrDefault("EnterPhoneNumber");
IsFieldVisible = true;
return _phoneNumberTcs.Task;
}
public async Task<int> GetSmsCode(ILoginConsumer caller, long? phoneNumber, string? hint)
{
IsPhoneNumber = true;
IsEmailConfirmation = true;
CanProceed = true;
HintText = string.Format(GetLocalizationOrDefault("PhoneHint"), hint);
IsFieldVisible = true;
var code = await _linkCodeTcs.Task;
return int.Parse(code);
}
#endregion
#region Client
static LinkAccountVM()
{
Proxy = new DynamicProxy();
var clientPair = ClientBuilder.BuildMobileClient(Proxy, null);
Client = clientPair.Client;
Handler = clientPair.Handler;
}
private void ClearCookies()
{
Handler.CookieContainer.ClearMobileSessionCookies();
}
private void SetProxy()
{
Proxy.SetData(SelectedProxy?.Value);
}
#endregion
}
-125
View File
@@ -1,125 +0,0 @@
using System.Collections.Generic;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
namespace NebulaAuth.ViewModel.Other;
public partial class SettingsVM : ObservableObject
{
public Settings Settings => Settings.Instance;
public BackgroundMode BackgroundMode
{
get => Settings.BackgroundMode;
set => Settings.BackgroundMode = value;
}
public bool HideToTray
{
get => Settings.HideToTray;
set => Settings.HideToTray = value;
}
public Dictionary<BackgroundMode, string> BackgroundModes => new()
{
{BackgroundMode.Default, LocManager.GetOrDefault("Default", "SettingsDialog", "BackgroundMode", "Default")},
{BackgroundMode.Custom, LocManager.GetOrDefault("Default", "SettingsDialog", "BackgroundMode", "Custom")},
{BackgroundMode.Color, LocManager.GetOrDefault("Default", "SettingsDialog", "BackgroundMode", "NoBackground")}
};
public Dictionary<LocalizationLanguage, string> Languages { get; } = new()
{
{LocalizationLanguage.English, "English"},
{LocalizationLanguage.Russian, "Русский"},
{LocalizationLanguage.Ukrainian, "Українська"}
};
public Color? BackgroundColor
{
get => Settings.BackgroundColor;
set => Settings.BackgroundColor = value;
}
public Color? IconColor
{
get => Settings.IconColor;
set => Settings.IconColor = value;
}
public bool UseIcon
{
get => IconColor != null;
set
{
if (value == false)
IconColor = null;
else
IconColor = Color.FromRgb(202, 39, 39);
OnPropertyChanged();
OnPropertyChanged(nameof(IconColor));
}
}
public bool UseBackground
{
get => BackgroundColor != null;
set
{
if (value == false)
BackgroundColor = null;
else
BackgroundColor = Color.FromRgb(202, 39, 39);
OnPropertyChanged();
OnPropertyChanged(nameof(BackgroundColor));
}
}
public LocalizationLanguage Language
{
get => Settings.Language;
set
{
Settings.Language = value;
LocManager.SetApplicationLocalization(value);
}
}
public bool LegacyMode
{
get => Settings.LegacyMode;
set => Settings.LegacyMode = value;
}
public bool AllowAutoUpdate
{
get => Settings.AllowAutoUpdate;
set => Settings.AllowAutoUpdate = value;
}
public bool UseAccountNameAsMafileName
{
get => Settings.UseAccountNameAsMafileName;
set => Settings.UseAccountNameAsMafileName = value;
}
public bool IgnorePatchTuesdayErrors
{
get => Settings.IgnorePatchTuesdayErrors;
set => Settings.IgnorePatchTuesdayErrors = value;
}
[ObservableProperty] private string? _password;
[RelayCommand]
private void SetPassword()
{
Settings.IsPasswordSet = PHandler.SetPassword(Password);
}
}
File diff suppressed because it is too large Load Diff
+7 -8
View File
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.5.7.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.7/NebulaAuth.1.5.7.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.7.html</changelog>
<mandatory>false</mandatory>
</item>
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.8.1</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.1/NebulaAuth.1.8.1.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.1.html</changelog>
<mandatory>false</mandatory>
</item>
@@ -1,59 +0,0 @@
using System.Net;
using SteamLib.Core;
using SteamLib.Exceptions;
using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Services;
namespace SteamLib.Api.Mobile;
public static class SteamMobileAuthenticatorApi
{
public static async Task<GetAuthSessionsForAccount_Response> GetAuthSessionsForAccount(HttpClient client,
string accessToken, CancellationToken cancellationToken = default)
{
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" +
accessToken;
try
{
return await client.GetProto<GetAuthSessionsForAccount_Response>(req, new EmptyMessage(),
cancellationToken);
}
catch (HttpRequestException ex)
when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
}
}
public static async Task<GetAuthSessionInfo_Response> GetAuthSessionInfo(HttpClient client, string accessToken,
ulong clientId, CancellationToken cancellationToken = default)
{
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionInfo/v1?access_token=" + accessToken;
var reqData = new GetAuthSessionInfo_Request
{
ClientId = clientId
};
try
{
return await client.PostProto<GetAuthSessionInfo_Response>(req, reqData, cancellationToken);
}
catch (HttpRequestException ex)
when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
}
}
public static async Task<bool> UpdateAuthSessionStatus(HttpClient client, string accessToken, string sharedSecret,
UpdateAuthSessionWithMobileConfirmation_Request request)
{
var req = SteamConstants.STEAM_API +
"IAuthenticationService/UpdateAuthSessionWithMobileConfirmation/v1?access_token=" + accessToken;
request.ComputeSignature(sharedSecret);
await client.PostProtoEnsureSuccess(req, request);
return true;
}
}
-114
View File
@@ -1,114 +0,0 @@
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;
public static class SteamGlobalApi
{
/// <summary>
/// Raw AccessToken value
/// </summary>
/// <param name="client"></param>
/// <param name="domain"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="SessionInvalidException"></exception>
public static async Task<string> RefreshJwt(HttpClient client, SteamDomain domain,
CancellationToken cancellationToken = default)
{
var domainUri = SteamDomains.GetDomain(domain);
var data = new Dictionary<string, string>
{
{"redir", domainUri}
};
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);
JwtRefreshJson? jwtRefresh;
try
{
jwtRefresh = JsonConvert.DeserializeObject<JwtRefreshJson>(respStr);
}
catch (Exception ex)
{
throw new UnsupportedResponseException(respStr, ex);
}
if (jwtRefresh?.Success != true)
{
Exception? inner = null;
if (jwtRefresh?.Error != null)
{
var errorCode = jwtRefresh.Error.Value;
var translated = SteamStatusCode.Translate<SteamStatusCode>(errorCode, out _);
inner = new SteamStatusCodeException(translated, respStr);
}
throw new SessionInvalidException(
"AjaxRefresh was not successful. 'steamRefresh_steam' cookie is missing or refresh token is invalid",
inner);
}
data = new Dictionary<string, string>
{
{"steamID", jwtRefresh.SteamId.ToString()},
{"nonce", jwtRefresh.Nonce},
{"redir", jwtRefresh.Redir},
{"auth", jwtRefresh.Auth}
};
cont = new FormUrlEncodedContent(data);
var update = await client.PostAsync(jwtRefresh.LoginUrl, cont, cancellationToken);
var updateResp = await update.Content.ReadAsStringAsync(cancellationToken);
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}}
};
}
return SteamTokenHelper.ExtractJwtFromSetCookiesHeader(update.Headers);
}
private class JwtRefreshJson
{
[JsonProperty("success")] public bool Success { get; set; }
[JsonProperty("login_url")] public string LoginUrl { get; set; } = string.Empty;
[JsonProperty("steamID")] public long SteamId { get; set; }
[JsonProperty("nonce")] public string Nonce { get; set; } = string.Empty;
[JsonProperty("redir")] public string Redir { get; set; } = string.Empty;
[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; }
}
}
@@ -1,112 +0,0 @@
using Microsoft.Extensions.Logging;
using SteamLib.Authentication;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
namespace SteamLib.Login.Default;
[Obsolete("Method removed from Steam")]
public class LoginExecutor
{
public static ILoginConsumer NullConsumer { get; } = new NullLoginConsumer();
public ILoginConsumer Caller { get; }
public HttpClient HttpClient { get; }
public ILogger? Logger { get; init; }
public IEmailProvider? EmailAuthProvider { get; init; }
public ICaptchaResolver? CaptchaResolver { get; init; }
public ISteamGuardProvider? SteamGuardProvider { get; init; }
private LoginExecutor(LoginExecutorOptions options)
{
Caller = options.Consumer;
HttpClient = options.HttpClient;
Logger = options.Logger;
EmailAuthProvider = options.EmailAuthProvider;
SteamGuardProvider = options.SteamGuardProvider;
CaptchaResolver = options.CaptchaResolver;
}
public static async Task<TransferParameters> DoLogin(LoginExecutorOptions options, string username, string password,
CancellationToken cancellationToken = default) //TODO: logs
{
var executor = new LoginExecutor(options);
var client = executor.HttpClient;
LoginStage loginStage = new GetRsaStage(username);
loginStage = await ((GetRsaStage) loginStage).Proceed(client, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
if (loginStage is ReadyToLoginStage rdyToLoginStage)
{
loginStage = await rdyToLoginStage.Proceed(client, password, string.Empty, cancellationToken);
}
cancellationToken.ThrowIfCancellationRequested();
var twoFactorTry = 0;
var emailTry = 0;
while (loginStage.GetType() != typeof(LoginErrorStage) && loginStage is not LoginSuccessStage)
{
cancellationToken.ThrowIfCancellationRequested();
switch (loginStage)
{
case CaptchaNeededStage captchaNeededStage:
{
if (executor.CaptchaResolver == null) throw new LoginException(LoginError.CaptchaRequired);
var captchaText = await executor.CaptchaResolver.Resolve(captchaNeededStage.CaptchaImage, client);
loginStage = await captchaNeededStage.Proceed(client, captchaText, cancellationToken);
break;
}
case EmailAuthStage emailAuthStage:
{
if (executor.EmailAuthProvider == null) throw new LoginException(LoginError.EmailAuthRequired);
if (emailTry > executor.EmailAuthProvider.MaxRetryCount)
throw new LoginException(LoginError.InvalidEmailAuthCode);
var code = await executor.EmailAuthProvider.GetEmailAuthCode(executor.Caller);
loginStage = await emailAuthStage.Proceed(client, code, cancellationToken);
emailTry++;
break;
}
case TwoFactorStage twoFactorStage:
{
if (executor.SteamGuardProvider == null) throw new LoginException(LoginError.SteamGuardRequired);
if (twoFactorTry > executor.SteamGuardProvider.MaxRetryCount)
throw new LoginException(LoginError.InvalidTwoFactorCode);
var twoFactor = await executor.SteamGuardProvider.GetSteamGuardCode(executor.Caller);
loginStage = await twoFactorStage.Proceed(client, twoFactor, cancellationToken);
twoFactorTry++;
break;
}
case ReadyToLoginStage readyToLoginStage: //When captcha proceeded, stage goes there
{
loginStage = await readyToLoginStage.Proceed(client, password, string.Empty, cancellationToken);
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(loginStage));
}
}
if (loginStage is LoginErrorStage error)
{
var content = await error.Response.Content.ReadAsStringAsync(cancellationToken);
if (content.Contains("The account name or password that you have entered is incorrect"))
{
throw new LoginException(LoginError.InvalidCredentials);
}
throw new LoginException(content);
}
if (loginStage is not LoginSuccessStage successStage)
{
throw new InvalidOperationException("Unexpected login stage at this point. " + loginStage.GetType())
{
Data = {{"stage", loginStage}}
};
}
return successStage.TransferParameters;
}
}
@@ -1,20 +0,0 @@
using Microsoft.Extensions.Logging;
using SteamLib.Core.Interfaces;
namespace SteamLib.Login.Default;
public class LoginExecutorOptions
{
public ILoginConsumer Consumer { get; }
public HttpClient HttpClient { get; }
public ILogger? Logger { get; init; }
public IEmailProvider? EmailAuthProvider { get; init; }
public ICaptchaResolver? CaptchaResolver { get; init; }
public ISteamGuardProvider? SteamGuardProvider { get; init; }
public LoginExecutorOptions(ILoginConsumer consumer, HttpClient httpClient)
{
Consumer = consumer;
HttpClient = httpClient;
}
}
@@ -1,18 +0,0 @@
using Newtonsoft.Json;
namespace SteamLib.Login.Default;
#pragma warning disable CS8618
/// <summary>
/// Class to Deserialize the json response strings after the login/>
/// </summary>
internal class LoginResultJson
{
[JsonProperty("success")] public bool Success { get; set; }
[JsonProperty("message")] public string Message { get; set; }
[JsonProperty("captcha_needed")] public bool CaptchaNeeded { get; set; }
[JsonProperty("captcha_gid")] public string CaptchaGid { get; set; }
[JsonProperty("emailauth_needed")] public bool EmailAuthNeeded { get; set; }
[JsonProperty("emailsteamid")] public string EmailSteamId { get; set; }
[JsonProperty("requires_twofactor")] public bool RequiresTwoFactor { get; set; }
}
@@ -1,252 +0,0 @@
using AchiesUtilities.Web.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SteamLib.Authentication;
using SteamLib.Utility;
namespace SteamLib.Login.Default;
internal abstract class LoginStage
{
protected Dictionary<string, string> Data { get; }
protected LoginStage()
{
Data = new Dictionary<string, string>();
}
protected LoginStage(Dictionary<string, string> data)
{
Data = data;
}
protected bool CheckIfLoginCompleted(LoginResultJson json)
{
return !json.CaptchaNeeded && !json.EmailAuthNeeded && !json.RequiresTwoFactor && json.Success;
}
protected async Task<LoginResultJson> ConvertJson(HttpResponseMessage response)
{
response.EnsureSuccessStatusCode();
var webResponseString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<LoginResultJson>(webResponseString)!;
}
protected async Task<LoginSuccessStage> CompleteLogin(HttpResponseMessage response)
{
var jsonStr = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(jsonStr);
var transferParameters = json["transfer_parameters"]!.ToObject<TransferParameters>()!;
return new LoginSuccessStage(transferParameters);
}
protected async Task<HttpResponseMessage> LoginRequest(HttpClient client,
CancellationToken cancellationToken = default)
{
return await client.PostAsync("https://steamcommunity.com/login/dologin", new FormUrlEncodedContent(Data),
cancellationToken);
}
}
internal class LoginErrorStage : LoginStage
{
public readonly int ErrorCode;
public readonly string ErrorString;
public readonly HttpResponseMessage Response;
public readonly LoginStage Stage;
public LoginErrorStage(string errorString, HttpResponseMessage responseMessage, LoginStage stage)
{
ErrorString = errorString;
Stage = stage;
ErrorCode = Utilities.GetSuccessCode(responseMessage.Content.ReadAsStringSync());
Response = responseMessage;
}
}
internal class GetRsaStage : LoginStage
{
public GetRsaStage(Dictionary<string, string> data) : base(data)
{
}
public GetRsaStage(string userName)
{
Data["username"] = userName;
}
/// <summary>
/// </summary>
/// <param name="client"></param>
/// <param name="cancellationToken"></param>
/// <returns> <see cref="ReadyToLoginStage" /> if success</returns>
public async Task<LoginStage> Proceed(HttpClient client, CancellationToken cancellationToken = default)
{
var content = new FormUrlEncodedContent(Data);
var response = await client.PostAsync("https://steamcommunity.com/login/getrsakey", content, cancellationToken);
var responseString = await response.Content.ReadAsStringAsync(cancellationToken);
var rsaJson = JsonConvert.DeserializeObject<RsaKeyJson>(responseString)!;
if (!rsaJson.success)
return new LoginErrorStage("Can't get RSA", response, this);
Data["rsatimestamp"] = rsaJson.timestamp;
return new ReadyToLoginStage(Data, rsaJson.publickey_exp, rsaJson.publickey_mod);
}
}
internal class ReadyToLoginStage : LoginStage
{
private readonly string _publicKeyExp;
private readonly string _publicKeyMod;
internal ReadyToLoginStage(Dictionary<string, string> data, string publicKeyExp, string publicKeyMod) : base(data)
{
_publicKeyExp = publicKeyExp;
_publicKeyMod = publicKeyMod;
}
public async Task<LoginStage> Proceed(HttpClient client, string password, string loginFriendlyName,
CancellationToken cancellationToken = default)
{
var encryptedBase64Password =
EncryptionHelper.ToBase64EncryptedPassword(_publicKeyExp, _publicKeyMod, password);
var unixTimestamp = (int) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
Data["password"] = encryptedBase64Password;
Data["remember_login"] = "true";
Data["loginfriendlyname"] = loginFriendlyName;
Data["donotcache"] =
unixTimestamp + "000"; // Added three "0"'s because Steam has a weird unix timestamp interpretation.
_ = await client.GetAsync("https://steamcommunity.com/", cancellationToken);
var webResponse = await LoginRequest(client, cancellationToken);
var result = await ConvertJson(webResponse);
if (CheckIfLoginCompleted(result))
{
return await CompleteLogin(webResponse);
}
if (result.CaptchaNeeded)
{
return new CaptchaNeededStage(Data, result.CaptchaGid);
}
if (result.EmailAuthNeeded)
{
return new EmailAuthStage(Data);
}
if (result.RequiresTwoFactor)
{
return new TwoFactorStage(Data);
}
return new LoginErrorStage("Can't proceed login.", webResponse, this);
}
}
internal class TwoFactorStage : LoginStage
{
internal TwoFactorStage(Dictionary<string, string> data) : base(data)
{
}
public async Task<LoginStage> Proceed(HttpClient client, string twoFactorCode,
CancellationToken cancellationToken = default)
{
Data["twofactorcode"] = twoFactorCode;
var webResponse = await LoginRequest(client, cancellationToken);
var result = await ConvertJson(webResponse);
if (CheckIfLoginCompleted(result))
{
return await CompleteLogin(webResponse);
}
if (result.CaptchaNeeded)
{
return new CaptchaNeededStage(Data, result.CaptchaGid);
}
if (result.RequiresTwoFactor)
{
return new TwoFactorStage(Data);
}
return new LoginErrorStage("Can't proceed login. Bad TwoFactor code", webResponse, this);
}
}
internal class EmailAuthStage : LoginStage
{
internal EmailAuthStage(Dictionary<string, string> data) : base(data)
{
}
public async Task<LoginStage> Proceed(HttpClient client, string emailCode,
CancellationToken cancellationToken = default)
{
Data["emailauth"] = emailCode;
var webResponse = await LoginRequest(client, cancellationToken);
var result = await ConvertJson(webResponse);
if (CheckIfLoginCompleted(result))
{
return await CompleteLogin(webResponse);
}
if (result.CaptchaNeeded)
{
return new CaptchaNeededStage(Data, result.CaptchaGid);
}
if (result.EmailAuthNeeded)
{
return new EmailAuthStage(Data);
}
return new LoginErrorStage("Can't proceed login. Bad Email code", webResponse, this);
}
}
internal class CaptchaNeededStage : LoginStage
{
internal Uri CaptchaImage { get; }
public CaptchaNeededStage(Dictionary<string, string> data, string captchaGid) : base(data)
{
captchaGid = Uri.EscapeDataString(captchaGid);
Data["captchagid"] = captchaGid;
CaptchaImage = new Uri("https://steamcommunity.com/login/rendercaptcha?gid=" + captchaGid);
}
/// <summary>
/// </summary>
/// <param name="client"></param>
/// <param name="captchaText"></param>
/// <param name="cancellationToken"></param>
/// <returns><see cref="ReadyToLoginStage" /> or <see cref="LoginErrorStage" /></returns>
public async Task<LoginStage> Proceed(HttpClient client, string captchaText,
CancellationToken cancellationToken = default)
{
Data["captcha_text"] = captchaText;
//When captcha required we need to do login from start
var rsaStage = new GetRsaStage(Data);
var res = await rsaStage.Proceed(client, cancellationToken);
return res;
}
}
internal class LoginSuccessStage : LoginStage
{
public TransferParameters TransferParameters { get; }
public LoginSuccessStage(TransferParameters transferParameters)
{
TransferParameters = transferParameters;
}
}
@@ -1,8 +0,0 @@
using SteamLib.Core.Interfaces;
namespace SteamLib.Login.Default;
internal class NullLoginConsumer : ILoginConsumer
{
public string FriendlyName { get; } = "null";
}
@@ -1,17 +0,0 @@
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
#pragma warning disable CS8618
namespace SteamLib.Login.Default;
/// <summary>
/// Class to Deserialize the json response strings of the getResKey request/>
/// </summary>
internal class RsaKeyJson
{
public bool success { get; set; }
public string publickey_mod { get; set; }
public string publickey_exp { get; set; }
public string timestamp { get; set; }
}
@@ -1,254 +0,0 @@
using AchiesUtilities.Web.Extensions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SteamLib.Account;
using SteamLib.Core;
using SteamLib.Core.Enums;
using SteamLib.Core.Interfaces;
using SteamLib.Core.Models;
using SteamLib.Core.StatusCodes;
using SteamLib.Exceptions;
using SteamLib.Login.Default;
using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Enums;
using SteamLib.ProtoCore.Exceptions;
using SteamLib.ProtoCore.Services;
using SteamLib.Utility;
using SteamLib.Web;
namespace SteamLib.Authentication.LoginV2;
public class LoginV2Executor
{
public static ILoginConsumer NullConsumer { get; } = new NullLoginConsumer();
public ILoginConsumer Caller { get; }
public HttpClient HttpClient { get; }
public ILogger? Logger { get; init; }
public IEmailProvider? EmailAuthProvider { get; init; }
public ICaptchaResolver? CaptchaResolver { get; init; }
public ISteamGuardProvider? SteamGuardProvider { get; init; }
public string WebsiteId { get; init; }
public DeviceDetails DeviceDetails { get; init; }
private LoginV2Executor(LoginV2ExecutorOptions options)
{
Caller = options.Consumer;
HttpClient = options.HttpClient;
Logger = options.Logger;
EmailAuthProvider = options.EmailAuthProvider;
SteamGuardProvider = options.SteamGuardProvider;
WebsiteId = options.GetWebsiteIdOrDefault();
DeviceDetails = options.DeviceDetails ?? DeviceDetails.CreateDefault();
}
/// <summary>
/// Do log in on <see href="https://steamcommunity.com/">SteamCommunity</see>.<br />
/// Some functions require proper SessionId. But <see cref="SessionData" /> contains only SteamCommunity related
/// SessionId and some functions on other services may not work
/// </summary>
/// <param name="options"></param>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="cancellationToken"></param>
/// <returns><see cref="SessionData" /> or <see cref="MobileSessionData" /> depending on which token type is returned</returns>
/// <exception cref="LoginException"></exception>
/// <exception cref="EResultException"></exception>
/// <exception cref="NotSupportedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static async Task<ISessionData> DoLogin(LoginV2ExecutorOptions options, string username, string password,
CancellationToken cancellationToken = default) //TODO: logs
{
var executor = new LoginV2Executor(options);
var client = executor.HttpClient;
var sessionId = await SteamWebApi.GetLoginSessionId(client, cancellationToken);
var rsgMsg = new GetPasswordRSAPublicKey_Request
{
AccountName = username
};
var rsaResp = await client.GetProto<GetPasswordRSAPublicKey_Response>(
"https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1", rsgMsg,
cancellationToken);
var encodedPassword =
EncryptionHelper.ToBase64EncryptedPassword(rsaResp.PublickKeyExp, rsaResp.PublickKeyMod, password);
var beginAuthMsg = new BeginAuthSessionViaCredentials_Request
{
DeviceFriendlyName = string.Empty,
AccountName = username,
EncryptedPassword = encodedPassword,
EncryptionTimestamp = rsaResp.Timestamp,
RememberLogin = true,
PlatformType = executor.DeviceDetails.PlatformType,
Persistence = 1,
WebsiteId = executor.WebsiteId,
DeviceDetails = executor.DeviceDetails
};
BeginAuthSessionViaCredentials_Response beginAuthResp;
try
{
beginAuthResp = await client.PostProto<BeginAuthSessionViaCredentials_Response>(
"https://api.steampowered.com/IAuthenticationService/BeginAuthSessionViaCredentials/v1", beginAuthMsg,
cancellationToken);
}
catch (EResultException ex)
when (ex.Result == EResult.InvalidPassword)
{
throw new LoginException(LoginError.InvalidCredentials);
}
var clientId = beginAuthResp.ClientId;
var steamId = (long) beginAuthResp.Steamid;
var conf = beginAuthResp.AllowedConfirmations.FirstOrDefault(c =>
c.ConfirmationType is EAuthSessionGuardType.EmailCode or EAuthSessionGuardType.DeviceCode);
conf ??= beginAuthResp.AllowedConfirmations.FirstOrDefault();
switch (conf?.ConfirmationType)
{
case EAuthSessionGuardType.None:
break;
case EAuthSessionGuardType.DeviceCode:
case EAuthSessionGuardType.EmailCode:
await UpdateWithCode(executor, clientId, (ulong) steamId, conf.ConfirmationType);
break;
case EAuthSessionGuardType.Unknown:
case EAuthSessionGuardType.DeviceConfirmation:
case EAuthSessionGuardType.EmailConfirmation:
case EAuthSessionGuardType.MachineToken:
case EAuthSessionGuardType.LegacyMachineAuth:
throw new NotSupportedException(
$"Auth confirmation type of {conf.ConfirmationType} is not implemented yet");
default:
throw new ArgumentOutOfRangeException(nameof(conf.ConfirmationType), conf?.ConfirmationType,
"Unknown confirmation type or null");
}
var pollSessionMsg = new PollAuthSessionStatus_Request
{
ClientId = clientId,
RequestId = beginAuthResp.RequestId
};
var pollResp =
await client.PostProto<PollAuthSessionStatus_Response>(
"https://api.steampowered.com/IAuthenticationService/PollAuthSessionStatus/v1", pollSessionMsg,
cancellationToken);
SteamAuthToken refreshToken;
try
{
refreshToken = SteamTokenHelper.Parse(pollResp.RefreshToken);
if (refreshToken.Type is not (SteamAccessTokenType.Refresh or SteamAccessTokenType.MobileRefresh))
throw new ArgumentException(
"Refresh token must be of type Refresh or MobileRefresh. No 'renew' audience found in JWT.",
nameof(pollResp.RefreshToken)); //Argument exception for less code
}
catch (ArgumentException ex)
{
throw new LoginException(null,
"Steam returned invalid refresh token or it's schema was unsupported. See inner exception for more details",
LoginError.UndefinedError, ex);
}
var data = new Dictionary<string, string>
{
{"nonce", pollResp.RefreshToken},
{"sessionid", sessionId}
};
var finalize = await client.PostAsync("https://login.steampowered.com/jwt/finalizelogin",
new FormUrlEncodedContent(data), cancellationToken);
var finalizeContent = await finalize.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
var finalizeResp =
SteamLibErrorMonitor.HandleResponse(finalizeContent,
() => JsonConvert.DeserializeObject<FinalizeLoginJson>(finalizeContent)!);
List<SteamAuthToken> tokens = new();
foreach (var transferInfo in finalizeResp.TransferInfo)
{
var transferData = new Dictionary<string, string>
{
{"nonce", transferInfo.TransferInfoParams.Nonce},
{"auth", transferInfo.TransferInfoParams.Auth},
{"steamID", steamId.ToString()}
};
try
{
var req = new HttpRequestMessage(HttpMethod.Post, transferInfo.Url);
req.Content = new FormUrlEncodedContent(transferData);
req.Headers.Referrer = SteamDomains.GetDomainUri(SteamDomain.Store);
var transferResp = await client.SendAsync(req, cancellationToken);
var transferContent = await transferResp.ReadAsStringEnsureSuccessAsync(cancellationToken);
var status = JObject.Parse(transferContent);
var result = status["result"]?.Value<int>();
if (result != null)
SteamStatusCode
.ValidateSuccessOrThrow(result
.Value); //TODO: Fix steam.tv token transfer (result always 8). But who really cares..
var tokenStr = SteamTokenHelper.ExtractJwtFromSetCookiesHeader(transferResp.Headers);
var token = SteamTokenHelper.Parse(tokenStr);
tokens.Add(token);
}
catch (Exception ex)
{
executor.Logger?.Log(LogLevel.Warning, ex, "Can't transfer tokens for URI: {uri}", transferInfo.Url);
}
}
var accessToken = SteamTokenHelper.Parse(pollResp.AccessToken);
if (accessToken.Type == SteamAccessTokenType.Mobile)
{
return new MobileSessionData(sessionId, SteamId.FromSteam64(steamId), refreshToken, accessToken, tokens);
}
return new SessionData(sessionId, SteamId.FromSteam64(steamId), refreshToken, tokens);
}
private static async Task UpdateWithCode(LoginV2Executor executor, ulong clientId, ulong steamId,
EAuthSessionGuardType guardType)
{
string? code;
if (guardType == EAuthSessionGuardType.DeviceCode)
{
if (executor.SteamGuardProvider != null)
code = await executor.SteamGuardProvider.GetSteamGuardCode(executor.Caller);
else
{
throw new LoginException(LoginError.SteamGuardRequired);
}
}
else
{
var t = executor.EmailAuthProvider?.GetEmailAuthCode(executor.Caller) ??
throw new LoginException(LoginError.EmailAuthRequired);
code = await t;
}
var updateCodeMsg = new UpdateAuthSessionWithSteamGuardCode_Request
{
ClientId = clientId,
Code = code,
CodeType = guardType,
Steamid = steamId
};
await executor.HttpClient.PostProtoEnsureSuccess(
new Uri("https://api.steampowered.com/IAuthenticationService/UpdateAuthSessionWithSteamGuardCode/v1"),
updateCodeMsg);
}
}
-757
View File
@@ -1,757 +0,0 @@
// ReSharper disable All
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace SteamLib.Core.Enums;
public enum Country
{
Afghanistan,
Aland_Islands,
Albania,
Algeria,
American_Samoa,
Andorra,
Angola,
Anguilla,
Antigua_and_Barbuda,
Argentina,
Armenia,
Aruba,
Australia,
Austria,
Azerbaijan,
Bahamas,
Bahrain,
Bangladesh,
Barbados,
Belarus,
Belgium,
Belize,
Bhutan,
Bolivia,
Bosnia_and_Herzegovina,
Botswana,
Brazil,
Brunei_Darussalam,
Bulgaria,
Burundi,
Cambodia,
Cameroon,
Canada,
Cape_Verde,
Cayman_Islands,
Chad,
Chile,
China,
Colombia,
Congo,
Costa_Rica,
Cote_d_Ivoire,
Croatia,
Cyprus,
Czech_Republic,
Denmark,
Dominica,
Dominican_Republic,
Ecuador,
Egypt,
El_Salvador,
Estonia,
Eswatini,
Ethiopia,
Faroe_Islands,
Finland,
France,
French_Guiana,
French_Polynesia,
Gabon,
Georgia,
Germany,
Ghana,
Gibraltar,
Greece,
Greenland,
Grenada,
Guadeloupe,
Guam,
Guatemala,
Guernsey,
Guyana,
Haiti,
Honduras,
Hong_Kong,
Hungary,
Iceland,
India,
Indonesia,
Iran,
Iraq,
Ireland,
Isle_of_Man,
Israel,
Italy,
Jamaica,
Japan,
Jersey,
Jordan,
Kazakhstan,
Kenya,
Korea,
Kuwait,
Kyrgyzstan,
Laos,
Latvia,
Lebanon,
Libya,
Liechtenstein,
Lithuania,
Luxembourg,
Macau,
Macedonia,
Madagascar,
Malaysia,
Maldives,
Mali,
Malta,
Martinique,
Mauritius,
Mayotte,
Mexico,
Moldova,
Monaco,
Mongolia,
Montenegro,
Morocco,
Mozambique,
Myanmar,
Namibia,
Nepal,
Netherlands,
New_Zealand,
Nicaragua,
Nigeria,
Northern_Mariana_Islands,
Norway,
Oman,
Pakistan,
Palestinian_Territory,
Panama,
Papua_New_Guinea,
Paraguay,
Peru,
Philippines,
Poland,
Portugal,
Puerto_Rico,
Qatar,
Reunion,
Romania,
Russian_Federation,
Rwanda,
Saint_Kitts_and_Nevis,
Saint_Lucia,
Saint_Vincent_and_the_Grenadines,
Saudi_Arabia,
Senegal,
Serbia,
Seychelles,
Singapore,
Slovakia,
Slovenia,
South_Africa,
Spain,
Sri_Lanka,
Sudan,
Suriname,
Sweden,
Switzerland,
Taiwan,
Tajikistan,
Tanzania,
Thailand,
Togo,
Trinidad_and_Tobago,
Tunisia,
Turkey,
Turkmenistan,
Turks_and_Caicos_Islands,
Uganda,
Ukraine,
United_Arab_Emirates,
United_Kingdom,
United_States,
Uruguay,
Uzbekistan,
Venezuela,
Viet_Nam,
Virgin_Islands,
Yemen,
Zambia,
Zimbabwe
}
public static class CountryResolver
{
public static IReadOnlyDictionary<string, Country> ResolveByCountryCode = new ReadOnlyDictionary<string, Country>(
new Dictionary<string, Country>()
{
{"US", Country.United_States},
{"CA", Country.Canada},
{"AF", Country.Afghanistan},
{"AX", Country.Aland_Islands},
{"AL", Country.Albania},
{"DZ", Country.Algeria},
{"AS", Country.American_Samoa},
{"AD", Country.Andorra},
{"AO", Country.Angola},
{"AI", Country.Anguilla},
{"AG", Country.Antigua_and_Barbuda},
{"AR", Country.Argentina},
{"AM", Country.Armenia},
{"AW", Country.Aruba},
{"AU", Country.Australia},
{"AT", Country.Austria},
{"AZ", Country.Azerbaijan},
{"BS", Country.Bahamas},
{"BH", Country.Bahrain},
{"BD", Country.Bangladesh},
{"BB", Country.Barbados},
{"BY", Country.Belarus},
{"BE", Country.Belgium},
{"BZ", Country.Belize},
{"BT", Country.Bhutan},
{"BO", Country.Bolivia},
{"BA", Country.Bosnia_and_Herzegovina},
{"BW", Country.Botswana},
{"BR", Country.Brazil},
{"BN", Country.Brunei_Darussalam},
{"BG", Country.Bulgaria},
{"BI", Country.Burundi},
{"KH", Country.Cambodia},
{"CM", Country.Cameroon},
{"CV", Country.Cape_Verde},
{"KY", Country.Cayman_Islands},
{"TD", Country.Chad},
{"CL", Country.Chile},
{"CN", Country.China},
{"CO", Country.Colombia},
{"CD", Country.Congo},
{"CR", Country.Costa_Rica},
{"CI", Country.Cote_d_Ivoire},
{"HR", Country.Croatia},
{"CY", Country.Cyprus},
{"CZ", Country.Czech_Republic},
{"DK", Country.Denmark},
{"DM", Country.Dominica},
{"DO", Country.Dominican_Republic},
{"EC", Country.Ecuador},
{"EG", Country.Egypt},
{"SV", Country.El_Salvador},
{"EE", Country.Estonia},
{"ET", Country.Ethiopia},
{"FO", Country.Faroe_Islands},
{"FI", Country.Finland},
{"FR", Country.France},
{"GF", Country.French_Guiana},
{"PF", Country.French_Polynesia},
{"GA", Country.Gabon},
{"GE", Country.Georgia},
{"DE", Country.Germany},
{"GH", Country.Ghana},
{"GI", Country.Gibraltar},
{"GR", Country.Greece},
{"GL", Country.Greenland},
{"GD", Country.Grenada},
{"GP", Country.Guadeloupe},
{"GU", Country.Guam},
{"GT", Country.Guatemala},
{"GG", Country.Guernsey},
{"GY", Country.Guyana},
{"HT", Country.Haiti},
{"HN", Country.Honduras},
{"HK", Country.Hong_Kong},
{"HU", Country.Hungary},
{"IS", Country.Iceland},
{"IN", Country.India},
{"ID", Country.Indonesia},
{"IQ", Country.Iraq},
{"IE", Country.Ireland},
{"IR", Country.Iran},
{"IM", Country.Isle_of_Man},
{"IL", Country.Israel},
{"IT", Country.Italy},
{"JM", Country.Jamaica},
{"JP", Country.Japan},
{"JE", Country.Jersey},
{"JO", Country.Jordan},
{"KZ", Country.Kazakhstan},
{"KE", Country.Kenya},
{"KR", Country.Korea},
{"KW", Country.Kuwait},
{"KG", Country.Kyrgyzstan},
{"LA", Country.Laos},
{"LV", Country.Latvia},
{"LB", Country.Lebanon},
{"LY", Country.Libya},
{"LI", Country.Liechtenstein},
{"LT", Country.Lithuania},
{"LU", Country.Luxembourg},
{"MO", Country.Macau},
{"MK", Country.Macedonia},
{"MG", Country.Madagascar},
{"MY", Country.Malaysia},
{"MV", Country.Maldives},
{"ML", Country.Mali},
{"MT", Country.Malta},
{"MQ", Country.Martinique},
{"MU", Country.Mauritius},
{"YT", Country.Mayotte},
{"MX", Country.Mexico},
{"MD", Country.Moldova},
{"MC", Country.Monaco},
{"MN", Country.Mongolia},
{"ME", Country.Montenegro},
{"MA", Country.Morocco},
{"MZ", Country.Mozambique},
{"MM", Country.Myanmar},
{"NA", Country.Namibia},
{"NP", Country.Nepal},
{"NL", Country.Netherlands},
{"NZ", Country.New_Zealand},
{"NI", Country.Nicaragua},
{"NG", Country.Nigeria},
{"MP", Country.Northern_Mariana_Islands},
{"NO", Country.Norway},
{"OM", Country.Oman},
{"PK", Country.Pakistan},
{"PS", Country.Palestinian_Territory},
{"PA", Country.Panama},
{"PG", Country.Papua_New_Guinea},
{"PY", Country.Paraguay},
{"PE", Country.Peru},
{"PH", Country.Philippines},
{"PL", Country.Poland},
{"PT", Country.Portugal},
{"PR", Country.Puerto_Rico},
{"QA", Country.Qatar},
{"RE", Country.Reunion},
{"RO", Country.Romania},
{"RU", Country.Russian_Federation},
{"RW", Country.Rwanda},
{"LC", Country.Saint_Lucia},
{"SA", Country.Saudi_Arabia},
{"SN", Country.Senegal},
{"RS", Country.Serbia},
{"SC", Country.Seychelles},
{"SG", Country.Singapore},
{"SK", Country.Slovakia},
{"SI", Country.Slovenia},
{"ZA", Country.South_Africa},
{"ES", Country.Spain},
{"LK", Country.Sri_Lanka},
{"KN", Country.Saint_Kitts_and_Nevis},
{"PM", Country.Saint_Vincent_and_the_Grenadines},
{"SD", Country.Sudan},
{"SR", Country.Suriname},
{"SE", Country.Sweden},
{"CH", Country.Switzerland},
{"TW", Country.Taiwan},
{"TJ", Country.Tajikistan},
{"TZ", Country.Tanzania},
{"TH", Country.Thailand},
{"TG", Country.Togo},
{"TT", Country.Trinidad_and_Tobago},
{"TN", Country.Tunisia},
{"TR", Country.Turkey},
{"TM", Country.Turkmenistan},
{"TC", Country.Turks_and_Caicos_Islands},
{"UG", Country.Uganda},
{"UA", Country.Ukraine},
{"GB", Country.United_Kingdom},
{"VI", Country.Virgin_Islands},
{"AE", Country.United_Arab_Emirates},
{"UY", Country.Uruguay},
{"UZ", Country.Uzbekistan},
{"VE", Country.Venezuela},
{"VN", Country.Viet_Nam},
{"YE", Country.Yemen},
{"ZM", Country.Zambia},
{"ZW", Country.Zimbabwe}
});
public static IReadOnlyDictionary<Country, string> ResolveString { get; } = new Dictionary<Country, string>
{
{Country.Afghanistan, "Afghanistan"},
{Country.Aland_Islands, "Aland Islands"},
{Country.Albania, "Albania"},
{Country.Algeria, "Algeria"},
{Country.American_Samoa, "American Samoa"},
{Country.Andorra, "Andorra"},
{Country.Angola, "Angola"},
{Country.Anguilla, "Anguilla"},
{Country.Antigua_and_Barbuda, "Antigua and Barbuda"},
{Country.Argentina, "Argentina"},
{Country.Armenia, "Armenia"},
{Country.Aruba, "Aruba"},
{Country.Australia, "Australia"},
{Country.Austria, "Austria"},
{Country.Azerbaijan, "Azerbaijan"},
{Country.Bahamas, "Bahamas"},
{Country.Bahrain, "Bahrain"},
{Country.Bangladesh, "Bangladesh"},
{Country.Barbados, "Barbados"},
{Country.Belarus, "Belarus"},
{Country.Belgium, "Belgium"},
{Country.Belize, "Belize"},
{Country.Bhutan, "Bhutan"},
{Country.Bolivia, "Bolivia"},
{Country.Bosnia_and_Herzegovina, "Bosnia and Herzegovina"},
{Country.Botswana, "Botswana"},
{Country.Brazil, "Brazil"},
{Country.Brunei_Darussalam, "Brunei Darussalam"},
{Country.Bulgaria, "Bulgaria"},
{Country.Burundi, "Burundi"},
{Country.Cambodia, "Cambodia"},
{Country.Cameroon, "Cameroon"},
{Country.Canada, "Canada"},
{Country.Cape_Verde, "Cape Verde"},
{Country.Cayman_Islands, "Cayman Islands"},
{Country.Chad, "Chad"},
{Country.Chile, "Chile"},
{Country.China, "China"},
{Country.Colombia, "Colombia"},
{Country.Congo, "Congo, the Democratic Republic of the"},
{Country.Costa_Rica, "Costa Rica"},
{Country.Cote_d_Ivoire, "Cote d'Ivoire"},
{Country.Croatia, "Croatia"},
{Country.Cyprus, "Cyprus"},
{Country.Czech_Republic, "Czech Republic"},
{Country.Denmark, "Denmark"},
{Country.Dominica, "Dominica"},
{Country.Dominican_Republic, "Dominican Republic"},
{Country.Ecuador, "Ecuador"},
{Country.Egypt, "Egypt"},
{Country.El_Salvador, "El Salvador"},
{Country.Estonia, "Estonia"},
{Country.Eswatini, "Eswatini"},
{Country.Ethiopia, "Ethiopia"},
{Country.Faroe_Islands, "Faroe Islands"},
{Country.Finland, "Finland"},
{Country.France, "France"},
{Country.French_Guiana, "French Guiana"},
{Country.French_Polynesia, "French Polynesia"},
{Country.Gabon, "Gabon"},
{Country.Georgia, "Georgia"},
{Country.Germany, "Germany"},
{Country.Ghana, "Ghana"},
{Country.Gibraltar, "Gibraltar"},
{Country.Greece, "Greece"},
{Country.Greenland, "Greenland"},
{Country.Grenada, "Grenada"},
{Country.Guadeloupe, "Guadeloupe"},
{Country.Guam, "Guam"},
{Country.Guatemala, "Guatemala"},
{Country.Guernsey, "Guernsey"},
{Country.Guyana, "Guyana"},
{Country.Haiti, "Haiti"},
{Country.Honduras, "Honduras"},
{Country.Hong_Kong, "Hong Kong"},
{Country.Hungary, "Hungary"},
{Country.Iceland, "Iceland"},
{Country.India, "India"},
{Country.Indonesia, "Indonesia"},
{Country.Iran, "Iran"},
{Country.Iraq, "Iraq"},
{Country.Ireland, "Ireland"},
{Country.Isle_of_Man, "Isle of Man"},
{Country.Israel, "Israel"},
{Country.Italy, "Italy"},
{Country.Jamaica, "Jamaica"},
{Country.Japan, "Japan"},
{Country.Jersey, "Jersey"},
{Country.Jordan, "Jordan"},
{Country.Kazakhstan, "Kazakhstan"},
{Country.Kenya, "Kenya"},
{Country.Korea, "Korea, Republic of"},
{Country.Kuwait, "Kuwait"},
{Country.Kyrgyzstan, "Kyrgyzstan"},
{Country.Laos, "Lao People's Democratic Republic"},
{Country.Latvia, "Latvia"},
{Country.Lebanon, "Lebanon"},
{Country.Libya, "Libya"},
{Country.Liechtenstein, "Liechtenstein"},
{Country.Lithuania, "Lithuania"},
{Country.Luxembourg, "Luxembourg"},
{Country.Macau, "Macau"},
{Country.Macedonia, "Macedonia, the former Yugoslav Republic of"},
{Country.Madagascar, "Madagascar"},
{Country.Malaysia, "Malaysia"},
{Country.Maldives, "Maldives"},
{Country.Mali, "Mali"},
{Country.Malta, "Malta"},
{Country.Martinique, "Martinique"},
{Country.Mauritius, "Mauritius"},
{Country.Mayotte, "Mayotte"},
{Country.Mexico, "Mexico"},
{Country.Moldova, "Moldova, Republic of"},
{Country.Monaco, "Monaco"},
{Country.Mongolia, "Mongolia"},
{Country.Montenegro, "Montenegro"},
{Country.Morocco, "Morocco"},
{Country.Mozambique, "Mozambique"},
{Country.Myanmar, "Myanmar"},
{Country.Namibia, "Namibia"},
{Country.Nepal, "Nepal"},
{Country.Netherlands, "Netherlands"},
{Country.New_Zealand, "New Zealand"},
{Country.Nicaragua, "Nicaragua"},
{Country.Nigeria, "Nigeria"},
{Country.Northern_Mariana_Islands, "Northern Mariana Islands"},
{Country.Norway, "Norway"},
{Country.Oman, "Oman"},
{Country.Pakistan, "Pakistan"},
{Country.Palestinian_Territory, "Palestinian Territory, Occupied"},
{Country.Panama, "Panama"},
{Country.Papua_New_Guinea, "Papua New Guinea"},
{Country.Paraguay, "Paraguay"},
{Country.Peru, "Peru"},
{Country.Philippines, "Philippines"},
{Country.Poland, "Poland"},
{Country.Portugal, "Portugal"},
{Country.Puerto_Rico, "Puerto Rico"},
{Country.Qatar, "Qatar"},
{Country.Reunion, "Reunion"},
{Country.Romania, "Romania"},
{Country.Russian_Federation, "Russian Federation"},
{Country.Rwanda, "Rwanda"},
{Country.Saint_Kitts_and_Nevis, "Saint Kitts and Nevis"},
{Country.Saint_Lucia, "Saint Lucia"},
{Country.Saint_Vincent_and_the_Grenadines, "Saint Vincent and the Grenadines"},
{Country.Saudi_Arabia, "Saudi Arabia"},
{Country.Senegal, "Senegal"},
{Country.Serbia, "Serbia"},
{Country.Seychelles, "Seychelles"},
{Country.Singapore, "Singapore"},
{Country.Slovakia, "Slovakia"},
{Country.Slovenia, "Slovenia"},
{Country.South_Africa, "South Africa"},
{Country.Spain, "Spain"},
{Country.Sri_Lanka, "Sri Lanka"},
{Country.Sudan, "Sudan"},
{Country.Suriname, "Suriname"},
{Country.Sweden, "Sweden"},
{Country.Switzerland, "Switzerland"},
{Country.Taiwan, "Taiwan"},
{Country.Tajikistan, "Tajikistan"},
{Country.Tanzania, "Tanzania, United Republic of"},
{Country.Thailand, "Thailand"},
{Country.Togo, "Togo"},
{Country.Trinidad_and_Tobago, "Trinidad and Tobago"},
{Country.Tunisia, "Tunisia"},
{Country.Turkey, "Turkey"},
{Country.Turkmenistan, "Turkmenistan"},
{Country.Turks_and_Caicos_Islands, "Turks and Caicos Islands"},
{Country.Uganda, "Uganda"},
{Country.Ukraine, "Ukraine"},
{Country.United_Arab_Emirates, "United Arab Emirates"},
{Country.United_Kingdom, "United Kingdom"},
{Country.United_States, "United States"},
{Country.Uruguay, "Uruguay"},
{Country.Uzbekistan, "Uzbekistan"},
{Country.Venezuela, "Venezuela"},
{Country.Viet_Nam, "Viet Nam"},
{Country.Virgin_Islands, "Virgin Islands, U.S."},
{Country.Yemen, "Yemen"},
{Country.Zambia, "Zambia"},
{Country.Zimbabwe, "Zimbabwe"}
};
public static IReadOnlyDictionary<string, Country> ResolveCountry { get; } = new Dictionary<string, Country>()
{
{"Afghanistan", Country.Afghanistan},
{"Aland Islands", Country.Aland_Islands},
{"Albania", Country.Albania},
{"Algeria", Country.Algeria},
{"American Samoa", Country.American_Samoa},
{"Andorra", Country.Andorra},
{"Angola", Country.Angola},
{"Anguilla", Country.Anguilla},
{"Antigua and Barbuda", Country.Antigua_and_Barbuda},
{"Argentina", Country.Argentina},
{"Armenia", Country.Armenia},
{"Aruba", Country.Aruba},
{"Australia", Country.Australia},
{"Austria", Country.Austria},
{"Azerbaijan", Country.Azerbaijan},
{"Bahamas", Country.Bahamas},
{"Bahrain", Country.Bahrain},
{"Bangladesh", Country.Bangladesh},
{"Barbados", Country.Barbados},
{"Belarus", Country.Belarus},
{"Belgium", Country.Belgium},
{"Belize", Country.Belize},
{"Bhutan", Country.Bhutan},
{"Bolivia", Country.Bolivia},
{"Bosnia and Herzegovina", Country.Bosnia_and_Herzegovina},
{"Botswana", Country.Botswana},
{"Brazil", Country.Brazil},
{"Brunei Darussalam", Country.Brunei_Darussalam},
{"Bulgaria", Country.Bulgaria},
{"Burundi", Country.Burundi},
{"Cambodia", Country.Cambodia},
{"Cameroon", Country.Cameroon},
{"Canada", Country.Canada},
{"Cape Verde", Country.Cape_Verde},
{"Cayman Islands", Country.Cayman_Islands},
{"Chad", Country.Chad},
{"Chile", Country.Chile},
{"China", Country.China},
{"Colombia", Country.Colombia},
{"Congo, the Democratic Republic of the", Country.Congo},
{"Costa Rica", Country.Costa_Rica},
{"Cote d'Ivoire", Country.Cote_d_Ivoire},
{"Croatia", Country.Croatia},
{"Cyprus", Country.Cyprus},
{"Czech Republic", Country.Czech_Republic},
{"Denmark", Country.Denmark},
{"Dominica", Country.Dominica},
{"Dominican Republic", Country.Dominican_Republic},
{"Ecuador", Country.Ecuador},
{"Egypt", Country.Egypt},
{"El Salvador", Country.El_Salvador},
{"Estonia", Country.Estonia},
{"Eswatini", Country.Eswatini},
{"Ethiopia", Country.Ethiopia},
{"Faroe Islands", Country.Faroe_Islands},
{"Finland", Country.Finland},
{"France", Country.France},
{"French Guiana", Country.French_Guiana},
{"French Polynesia", Country.French_Polynesia},
{"Gabon", Country.Gabon},
{"Georgia", Country.Georgia},
{"Germany", Country.Germany},
{"Ghana", Country.Ghana},
{"Gibraltar", Country.Gibraltar},
{"Greece", Country.Greece},
{"Greenland", Country.Greenland},
{"Grenada", Country.Grenada},
{"Guadeloupe", Country.Guadeloupe},
{"Guam", Country.Guam},
{"Guatemala", Country.Guatemala},
{"Guernsey", Country.Guernsey},
{"Guyana", Country.Guyana},
{"Haiti", Country.Haiti},
{"Honduras", Country.Honduras},
{"Hong Kong", Country.Hong_Kong},
{"Hungary", Country.Hungary},
{"Iceland", Country.Iceland},
{"India", Country.India},
{"Indonesia", Country.Indonesia},
{"Iran", Country.Iran},
{"Iraq", Country.Iraq},
{"Ireland", Country.Ireland},
{"Isle of Man", Country.Isle_of_Man},
{"Israel", Country.Israel},
{"Italy", Country.Italy},
{"Jamaica", Country.Jamaica},
{"Japan", Country.Japan},
{"Jersey", Country.Jersey},
{"Jordan", Country.Jordan},
{"Kazakhstan", Country.Kazakhstan},
{"Kenya", Country.Kenya},
{"Korea, Republic of", Country.Korea},
{"Kuwait", Country.Kuwait},
{"Kyrgyzstan", Country.Kyrgyzstan},
{"Lao People's Democratic Republic", Country.Laos},
{"Latvia", Country.Latvia},
{"Lebanon", Country.Lebanon},
{"Libya", Country.Libya},
{"Liechtenstein", Country.Liechtenstein},
{"Lithuania", Country.Lithuania},
{"Luxembourg", Country.Luxembourg},
{"Macau", Country.Macau},
{"Macedonia, the former Yugoslav Republic of", Country.Macedonia},
{"Madagascar", Country.Madagascar},
{"Malaysia", Country.Malaysia},
{"Maldives", Country.Maldives},
{"Mali", Country.Mali},
{"Malta", Country.Malta},
{"Martinique", Country.Martinique},
{"Mauritius", Country.Mauritius},
{"Mayotte", Country.Mayotte},
{"Mexico", Country.Mexico},
{"Moldova, Republic of", Country.Moldova},
{"Monaco", Country.Monaco},
{"Mongolia", Country.Mongolia},
{"Montenegro", Country.Montenegro},
{"Morocco", Country.Morocco},
{"Mozambique", Country.Mozambique},
{"Myanmar", Country.Myanmar},
{"Namibia", Country.Namibia},
{"Nepal", Country.Nepal},
{"Netherlands", Country.Netherlands},
{"New Zealand", Country.New_Zealand},
{"Nicaragua", Country.Nicaragua},
{"Nigeria", Country.Nigeria},
{"Northern Mariana Islands", Country.Northern_Mariana_Islands},
{"Norway", Country.Norway},
{"Oman", Country.Oman},
{"Pakistan", Country.Pakistan},
{"Palestinian Territory, Occupied", Country.Palestinian_Territory},
{"Panama", Country.Panama},
{"Papua New Guinea", Country.Papua_New_Guinea},
{"Paraguay", Country.Paraguay},
{"Peru", Country.Peru},
{"Philippines", Country.Philippines},
{"Poland", Country.Poland},
{"Portugal", Country.Portugal},
{"Puerto Rico", Country.Puerto_Rico},
{"Qatar", Country.Qatar},
{"Reunion", Country.Reunion},
{"Romania", Country.Romania},
{"Russian Federation", Country.Russian_Federation},
{"Rwanda", Country.Rwanda},
{"Saint Kitts and Nevis", Country.Saint_Kitts_and_Nevis},
{"Saint Lucia", Country.Saint_Lucia},
{"Saint Vincent and the Grenadines", Country.Saint_Vincent_and_the_Grenadines},
{"Saudi Arabia", Country.Saudi_Arabia},
{"Senegal", Country.Senegal},
{"Serbia", Country.Serbia},
{"Seychelles", Country.Seychelles},
{"Singapore", Country.Singapore},
{"Slovakia", Country.Slovakia},
{"Slovenia", Country.Slovenia},
{"South Africa", Country.South_Africa},
{"Spain", Country.Spain},
{"Sri Lanka", Country.Sri_Lanka},
{"Sudan", Country.Sudan},
{"Suriname", Country.Suriname},
{"Sweden", Country.Sweden},
{"Switzerland", Country.Switzerland},
{"Taiwan", Country.Taiwan},
{"Tajikistan", Country.Tajikistan},
{"Tanzania, United Republic of", Country.Tanzania},
{"Thailand", Country.Thailand},
{"Togo", Country.Togo},
{"Trinidad and Tobago", Country.Trinidad_and_Tobago},
{"Tunisia", Country.Tunisia},
{"Turkey", Country.Turkey},
{"Turkmenistan", Country.Turkmenistan},
{"Turks and Caicos Islands", Country.Turks_and_Caicos_Islands},
{"Uganda", Country.Uganda},
{"Ukraine", Country.Ukraine},
{"United Arab Emirates", Country.United_Arab_Emirates},
{"United Kingdom", Country.United_Kingdom},
{"United States", Country.United_States},
{"Uruguay", Country.Uruguay},
{"Uzbekistan", Country.Uzbekistan},
{"Venezuela", Country.Venezuela},
{"Viet Nam", Country.Viet_Nam},
{"Virgin Islands, U.S.", Country.Virgin_Islands},
{"Yemen", Country.Yemen},
{"Zambia", Country.Zambia},
{"Zimbabwe", Country.Zimbabwe}
};
}
-103
View File
@@ -1,103 +0,0 @@
// ReSharper disable InconsistentNaming
namespace SteamLib.Core.Enums;
public enum Currency
{
None = 0,
USD = 1,
GBP = 2,
EUR = 3,
CHF = 4,
RUB = 5,
PLN = 6,
BRL = 7,
JPY = 8,
NOK = 9,
IDR = 10,
MYR = 11,
PHP = 12,
SGD = 13,
THB = 14,
VND = 15,
KRW = 16,
TRY = 17,
UAH = 18,
MXN = 19,
CAD = 20,
AUD = 21,
NZD = 22,
CNY = 23,
INR = 24,
CLP = 25,
PEN = 26,
COP = 27,
ZAR = 28,
HKD = 29,
TWD = 30,
SAR = 31,
AED = 32,
ARS = 34,
ILS = 35,
KZT = 37,
KWD = 38,
QAR = 39,
CRC = 40,
UYU = 41
}
public static class CurrencyInfo
{
public static IReadOnlyDictionary<Currency, string> CurrencySymbols { get; } = new Dictionary<Currency, string>
{
{Currency.USD, "$"},
{Currency.GBP, "£"},
{Currency.EUR, "€"},
{Currency.CHF, "CHF"},
{Currency.RUB, "pуб."},
{Currency.PLN, "zł"},
{Currency.BRL, "R$"},
{Currency.JPY, "¥"},
{Currency.NOK, "kr"},
{Currency.IDR, "Rp"},
{Currency.MYR, "RM"},
{Currency.PHP, "P"},
{Currency.SGD, "S$"},
{Currency.THB, "฿"},
{Currency.VND, "₫"},
{Currency.KRW, "₩"},
{Currency.TRY, "TL"},
{Currency.UAH, "₴"},
{Currency.MXN, "Mex$"},
{Currency.CAD, "CDN$"},
{Currency.AUD, "A$"},
{Currency.NZD, "NZ$"},
{Currency.CNY, "¥"},
{Currency.INR, "₹"},
{Currency.CLP, "CLP$"},
{Currency.PEN, "S/."},
{Currency.COP, "COL$"},
{Currency.ZAR, "R"},
{Currency.HKD, "HK$"},
{Currency.TWD, "NT$"},
{Currency.SAR, "SR"},
{Currency.AED, "AED"},
{Currency.ARS, "ARS$"},
{Currency.ILS, "₪"},
{Currency.KZT, "₸"},
{Currency.KWD, "KD"},
{Currency.QAR, "QR"},
{Currency.CRC, "₡"},
{Currency.UYU, "$U"}
};
public static int ToInt(this Currency currency)
{
return (int) currency;
}
public static string ToIntString(this Currency currency)
{
return currency.ToInt().ToString();
}
}
-11
View File
@@ -1,11 +0,0 @@
namespace SteamLib.Core.Enums;
public enum SteamDomain
{
Undefined = 0,
Community = 1,
Store = 2,
Help = 3,
TV = 4,
Checkout = 5
}
@@ -1,6 +0,0 @@
namespace SteamLib.Core.Interfaces;
public interface ICaptchaResolver
{
public Task<string> Resolve(Uri imageUrl, HttpClient client);
}
@@ -1,15 +0,0 @@
namespace SteamLib.Core.Interfaces;
public enum EmailConfirmationType
{
AttachPhoneAuthenticator
}
public interface IEmailProvider
{
public int MaxRetryCount { get; }
public Task<string> GetEmailAuthCode(ILoginConsumer caller);
public Task<string> GetAddAuthenticatorCode(ILoginConsumer caller);
public Task ConfirmEmailLink(ILoginConsumer caller, EmailConfirmationType confirmationType);
}
@@ -1,6 +0,0 @@
namespace SteamLib.Core.Interfaces;
public interface ILoginConsumer
{
public string FriendlyName { get; }
}
@@ -1,7 +0,0 @@
namespace SteamLib.Core.Interfaces;
public interface ISteamGuardProvider
{
public int MaxRetryCount { get; }
public ValueTask<string> GetSteamGuardCode(ILoginConsumer caller);
}
@@ -1,174 +0,0 @@
using System.Collections.ObjectModel;
using AchiesUtilities.Models;
using SteamLib.Exceptions;
using SteamLib.Utility;
namespace SteamLib.Core.StatusCodes;
public partial class SteamStatusCode : Enumeration
{
public static IReadOnlyDictionary<int, SteamStatusCode> StatusCodes { get; }
static SteamStatusCode()
{
StatusCodes =
new ReadOnlyDictionary<int, SteamStatusCode>(GetAll<SteamStatusCode>()
.ToDictionary(ssc => ssc.Id, ssc => ssc));
}
protected SteamStatusCode(int id, string name) : base(id, name)
{
}
/// <summary>
/// Tries to translate status code. If no status code was found <see cref="SteamStatusCodeException" /> with
/// <see cref="Undefined" /> will be thrown
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="response"></param>
/// <param name="isUniversal"></param>
/// <returns>
/// <see cref="SteamStatusCode" /> if value is universal. <typeparamref name="T" /> if value is
/// <typeparamref name="T" /> specific
/// </returns>
/// <exception cref="SteamStatusCodeException"></exception>
public static SteamStatusCode Translate<T>(string response, out bool isUniversal) where T : SteamStatusCode
{
int statusCode;
try
{
statusCode = Utilities.GetSuccessCode(response);
}
catch
{
throw new SteamStatusCodeException(Undefined, response);
}
return Translate<T>(statusCode, out isUniversal);
}
/// <summary>
/// Translate <paramref name="statusCode" /> to specific <see cref="T" /> or <see cref="SteamStatusCode" />
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="statusCode"></param>
/// <param name="isUniversal"></param>
/// <returns>
/// <see cref="SteamStatusCode" /> if value is universal. <typeparamref name="T" /> if value is
/// <typeparamref name="T" /> specific
/// </returns>
/// <exception cref="SteamStatusCodeException"></exception>
public static SteamStatusCode Translate<T>(int statusCode, out bool isUniversal) where T : SteamStatusCode
{
if (statusCode < 2)
{
var status = StatusCodes[statusCode];
isUniversal = true;
return status;
}
var allSpecific = GetAll<T>();
var translated = allSpecific.FirstOrDefault(s => s.Id == statusCode);
isUniversal = translated == null;
if (translated != null)
{
return translated;
}
if (translated == null && StatusCodes.TryGetValue(statusCode, out var universal))
{
return universal;
}
return translated ?? new SteamStatusCode(statusCode, nameof(Unknown));
}
public override bool Equals(object? obj)
{
if (obj is not SteamStatusCode translated)
return false;
if (Id < 2)
{
return translated.Id == Id;
}
return base.Equals(translated);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
/// <summary>
/// </summary>
/// <param name="response"></param>
/// <exception cref="SteamStatusCodeException"></exception>
public static void ValidateSuccessOrThrow<T>(string response) where T : SteamStatusCode
{
var translated = Translate<T>(response, out _);
if (translated.Id != 1)
throw new SteamStatusCodeException(translated, response);
}
/// <summary>
/// Same as <see cref="ValidateSuccessOrThrow{T}(string)" /> with <see cref="SteamStatusCode" /> generic parameter
/// <br />
/// <b>(Used in case when steam status code is not defined for this operation)</b>
/// </summary>
/// <param name="response"></param>
/// <exception cref="SteamStatusCodeException"></exception>
public static void ValidateSuccessOrThrow(string response)
{
ValidateSuccessOrThrow<SteamStatusCode>(response);
}
/// <summary>
/// </summary>
/// <param name="statusCode"></param>
/// <exception cref="SteamStatusCodeException"></exception>
public static void ValidateSuccessOrThrow<T>(int statusCode) where T : SteamStatusCode
{
if (statusCode == 1) return;
var translated = Translate<T>(statusCode, out _);
throw new SteamStatusCodeException(translated, statusCode.ToString());
}
/// <summary>
/// Same as <see cref="ValidateSuccessOrThrow{T}(int)" /> with <see cref="SteamStatusCode" /> generic parameter <br />
/// <b>(Used in case when steam status code is not defined for this operation)</b>
/// </summary>
/// <param name="statusCode"></param>
/// <exception cref="SteamStatusCodeException"></exception>
public static void ValidateSuccessOrThrow(int statusCode)
{
ValidateSuccessOrThrow<SteamStatusCode>(statusCode);
}
/// <summary>
/// Validates that status code is <see cref="Ok" /> or <typeparamref name="T" /> specific
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="response"></param>
/// <param name="isUniversal"></param>
/// <exception cref="SteamStatusCodeException"></exception>
public static SteamStatusCode TranslateOrThrow<T>(string response, out bool isUniversal) where T : SteamStatusCode
{
var translated = Translate<T>(response, out isUniversal);
if (translated.Id < 1)
throw new SteamStatusCodeException(translated, response);
return translated;
}
public override string ToString()
{
return $"{Name} ({Id})";
}
}
@@ -1,51 +0,0 @@
using SteamLib.Core.Interfaces;
namespace SteamLib.Exceptions;
public class LoginException : Exception
{
public LoginError Error { get; }
public string? Response { get; }
public LoginException(LoginError error)
: base($"Login was unsuccessful. Error {error}")
{
Error = error;
}
public LoginException(string response)
{
Error = LoginError.UndefinedError;
Response = response;
}
public LoginException(string? response, string? message, LoginError? error, Exception? innerException) : base(
message, innerException)
{
Response = response;
Error = error ?? LoginError.UndefinedError;
}
}
public enum LoginError
{
CaptchaRequired,
InvalidCredentials,
InvalidEmailAuthCode,
InvalidTwoFactorCode,
/// <summary>
/// SteamEmail authentication is required to login but no <see cref="IEmailProvider" /> was provided
/// </summary>
EmailAuthRequired,
/// <summary>
/// SteamGuard is required to login but no <see cref="ISteamGuardProvider" /> was provided
/// </summary>
SteamGuardRequired,
/// <summary>
/// Some error occurred while trying to login.
/// </summary>
UndefinedError
}
@@ -1,24 +0,0 @@
using SteamLib.Core.StatusCodes;
namespace SteamLib.Exceptions;
public class SteamStatusCodeException : Exception
{
public SteamStatusCode StatusCode { get; }
public string? Response { get; }
public SteamStatusCodeException(SteamStatusCode statusCode, string? response, Exception? innerException = null)
: base($"Steam return not successful status code {statusCode}", innerException)
{
StatusCode = statusCode;
Response = response;
}
public SteamStatusCodeException(string message, SteamStatusCode statusCode, string? response = null,
Exception? innerException = null)
: base(message, innerException)
{
StatusCode = statusCode;
Response = response;
}
}
@@ -1,28 +0,0 @@
using SteamLib.ProtoCore.Enums;
namespace SteamLib.ProtoCore.Exceptions;
[Serializable]
public class EResultException : Exception
{
public EResult Result { get; }
public EResultException()
{
}
public EResultException(EResult result) : base("EResult error: " + result)
{
Result = result;
}
public EResultException(string message, EResult result, Exception inner) : base(message, inner)
{
Result = result;
}
public EResultException(EResult result, Exception inner) : base("EResult error: " + result, inner)
{
Result = result;
}
}
@@ -1,16 +0,0 @@
namespace SteamLib.ProtoCore.Exceptions;
[Serializable]
public class UnknownEResultException : Exception
{
public int EResult { get; }
public UnknownEResultException()
{
}
public UnknownEResultException(int eResult) : base("Got unknown EResult: " + eResult)
{
EResult = eResult;
}
}
-21
View File
@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AchiesUtilities.Newtonsoft.JSON" Version="1.2.11" />
<PackageReference Include="AchiesUtilities.Web" Version="1.0.14" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="protobuf-net.Core" Version="3.2.30" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />
</ItemGroup>
</Project>
@@ -1,110 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace SteamLib.Utility.MafileSerialization;
public partial class MafileSerializer //Write
{
private const string CREDITS_PROPERTY_NAME = "Credits";
public static string Serialize(MobileDataExtended mobileData, Formatting formatting = Formatting.Indented,
bool sign = true, MafileCredits? credits = null)
{
using var w = new StringWriter();
using var write = new JsonTextWriter(w);
write.Formatting = formatting;
var j = JObject.FromObject(mobileData);
j.Add(SIGNATURE_PROPERTY_NAME, MAFILE_VERSION);
if (sign)
{
credits ??= MafileCredits.Instance;
var obj = JObject.FromObject(credits);
j.Add(CREDITS_PROPERTY_NAME, obj);
}
j.WriteTo(write);
return w.ToString();
}
public static async Task<string> SerializeAsync(MobileDataExtended mobileData,
Formatting formatting = Formatting.Indented, bool sign = true, MafileCredits? credits = null)
{
await using var w = new StringWriter();
await using var write = new JsonTextWriter(w);
write.Formatting = formatting;
var j = JObject.FromObject(mobileData);
j.Add(SIGNATURE_PROPERTY_NAME, j);
if (sign)
{
credits ??= MafileCredits.Instance;
var obj = JObject.FromObject(credits);
j.Add(CREDITS_PROPERTY_NAME, obj);
}
await j.WriteToAsync(write);
return w.ToString();
}
public static string SerializeLegacy(MobileData mobileData, Formatting formatting,
Dictionary<string, object?>? additionalProperties = null, bool sign = true, MafileCredits? credits = null)
{
var result = new LegacyMafile
{
SharedSecret = mobileData.SharedSecret,
IdentitySecret = mobileData.IdentitySecret,
DeviceId = mobileData.DeviceId
};
if (mobileData is MobileDataExtended ext)
{
result.RevocationCode = ext.RevocationCode ?? string.Empty;
result.AccountName = ext.AccountName;
result.SessionData = ext.SessionData == null
? null
: new
{
AccessToken = ext.SessionData?.MobileToken?.Token,
steamLoginSecure = ext.SessionData?.MobileToken?.SignedToken,
RefreshToken = ext.SessionData?.RefreshToken.Token,
SteamID = ext.SessionData?.SteamId.Steam64.Id,
SessionID = ext.SessionData?.SessionId
};
result.ServerTime = ext.ServerTime;
result.SerialNumber = ext.SerialNumber.ToString();
result.Uri = ext.Uri;
result.TokenGid = ext.TokenGid;
result.Secret1 = ext.Secret1;
result.SteamId = ext.SteamId.Steam64.Id;
}
using var w = new StringWriter();
using var write = new JsonTextWriter(w);
write.Formatting = formatting;
var j = JObject.FromObject(result);
if (additionalProperties != null)
{
foreach (var (name, value) in additionalProperties)
{
JToken? jToken = null;
if (value != null)
{
jToken = JToken.FromObject(value);
}
j.Add(name, jToken);
}
}
if (sign)
{
credits ??= MafileCredits.Instance;
var obj = JObject.FromObject(credits);
j.Add(CREDITS_PROPERTY_NAME, obj);
}
j.WriteTo(write);
return w.ToString();
}
}
@@ -1,17 +0,0 @@
using Newtonsoft.Json;
namespace SteamLib.Web.Converters;
public class StringToLongIfNeededConverter : JsonConverter<string>
{
public override void WriteJson(JsonWriter writer, string? value, JsonSerializer serializer)
{
writer.WriteValue(value);
}
public override string ReadJson(JsonReader reader, Type objectType, string? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return reader.Value?.ToString() ?? "null";
}
}
@@ -1,25 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
#pragma warning disable CS8618
namespace SteamLib.Web.Models.GlobalMarketInfo;
[PublicAPI]
public class GlobalInfoModel
{
[MemberNotNullWhen(true, nameof(SteamId))]
public bool IsLoggedIn { get; set; }
/// <summary>
/// <see langword="null" /> if <see cref="IsLoggedIn" /> is <see langword="null" /> or wallet doesn't exist
/// </summary>
public MarketWalletSchema? WalletInfo { get; set; }
public bool RequiresBillingInfo { get; set; }
public bool HasBillingStates { get; set; }
public string CountryCode { get; set; }
public string Language { get; set; }
public long? SteamId { get; set; }
public string SessionId { get; set; }
}
@@ -1,56 +0,0 @@
using AchiesUtilities.Newtonsoft.JSON.Converters.Common;
using Newtonsoft.Json;
using SteamLib.Core.Enums;
#pragma warning disable CS8618
namespace SteamLib.Web.Models.GlobalMarketInfo;
public class MarketWalletSchema
{
[JsonProperty("wallet_currency")] public Currency WalletCurrency { get; set; }
[JsonProperty("wallet_country")] public string WalletCountry { get; set; }
[JsonProperty("wallet_state")] public string WalletState { get; set; }
[JsonProperty("wallet_fee")]
[JsonConverter(typeof(IntToStringConverter))]
public int WalletFee { get; set; }
[JsonProperty("wallet_fee_minimum")]
[JsonConverter(typeof(IntToStringConverter))]
public int WalletFeeMinimum { get; set; }
[JsonProperty("wallet_fee_percent")]
[JsonConverter(typeof(DecimalToStringConverter))]
public decimal WalletFeePercent { get; set; }
[JsonProperty("wallet_publisher_fee_percent_default")]
[JsonConverter(typeof(DecimalToStringConverter))]
public decimal WalletPublisherFeePercentDefault { get; set; }
[JsonProperty("wallet_fee_base")]
[JsonConverter(typeof(IntToStringConverter))]
public int WalletFeeBase { get; set; }
[JsonProperty("wallet_balance")]
[JsonConverter(typeof(LongToStringConverter))]
public long WalletBalance { get; set; }
[JsonProperty("wallet_delayed_balance")]
[JsonConverter(typeof(LongToStringConverter))]
public long WalletDelayedBalance { get; set; }
[JsonProperty("wallet_max_balance")]
[JsonConverter(typeof(LongToStringConverter))]
public long WalletMaxBalance { get; set; }
[JsonProperty("wallet_trade_max_balance")]
[JsonConverter(typeof(LongToStringConverter))]
public long WalletTradeMaxBalance { get; set; }
[JsonProperty("success")] public int Success { get; set; }
[JsonProperty("rwgrsn")] public int Rwgrsn { get; set; }
}
@@ -1,81 +0,0 @@
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using Newtonsoft.Json;
using SteamLib.Web.Models.GlobalMarketInfo;
namespace SteamLib.Web.Scrappers.HTML;
public static class MarketGlobalInfoScrapper
{
private static readonly Regex LoggedInRegex = new("var g_bLoggedIn = (.+);", RegexOptions.Compiled);
private static readonly Regex ReqBillingInfoRegex =
new("var g_bRequiresBillingInfo = (.+);", RegexOptions.Compiled);
private static readonly Regex CountryCodeRegex = new("var g_strCountryCode = \"(.+)\";", RegexOptions.Compiled);
private static readonly Regex HasBillingStates = new("var g_bHasBillingStates = (.+);", RegexOptions.Compiled);
private static readonly Regex LanguageRegex = new("var g_strLanguage = \"(.+)\";", RegexOptions.Compiled);
private static readonly Regex WalletInfoRegex = new("var g_rgWalletInfo = (.+);", RegexOptions.Compiled);
private static readonly Regex SessionIdRegex = new("g_sessionID = \"(.+)\";", RegexOptions.Compiled);
private static readonly Regex SteamIdRegex = new("g_steamID = \"(.+)\";", RegexOptions.Compiled);
public static GlobalInfoModel Scrap(string html)
{
var document = new HtmlDocument();
document.LoadHtml(html);
var scriptNodes = document.DocumentNode.SelectNodes("//*[@id=\"responsive_page_template_content\"]/script");
var index = scriptNodes.Count > 2
? 1
: 0; //If account is limited or market unavailable elements will be displaced
var scriptNode = scriptNodes[index];
var script = scriptNode.InnerText!;
var logged = GetBool(script, LoggedInRegex);
var hasBillingStates = GetBool(script, HasBillingStates);
var reqBillingInfo = GetBool(script, ReqBillingInfoRegex);
var country = CountryCodeRegex.Match(html).Groups[1].Value;
var language = LanguageRegex.Match(html).Groups[1].Value;
var walletInfoStr = WalletInfoRegex.Match(html).Groups[1].Value;
MarketWalletSchema? wallet = null;
var sessionScriptNode =
document.DocumentNode.SelectSingleNode("//div[@class='responsive_page_content']/script");
var sessionScript = sessionScriptNode.InnerText!;
var sessionId = SessionIdRegex.Match(sessionScript).Groups[1].Value;
long? steamId = null;
if (logged)
{
wallet = JsonConvert.DeserializeObject<MarketWalletSchema>(walletInfoStr);
var steamIdStr = SteamIdRegex.Match(sessionScript).Groups[1].Value;
steamId = long.Parse(new string(steamIdStr.Where(char.IsDigit).ToArray()));
}
return new GlobalInfoModel
{
CountryCode = country,
HasBillingStates = hasBillingStates,
IsLoggedIn = logged,
Language = language,
RequiresBillingInfo = reqBillingInfo,
WalletInfo = wallet,
SessionId = sessionId,
SteamId = steamId
};
}
private static bool GetBool(string script, Regex regex)
{
var value = regex.Match(script).Groups[1].Value;
return bool.Parse(value);
}
}
@@ -1,60 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using SteamLib.Core;
using SteamLib.Exceptions.General;
using SteamLib.Web.Models.GlobalMarketInfo;
using SteamLib.Web.Scrappers.HTML;
using System.Threading;
using JetBrains.Annotations;
using HtmlAgilityPack;
using SteamLib.Core.Models;
namespace SteamLib.Web;
public static class SteamWebApi
{
/// <summary>
/// Login is not required
/// </summary>
/// <param name="client"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<GlobalInfoModel> GetMarketGlobalInfo(HttpClient client,
CancellationToken cancellationToken = default)
{
var resp = await client.GetStringAsync(SteamConstants.STEAM_MARKET, cancellationToken);
try
{
return MarketGlobalInfoScrapper.Scrap(resp);
}
catch (Exception ex)
{
throw new UnsupportedResponseException(resp, ex);
}
}
[RegexPattern]
[SuppressMessage("ReSharper", "UseRawString")]
[SuppressMessage("ReSharper", "StringLiteralTypo")]
private static readonly string _regexTip =
@"g_sessionID = ""(?<g_sessionID>.*)"";"
+ @"\s*g_steamID = (?<g_steamID>.*);";
private static readonly Regex Regex = new(_regexTip, RegexOptions.Compiled);
private const string XPATH = "//div[@class='responsive_page_content']/script";
public static async Task<string> GetLoginSessionId(HttpClient client, CancellationToken cancellationToken = default)
{
var resp = await client.GetStringAsync("https://steamcommunity.com/login/home", cancellationToken);
var document = new HtmlDocument();
document.LoadHtml(resp);
var scriptNode = document.DocumentNode.SelectSingleNode(XPATH) ?? throw new NullReferenceException("Script Node was null");
var script = scriptNode.InnerText;
var match = Regex.Match(script);
if (!match.Success)
throw new UnsupportedResponseException(script, "Page contains script but regex was unsuccessful");
return match.Groups["g_sessionID"].Value;
}
}
+89
View File
@@ -0,0 +1,89 @@
<!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.7.1</div>
<div class="date">09.07.2025</div>
<div class="description">
<ul>
<li>
<b>NEWS:</b> Official Telegram group now available! Join us at
<b>
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b>
</li>
<li><b>UPDATE:</b> Added support for a new confirmation type — Purchase.</li>
<li><b>UPDATE:</b> Merged Global 1.7.0 updates into the release version (<a href="https://t.me/nebulaauth/32">patch notes</a>).</li>
<li><b>FIX:</b> Fixed a crash occurring during confirmation loading.</li>
<li><b>ENHANCEMENT:</b> Improved localization and system messages.</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.7.2</div>
<div class="date">10.07.2025</div>
<div class="description">
<ul>
<li>
<b>NEWS:</b> Official Telegram group now available! Join us at
<b>
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b>
</li>
<li><b>FIX:</b> Crash on loading trade confirmations</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+87
View File
@@ -0,0 +1,87 @@
<!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.7.3</div>
<div class="date">17.07.2025</div>
<div class="description">
<ul>
<li>
<b>NEWS:</b> Official Telegram group now available! Join us at
<b>
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b>
</li>
<li><b>UPDATE:</b> Added automatic acknowledgment request for sending trade confirmation</li>
<li><b>FIX:</b> Fixed notifications text in auto-confirm timer</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.7.4</div>
<div class="date">19.07.2025</div>
<div class="description">
<ul>
<li>
<b>NEWS:</b> Official Telegram group now available! Join us at
<b>
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b>
</li>
<li><b>FIX:</b> Resolved issue where a "Confirmation Error" notification appeared despite the confirmation being successful.</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.8.0</div>
<div class="date">07.11.2025</div>
<div class="description">
<ul>
<li> Major update: Full changelog is available here <a href="https://teletype.in/@achies_raw/nebula-1-8-0-eng">ENG</a> | <a href="https://teletype.in/@achies_raw/nebula-1-8-0-rus">RUS</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
+87
View File
@@ -0,0 +1,87 @@
<!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">
<div class="change">
<div class="version">Version 1.8.1</div>
<div class="date">25.01.2026</div>
<div class="description">
<ul>
<li><b>NEW:</b> Added Mafile Export tool with templates, batch export and flexible field filtering.</li>
<li><b>IMPROVEMENT:</b> Reworked auto-confirmation error handling logic (MAAC) — fewer false disables, smarter retries.</li>
<li><b>UPDATE:</b> Added “Unattach Proxy” option to the account context menu.</li>
<li><b>LOCALIZATION:</b> Added Chinese (Simplified) and French languages.</li>
<li><b>FIX:</b> Fixed crashes related to duplicate mafiles, timers, search filtering and import logic.</li>
<li>
<b>DETAILS:</b> Full patch notes:
<a href="https://teletype.in/@achies_raw/nebula-1-8-1-eng">ENG</a> |
<a href="https://teletype.in/@achies_raw/nebula-1-8-1-rus">RUS</a>
</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>1.5.6.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.6/NebulaAuth.1.5.6.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.6.html</changelog>
<mandatory>false</mandatory>
</item>
+43
View File
@@ -0,0 +1,43 @@
<Application x:Class="NebulaAuth.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Converters/Converters.xaml" />
<ResourceDictionary Source="Theme/Themes/DefaultTheme.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
<ResourceDictionary Source="Theme/Brushes.xaml" />
<!--Controls-->
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.snackbar.xaml" />
<ResourceDictionary Source="View/ConfirmationTemplates.xaml" />
<ResourceDictionary Source="View/LinkerStepTemplates.xaml" />
<ResourceDictionary Source="View/MafileMoverStepTemplates.xaml" />
<!--Theme-->
<ResourceDictionary Source="Theme/WindowStyle/WindowStyle.xaml" />
<ResourceDictionary Source="Theme/MaterialDesignThemes.Overrides.xaml" />
<ResourceDictionary>
<Style TargetType="ToolTip" BasedOn="{StaticResource {x:Type ToolTip}}">
<Setter Property="Foreground" Value="{DynamicResource Base100Brush}" />
<Setter Property="Background" Value="{DynamicResource BaseContentBrush}" />
</Style>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
@@ -8,15 +8,19 @@ namespace NebulaAuth;
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
protected override async void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
LocManager.Init();
LocManager.SetApplicationLocalization(Settings.Instance.Language);
try
{
Shell.Initialize();
var splashScreen = new SplashScreen("Theme\\SplashScreen.png");
splashScreen.Show(false, true);
base.OnStartup(e);
await Shell.Initialize();
var mainWindow = new MainWindow();
Current.MainWindow = mainWindow;
mainWindow.Show();
splashScreen.Close(TimeSpan.Zero);
}
catch (Exception ex)
{
@@ -28,7 +32,9 @@ public partial class App
MessageBox.Show(msg, "Error", MessageBoxButton.OK, MessageBoxImage.Stop, MessageBoxResult.OK,
MessageBoxOptions.DefaultDesktopOnly);
throw;
Shell.Logger.Fatal(ex, "Application startup failed");
Current.Shutdown(1);
}
}
}
@@ -0,0 +1,58 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace NebulaAuth;
public class CodeProgressBar : ProgressBar
{
public static readonly DependencyProperty TimeRemainingProperty =
DependencyProperty.Register(nameof(TimeRemaining), typeof(double), typeof(CodeProgressBar),
new PropertyMetadata(-1.0, OnTimeRemainingChanged));
public static readonly DependencyProperty MaxTimeProperty =
DependencyProperty.Register(nameof(MaxTime), typeof(double), typeof(CodeProgressBar),
new PropertyMetadata(30.0));
public double TimeRemaining
{
get => (double) GetValue(TimeRemainingProperty);
set => SetValue(TimeRemainingProperty, value);
}
public double MaxTime
{
get => (double) GetValue(MaxTimeProperty);
set => SetValue(MaxTimeProperty, value);
}
private static void OnTimeRemainingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is CodeProgressBar progressBar)
{
var newValue = (double) e.NewValue;
progressBar.StartProgressAnimation(newValue);
}
}
private void StartProgressAnimation(double timeRemaining)
{
if (timeRemaining <= 0 || MaxTime <= 0) return;
var progress = (1 - timeRemaining / MaxTime) * 100;
Value = 0;
Value = 100;
var animation = new DoubleAnimation
{
From = progress,
To = 100,
Duration = TimeSpan.FromSeconds(timeRemaining),
AccelerationRatio = 0,
DecelerationRatio = 0
};
BeginAnimation(ValueProperty, animation);
}
}
+52
View File
@@ -0,0 +1,52 @@
<UserControl x:Class="NebulaAuth.HintBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance nebulaAuth:HintBox}"
d:DesignHeight="100" d:DesignWidth="400">
<Border Padding="5"
BorderThickness="1"
CornerRadius="12"
Visibility="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource NullableToVisibilityConverter}}">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BaseShadowColor}" Opacity="0.5" />
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.2" />
</Border.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Height="22" Width="22"
VerticalAlignment="Center"
Margin="5,0,0,0"
Foreground="{Binding IconBrush, RelativeSource={RelativeSource AncestorType=UserControl}}"
Kind="{Binding IconKind, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<TextBlock Grid.Column="1"
FontSize="{Binding FontSize, RelativeSource={RelativeSource AncestorType=UserControl}}"
TextWrapping="WrapWithOverflow"
VerticalAlignment="Center"
Margin="10,10,10,10"
Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<Button Grid.Column="2"
Visibility="{Binding ShowCloseButton, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource BooleanToVisibilityConverter}}"
Command="{Binding CloseCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
Style="{StaticResource MaterialDesignToolButton}"
Margin="0,0,5,0"
Padding="2">
<materialDesign:PackIcon Kind="Close" Width="18" Height="18" />
</Button>
</Grid>
</Border>
</UserControl>
+98
View File
@@ -0,0 +1,98 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using CommunityToolkit.Mvvm.Input;
using MaterialDesignThemes.Wpf;
namespace NebulaAuth;
public partial class HintBox : UserControl
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(HintBox));
public static readonly DependencyProperty SeverityProperty =
DependencyProperty.Register(nameof(Severity), typeof(HintBoxSeverity), typeof(HintBox),
new PropertyMetadata(HintBoxSeverity.Info, OnSeverityChanged));
public static readonly DependencyProperty ShowCloseButtonProperty =
DependencyProperty.Register(nameof(ShowCloseButton), typeof(bool), typeof(HintBox),
new PropertyMetadata(false));
public static readonly DependencyProperty CloseCommandProperty =
DependencyProperty.Register(nameof(CloseCommand), typeof(ICommand), typeof(HintBox),
new PropertyMetadata(null));
public string Text
{
get => (string) GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public HintBoxSeverity Severity
{
get => (HintBoxSeverity) GetValue(SeverityProperty);
set => SetValue(SeverityProperty, value);
}
public ICommand CloseCommand
{
get => (ICommand) GetValue(CloseCommandProperty) ?? InternalCloseCommand;
set => SetValue(CloseCommandProperty, value);
}
public bool ShowCloseButton
{
get => (bool) GetValue(ShowCloseButtonProperty);
set => SetValue(ShowCloseButtonProperty, value);
}
private ICommand InternalCloseCommand { get; }
public PackIconKind IconKind { get; private set; }
public Brush IconBrush { get; private set; }
public HintBox()
{
InitializeComponent();
ApplySeverityVisuals();
InternalCloseCommand = new RelayCommand(Close);
}
public event RoutedEventHandler Closed;
private static void OnSeverityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((HintBox) d).ApplySeverityVisuals();
}
private void ApplySeverityVisuals()
{
switch (Severity)
{
case HintBoxSeverity.Error:
IconKind = PackIconKind.ErrorOutline;
IconBrush = (Brush) Application.Current.FindResource("ErrorBrush")!;
break;
default:
IconKind = PackIconKind.InfoCircleOutline;
IconBrush = (Brush) Application.Current.FindResource("InfoBrush")!;
break;
}
}
private void Close()
{
Visibility = Visibility.Collapsed;
Closed?.Invoke(this, new RoutedEventArgs());
}
}
public enum HintBoxSeverity
{
Info,
Error
}
@@ -11,10 +11,10 @@ public class AnyMafilesToVisibilityConverter : IValueConverter
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (_everAnyMafiles)
{
return Visibility.Collapsed;
}
//if (_everAnyMafiles)
//{
// return Visibility.Collapsed;
//}
if (value is 0)
{
@@ -18,7 +18,7 @@ public class BackgroundSourceConverter : IValueConverter
}
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg"));
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.png"));
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
@@ -0,0 +1,20 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class BoolToMafileNamingConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not bool boolValue)
return null;
return boolValue ? "Login" : "Steam ID";
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,28 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class BoolToValueConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not bool b) return Binding.DoNothing;
if (parameter is Array)
{
var arr = (Array) parameter;
if (arr.Length == 0) return Binding.DoNothing;
var first = arr.GetValue(0);
var second = arr.Length > 1 ? arr.GetValue(1) : null;
return b ? first : second;
}
return b ? parameter : null;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
+30
View File
@@ -0,0 +1,30 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:background="clr-namespace:NebulaAuth.Converters.Background">
<converters:CoefficientConverter x:Key="CoefficientConverter" />
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter" />
<converters:ProxyTextConverter x:Key="ProxyTextConverter" />
<converters:ProxyDataTextConverter x:Key="ProxyDataTextConverter" />
<converters:ProxyDataTextMultiConverter x:Key="ProxyDataTextMultiConverter" />
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter" />
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter" />
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter" />
<converters:BoolToMafileNamingConverter x:Key="BoolToMafileNamingConverter" />
<converters:BoolToValueConverter x:Key="BoolToValueConverter" />
<converters:NullableToBooleanConverter x:Key="NullableToBooleanConverter" />
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter" />
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<materialDesign:NullableToVisibilityConverter x:Key="NullableToVisibilityConverter" />
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
<system:Boolean x:Key="True">True</system:Boolean>
<system:Boolean x:Key="False">False</system:Boolean>
</ResourceDictionary>
@@ -0,0 +1,19 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class InverseBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value is bool b && !b ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return value is Visibility v && v != Visibility.Visible;
}
}
@@ -1,20 +1,14 @@
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace NebulaAuth.Converters;
public class PortableMaClientStatusToColorConverter : IValueConverter
public class NullableToBooleanConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is false)
{
return new SolidColorBrush(Color.FromRgb(187, 224, 139));
}
return new SolidColorBrush(Color.FromRgb(224, 139, 139));
return value != null;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
@@ -0,0 +1,83 @@
using System;
using System.Globalization;
using System.Text;
using System.Windows.Data;
using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.Converters;
public class ProxyTextConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not MaProxy p)
{
return string.Empty;
}
return $"{p.Id}: {p.Data.Address}:{p.Data.Port}";
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
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.Protocol.ToString().ToLowerInvariant()}://{p.Address}:{p.Port}";
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
public class ProxyDataTextMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var value = values[0] as ProxyData;
var displayProtocol = values.Length > 1 && values[1] is bool dp && dp;
var displayCredentials = values.Length > 2 && values[2] is bool dc && dc;
if (value == null)
{
return string.Empty;
}
var sb = new StringBuilder();
if (displayProtocol)
{
sb.Append(value.Protocol.ToString().ToLowerInvariant());
sb.Append("://");
}
sb.Append(value.Address);
sb.Append(':');
sb.Append(value.Port);
if (displayCredentials && value.AuthEnabled)
{
sb.Append(':');
sb.Append(value.Username);
sb.Append(':');
sb.Append(value.Password);
}
return sb.ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -5,31 +5,14 @@ using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.View;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel.Linker;
using NebulaAuth.ViewModel.MafileMover;
using NebulaAuth.ViewModel.Other;
namespace NebulaAuth.Core;
public static class DialogsController
{
#region CommonDialogs
public static async Task<bool> ShowConfirmCancelDialog(string? msg = null)
{
var content = msg == null ? new ConfirmCancelDialog() : new ConfirmCancelDialog(msg);
var result = await DialogHost.Show(content);
return result != null && (bool) result;
}
//public static async Task<string?> ShowTextFieldDialog(string? msg = null)
//{
// var content = msg == null ? new TextFieldDialog() : new TextFieldDialog(msg);
// var result = await DialogHost.Show(content);
// return result as string;
//}
#endregion
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null)
{
var vm = new LoginAgainVM
@@ -86,11 +69,76 @@ public static class DialogsController
public static async Task ShowLinkerDialog()
{
var vm = new LinkAccountVM();
var view = new LinkerView
LinkAccountVM? vm = null;
try
{
vm = new LinkAccountVM();
var view = new LinkerView
{
DataContext = vm
};
await DialogHost.Show(view);
}
finally
{
vm?.Dispose();
}
}
public static async Task ShowMafileMoverDialog()
{
MafileMoverVM? vm = null;
try
{
vm = new MafileMoverVM();
var view = new MafileMoverView
{
DataContext = vm
};
await DialogHost.Show(view);
}
finally
{
vm?.Dispose();
}
}
public static async Task ShowSetAccountsPasswordDialog()
{
var vm = new SetAccountPasswordsVM();
var dialog = new SetAccountPasswordsView
{
DataContext = vm
};
await DialogHost.Show(view);
await DialogHost.Show(dialog);
}
public static async Task ShowExportDialog()
{
var vm = new MafileExporterVM();
var dialog = new MafileExporterView
{
DataContext = vm
};
await DialogHost.Show(dialog);
}
#region CommonDialogs
public static async Task<bool> ShowConfirmCancelDialog(string? msg = null)
{
var content = msg == null ? new ConfirmCancelDialog() : new ConfirmCancelDialog(msg);
var result = await DialogHost.Show(content);
return result != null && (bool) result;
}
public static async Task<string?> ShowTextFieldDialog(string? title = null, string? msg = null)
{
var content = new TextFieldDialog(title, msg);
var result = await DialogHost.Show(content);
return result as string;
}
#endregion
}
@@ -31,6 +31,8 @@ public static class LocManager
LocalizationLanguage.English => "en",
LocalizationLanguage.Russian => "ru",
LocalizationLanguage.Ukrainian => "ua",
LocalizationLanguage.ChineseSimplified => "zh",
LocalizationLanguage.French => "fr",
_ => throw new ArgumentOutOfRangeException(nameof(language), language, null)
};
}
@@ -38,7 +40,7 @@ public static class LocManager
public static void Init()
{
Loc.LogOutMissingTranslations = true;
Loc.Instance.LogOutMissingTranslations = true;
LocalizationLoader.Instance.FileLanguageLoaders.Add(new JsonFileLoader());
@@ -107,5 +109,7 @@ public enum LocalizationLanguage
{
English,
Russian,
Ukrainian
Ukrainian,
ChineseSimplified,
French
}
@@ -44,7 +44,7 @@ public class SnackbarController
private static TimeSpan GetSnackbarTime(string str)
{
var duration = str.Length / 0.03;
var duration = str.Length / 0.022;
if (duration < MIN_SNACKBAR_TIME)
{
duration = MIN_SNACKBAR_TIME;
@@ -2,9 +2,9 @@
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shell;
using NebulaAuth.Model;
@@ -14,7 +14,6 @@ namespace NebulaAuth.Core;
public static class ThemeManager
{
public static System.Windows.Media.Color DefaultBackgroundColor = System.Windows.Media.Color.FromRgb(30, 32, 37);
private static readonly Window MainWindow = Application.Current.MainWindow!;
static ThemeManager()
@@ -24,11 +23,7 @@ public static class ThemeManager
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Settings.BackgroundColor))
{
UpdateBackground();
}
else if (e.PropertyName == nameof(Settings.IconColor))
if (e.PropertyName == nameof(Settings.IconColor))
{
UpdateIcon();
}
@@ -58,15 +53,40 @@ public static class ThemeManager
}
}
private static void UpdateBackground()
public static void ApplyTheme(string themeName)
{
var color = Settings.Instance.BackgroundColor ?? DefaultBackgroundColor;
Application.Current.Resources["WindowBackground"] = new SolidColorBrush(color);
var colorDict = new ResourceDictionary
{
Source = new Uri($"Theme/Themes/{themeName}.xaml", UriKind.Relative)
};
var brushDict = new ResourceDictionary
{
Source = new Uri("Theme/Brushes.xaml", UriKind.Relative)
};
var mergedDictionaries = Application.Current.Resources.MergedDictionaries;
var toRemove = mergedDictionaries
.Where(d => d.Source?.OriginalString.Contains("Theme/Themes/") == true ||
d.Source?.OriginalString.EndsWith("Brushes.xaml") == true)
.ToList();
foreach (var dict in toRemove)
{
mergedDictionaries.Remove(dict);
}
mergedDictionaries.Insert(0, colorDict);
mergedDictionaries.Insert(0, brushDict);
}
public static void InitializeTheme()
{
UpdateIcon();
UpdateBackground();
ApplyTheme(Settings.Instance.GetTheme());
}
}
@@ -29,7 +29,7 @@ public static class TrayManager
var contextMenu = new ContextMenuStrip();
contextMenu.Items.Add("Выйти", null!, OnExitClick);
contextMenu.Items.Add("Exit", null!, OnExitClick);
_notifyIcon.ContextMenuStrip = contextMenu;
@@ -69,7 +69,7 @@ public static class TrayManager
private static void HideMainWindow()
{
if (!Init) return;
if (IsEnabled == false) return;
if (!IsEnabled) return;
_notifyIcon.Visible = true;
MainWindow.Hide();
}
@@ -1,7 +1,6 @@
using System;
using System.IO;
using AutoUpdaterDotNET;
using NebulaAuth.Model;
namespace NebulaAuth.Core;
@@ -15,11 +14,28 @@ public static class UpdateManager
var jsonPath = Path.Combine(Environment.CurrentDirectory, "update-settings.json");
AutoUpdater.PersistenceProvider = new JsonFilePersistenceProvider(jsonPath);
AutoUpdater.ShowSkipButton = false;
if (Settings.Instance.AllowAutoUpdate)
AutoUpdater.UpdateMode = Mode.ForcedDownload;
AutoUpdater.RunUpdateAsAdmin = RequiresAdminAccess();
AutoUpdater.Start(UPDATE_URL);
}
private static bool RequiresAdminAccess()
{
try
{
var testFile = Path.Combine(Environment.CurrentDirectory, "test.tmp");
using (File.Create(testFile))
{
}
File.Delete(testFile);
return false;
}
catch (UnauthorizedAccessException)
{
return true;
}
}
//static UpdateManager()
//{
+538
View File
@@ -0,0 +1,538 @@
<Window x:Class="NebulaAuth.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:windowStyle="clr-namespace:NebulaAuth.Theme.WindowStyle"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:viewModel="clr-namespace:NebulaAuth.ViewModel"
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
WindowStartupLocation="CenterScreen"
MinHeight="500" MinWidth="500"
Title="NebulaAuth" Height="800" Width="730"
Style="{StaticResource MainWindow}"
RenderOptions.BitmapScalingMode="HighQuality"
FontFamily="{md:MaterialDesignFont}"
TextElement.Foreground="{DynamicResource BaseContentBrush}"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModel:MainVM}"
md:RippleAssist.IsDisabled="{Binding Settings.RippleDisabled}"
FontSize="18">
<b:Interaction.Behaviors>
<windowStyle:WindowChromeRenderedBehavior />
</b:Interaction.Behaviors>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=PasteMafilesFromClipboardCommand}"
Key="V"
Modifiers="Control" />
<KeyBinding Command="{x:Static ApplicationCommands.Find}"
Key="F"
Modifiers="Control" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="Find"
Executed="FocusSearchBox" />
</Window.CommandBindings>
<Border Name="DragNDropBorder" Panel.ZIndex="3" Opacity="1" AllowDrop="True"
DragDrop.DragEnter="Rectangle_DragEnter" DragLeave="Rectangle_DragLeave" Drop="Rectangle_Drop">
<md:DialogHost DialogOpened="DialogHost_DialogOpened"
DialogClosed="DialogHost_DialogClosed"
ApplyBlurBackground="{Binding Settings.ApplyBlurBackground}"
BlurRadius="12"
x:Name="DialogHostInstance">
<Grid ZIndex="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.RowSpan="2" Opacity="{Binding Settings.BackgroundOpacity}" Stretch="UniformToFill"
RenderOptions.BitmapScalingMode="LowQuality" HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}"
Source="{Binding Settings.BackgroundMode, Mode=OneWay, Converter={StaticResource BackgroundSourceConverter}}">
<Image.Effect>
<BlurEffect Radius="{Binding Settings.BackgroundBlur}" />
</Image.Effect>
</Image>
<Rectangle Grid.Row="0" Grid.RowSpan="2" Fill="Black" Stretch="UniformToFill"
Opacity="{Binding Settings.BackgroundGamma}"
IsHitTestVisible="False" />
<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" Grid.RowSpan="3" Visibility="Hidden"
VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock FontSize="24" FontWeight="Bold" Foreground="#9a65b8"
Text="{Tr MainWindow.Global.DragNDropHint}" />
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36"
Kind="FileReplaceOutline" />
</StackPanel>
<ToolBarTray Grid.Row="0">
<ToolBar ClipToBounds="True" HorizontalContentAlignment="Stretch"
Style="{StaticResource MaterialDesignToolBar}" FontSize="16">
<Menu FontSize="16">
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.File.Import}"
Command="{Binding AddMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Remove}"
Command="{Binding RemoveMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}"
Command="{Binding OpenMafileFolderCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.ProxyManager}"
Command="{Binding OpenProxyManagerCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}"
Command="{Binding OpenSettingsDialogCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.Other.Title}">
<MenuItem Header="{Tr MainWindow.Menu.File.Other.SetPasswords}"
Command="{Binding OpenSetPasswordsDialogCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Other.OpenExport}"
Command="{Binding OpenExporterDialogCommand}" />
</MenuItem>
</MenuItem>
</Menu>
<Menu>
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}"
Command="{Binding LinkAccountCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.MoveSteamGuard}"
Command="{Binding MoveAccountCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}"
Command="{Binding RemoveAuthenticatorCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.Account.RefreshSession}"
Command="{Binding RefreshSessionCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.LoginAgain}"
Command="{Binding LoginAgainCommand}" />
</MenuItem>
</Menu>
<Separator />
<ComboBox ToolTip="{Tr MainWindow.AppBar.GroupToolTip}"
md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}"
MinWidth="100"
MaxWidth="200"
Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
ItemsSource="{Binding Groups}"
SelectedValue="{Binding SelectedGroup}"
md:TextFieldAssist.HasClearButton="True">
<FrameworkElement.Resources>
<ResourceDictionary>
<Style TargetType="md:PackIcon">
<Setter Property="Width" Value="15" />
<Setter Property="Height" Value="15" />
</Style>
</ResourceDictionary>
</FrameworkElement.Resources>
<UIElement.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}"
CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
</UIElement.InputBindings>
</ComboBox>
<ComboBox ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyManipulateToolTip}"
MinWidth="80"
MaxWidth="200"
Margin="8,0,8,0"
VerticalAlignment="Center"
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
md:ComboBoxAssist.ShowSelectedItem="False"
SelectedValue="{Binding SelectedProxy}"
ItemsSource="{Binding Proxies}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
<TextBlock
Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.Proxy.ProxyOpenManagerHint}"
Command="{Binding OpenProxyManagerCommand}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
<UIElement.InputBindings>
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
</UIElement.InputBindings>
</ComboBox>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
Foreground="{DynamicResource ErrorBrush}" Margin="3"
ToolTipService.InitialShowDelay="300">
<md:PackIcon.ToolTip>
<TextBlock>
<Run
Text="{Tr MainWindow.AppBar.Proxy.ProxyAlert.MafileProxyInUse, IsDynamic=False}" />
<Run Text="&#10;" />
<Run Text="{Binding SelectedMafile.Proxy.Data, FallbackValue='', Mode=OneWay}" />
</TextBlock>
</md:PackIcon.ToolTip>
<UIElement.Visibility>
<Binding Path="ProxyExist">
<Binding.Converter>
<converters:ValueConverterGroup>
<converters:ReverseBooleanConverter />
<BooleanToVisibilityConverter />
</converters:ValueConverterGroup>
</Binding.Converter>
</Binding>
</UIElement.Visibility>
</md:PackIcon>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
Foreground="{DynamicResource WarningBrush}" Margin="3"
ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}"
ToolTipService.InitialShowDelay="300"
Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Separator />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}"
IsEnabled="{Binding IsMafileSelected}" Margin="2"
Style="{StaticResource MaterialDesignFlatToggleButton}"
IsChecked="{Binding MarketTimerEnabled}">
<md:PackIcon Kind="ShoppingCart" />
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}"
Command="{Binding Path=SwitchMAACOnGroupCommand}"
CommandParameter="{StaticResource True}" />
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}"
Command="{Binding Path=SwitchMAACOnAllCommand}"
CommandParameter="{StaticResource True}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
</ToggleButton>
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}"
IsEnabled="{Binding IsMafileSelected}" Margin="2"
Style="{StaticResource MaterialDesignFlatToggleButton}"
IsChecked="{Binding TradeTimerEnabled}">
<md:PackIcon Kind="AccountArrowRight" />
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACGroup}"
Command="{Binding Path=SwitchMAACOnGroupCommand}"
CommandParameter="{StaticResource False}" />
<MenuItem Header="{Tr MainWindow.AppBar.TimersContextMenu.SwitchMAACAll}"
Command="{Binding Path=SwitchMAACOnAllCommand}"
CommandParameter="{StaticResource False}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
</ToggleButton>
<TextBox 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" />
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}"
HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}"
Foreground="{DynamicResource BaseContentBrush}" VerticalAlignment="Center"
Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
<md:PackIcon Kind="Accounts" />
</ToggleButton>
</ToolBar>
</ToolBarTray>
<Grid Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.4*" />
<ColumnDefinition Width="0.6*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Grid.Row="0" Margin="10" Padding="5"
BorderThickness="1"
CornerRadius="12">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.3" />
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource Base100Color}"
Opacity="{Binding Settings.LeftOpacity}" />
</Border.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Margin="4"
Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}}"
TextWrapping="WrapWithOverflow" FontSize="16"
Text="{Tr MainWindow.LeftPart.NoMafiles}" />
<ListBox FontSize="17" Margin="2"
x:Name="MafileListBox"
SelectionMode="Single"
ItemsSource="{Binding MaFiles}"
SelectedValue="{Binding SelectedMafile}"
PreviewMouseDown="MafileListBox_OnPreviewMouseDown">
<ListBox.Resources>
<DiscreteObjectKeyFrame x:Key="groupsProxy" Value="{Binding Groups}" />
<DiscreteObjectKeyFrame x:Key="listBoxProxy"
Value="{Binding ElementName=MafileListBox}" />
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem"
BasedOn="{StaticResource MaterialDesignListBoxItem}">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid ToolTip="{Binding Filename}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch"
Text="{Binding AccountName}"
ToolTip="{Binding LinkedClient.Status.Message, FallbackValue={x:Null}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground"
Value="{DynamicResource BaseContentBrush}" />
<Style.Triggers>
<DataTrigger
Binding="{Binding LinkedClient.Status.StatusType}"
Value="Ok">
<Setter Property="Foreground"
Value="{DynamicResource SuccessBrush}" />
</DataTrigger>
<DataTrigger
Binding="{Binding LinkedClient.Status.StatusType}"
Value="Warning">
<Setter Property="Foreground"
Value="{DynamicResource WarningBrush}" />
</DataTrigger>
<DataTrigger
Binding="{Binding LinkedClient.Status.StatusType}"
Value="Error">
<Setter Property="Foreground"
Value="{DynamicResource ErrorBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<StackPanel
Visibility="{Binding LinkedClient, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Column="1" Orientation="Horizontal">
<md:PackIcon Kind="ShoppingCart"
Visibility="{Binding LinkedClient.AutoConfirmMarket, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
<md:PackIcon Kind="AccountArrowRight"
Visibility="{Binding LinkedClient.AutoConfirmTrades, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.InputBindings>
<KeyBinding Key="C" Modifiers="Control"
Command="{Binding DataContext.CopyLoginCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
<KeyBinding Key="X" Modifiers="Control"
Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
<KeyBinding Modifiers="Control+Shift" Key="C"
Command="{Binding DataContext.CopyPasswordCommand, RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
</ListBox.InputBindings>
<FrameworkElement.ContextMenu>
<ContextMenu Tag="{Binding Groups}">
<MenuItem InputGestureText="Ctrl+C"
Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}"
Command="{Binding CopyLoginCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
Command="{Binding Path=CopyPasswordCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
InputGestureText="CTRL+⇧+C" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}"
Command="{Binding CopySteamIdCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem InputGestureText="Ctrl+X"
Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}"
Command="{Binding CopyMafileCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}">
<MenuItem.ItemsSource>
<CompositeCollection>
<MenuItem
Command="{Binding CreateGroupCommand}"
CommandParameter="{Binding Value.SelectedValue, Source={StaticResource listBoxProxy}}">
<MenuItem.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<md:PackIcon Kind="PlusCircle"
VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center"
Margin="5,0,0,0" Grid.Column="1"
Text="{Tr MainWindow.ContextMenus.Mafile.CreateGroup}" />
</Grid>
</MenuItem.Header>
</MenuItem>
<Separator />
<CollectionContainer
Collection="{Binding Value, Source={StaticResource groupsProxy}}" />
</CompositeCollection>
</MenuItem.ItemsSource>
<ItemsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignMenuItem}"
TargetType="{x:Type MenuItem}">
<Setter Property="Command"
Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding
Converter="{StaticResource MultiCommandParameterConverter}">
<Binding />
<Binding Path="PlacementTarget.SelectedValue"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</MenuItem>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}"
Command="{Binding Path=RemoveGroupCommand}"
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.UnattachProxy}"
Command="{Binding RemoveProxyCommand}"
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
</ListBox>
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
FontSize="16"
md:TextFieldAssist.HasClearButton="True" Grid.Row="1" Margin="10,5,10,10"
md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}"
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
x:Name="SearchField"
KeyDown="SearchField_OnKeyDown" />
</Grid>
</Border>
</Grid>
<Grid Grid.Column="1">
<Border Background="{DynamicResource MaterialDesign.Brush.Card.Background}"
Opacity="{Binding Settings.RightOpacity}"
BorderThickness="1"
Margin="10,10,15,10" CornerRadius="12" VerticalAlignment="Stretch">
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.3" />
</Border.BorderBrush>
</Border>
<md:Card Margin="10,10,15,10" Padding="2" UniformCornerRadius="12"
Background="Transparent">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="115*" />
<RowDefinition Height="436*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Row="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox FontSize="24"
IsReadOnly="True" HorizontalContentAlignment="Center"
Background="#00FFFFFF"
Style="{StaticResource MaterialDesignTextBox}"
Text="{Binding Code, FallbackValue=Code}">
<TextBox.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
</TextBox.InputBindings>
</TextBox>
<nebulaAuth:CodeProgressBar
Visibility="{Binding SelectedMafile, Converter={StaticResource NullableToVisibilityConverter}}"
Grid.Row="1" Height="4"
Style="{StaticResource MaterialDesignLinearProgressBar}"
MaxTime="30" TimeRemaining="{Binding CodeProgress, Mode=OneWay}" />
</Grid>
<Button IsEnabled="{Binding IsMafileSelected}"
FontSize="14"
Grid.Row="1"
Style="{StaticResource MaterialDesignOutlinedButton}"
Margin="0,10,0,10"
HorizontalAlignment="Center"
Content="{Tr MainWindow.RightPart.LoadConfirmations}"
Command="{Binding GetConfirmationsCommand}"
md:ButtonProgressAssist.IsIndeterminate="True"
md:ButtonProgressAssist.IsIndicatorVisible="{Binding GetConfirmationsCommand.IsRunning}"
Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}" />
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2"
FontSize="16"
Margin="10,10,10,10"
Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
ItemsSource="{Binding Confirmations}" Grid.RowSpan="2" />
<Button FontSize="16"
Style="{StaticResource MaterialDesignFlatButton}" Grid.Row="4"
Command="{Binding ConfirmLoginCommand}"
Content="{Tr MainWindow.RightPart.ConfirmLogin}" />
</Grid>
</md:Card>
</Grid>
</Grid>
<Border Grid.Row="2" BorderBrush="#1e1e24">
<Border.Background>
<SolidColorBrush Opacity="1" Color="{DynamicResource Base300Color}" />
</Border.Background>
<Grid>
<ToolBarPanel TextElement.Foreground="{DynamicResource SecondaryContentBrush}"
Orientation="Horizontal" Margin="5">
<TextBlock FontSize="15" Text="{Tr MainWindow.Footer.AccountsCount}" />
<TextBlock FontSize="15" Text="{Binding MaFiles.Count}" />
<TextBlock FontSize="15" Text="|" Margin="10,0,10,0" />
<TextBlock FontSize="15" Text="{Tr MainWindow.Footer.SelectedAccount}" />
<TextBlock FontSize="15"
Text="{Binding SelectedMafile.AccountName, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
<!--<Button Command="{Binding DebugCommand}">Debug</Button>-->
</ToolBarPanel>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="16"
Margin="0,0,10,0">
<Hyperlink
NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies"
Foreground="{DynamicResource AccentBrush}"
Command="{Binding OpenLinksViewCommand}">
by achies
</Hyperlink>
</TextBlock>
</Grid>
</Border>
<md:Snackbar Grid.Row="1" MessageQueue="{Binding MessageQueue}" />
</Grid>
</md:DialogHost>
</Border>
</Window>

Some files were not shown because too many files have changed in this diff Show More