Compare commits

...

58 Commits

Author SHA1 Message Date
achiez 5836ed4508 feat(proxy): add SignAtScheme support for improved proxy parsing 2026-06-19 18:55:39 +03:00
achiez ce55005d44 chore: remove outdated README files and update main README with new features and documentation link 2026-05-04 19:37:37 +03:00
github-actions a23d7017a7 chore(release): 1.8.4 2026-03-14 13:36:41 +00:00
achiez c879dd462c fix(build): include NebulaAuth.exe in release package
Fix CI packaging configuration to ensure NebulaAuth.exe is included
in the published release artifacts.
2026-03-14 15:36:02 +02:00
github-actions e681ca07f1 chore(release): 1.8.3 2026-03-13 13:37:20 +00:00
Achies 9227b383bb Merge pull request #20 from achiez/1.8.3
Release 1.8.3
2026-03-13 15:36:47 +02:00
achiez 0130737789 chore(ver): bump version to 1.8.3 and clean solution
- Bump version to 1.8.3
- Add 1.8.3 changelog
- Migrate to .slnx
- Cleanup solution
2026-03-13 15:35:38 +02:00
achiez 1ab2f99783 chore: perform resharper cleanup 2026-03-13 14:54:33 +02:00
achiez da00fa5c96 loc: add es, tr, kk locales and split localization files 2026-03-13 14:51:03 +02:00
Achies 0a46950ca9 Merge pull request #19 from achiez/feature/updates-v2
feat(update): introduce modern update system

- Replace legacy AutoUpdater dialog with a custom update UI integrated into the application theme
- Add support for JSON-based changelog with HTML fallback for backward compatibility
- Implement "Remind later" and "Skip version" options to reduce intrusive update prompts
- Add visual update indicator and manual "Check for updates" action in the links menu
- Persist user update preferences (skipped versions and reminder delays)
- Introduce checksum validation (SHA256) for downloaded update packages
- Migrate changelog source format from HTML to JSON
- Automatically generate HTML changelog and GitHub release notes from JSON via CI
- Consolidate release automation into a single GitHub Actions workflow
- Remove obsolete legacy update dialog and related code
- Expand localization coverage for update-related UI
2026-03-13 00:25:04 +02:00
achiez 9ac4ffdc34 feat(update): introduce modern update system with JSON changelog, reminders and checksum validation
- Replace legacy AutoUpdater dialog with a custom update UI integrated into the application theme
- Add support for JSON-based changelog with HTML fallback for backward compatibility
- Implement "Remind later" and "Skip version" options to reduce intrusive update prompts
- Add visual update indicator and manual "Check for updates" action in the links menu
- Persist user update preferences (skipped versions and reminder delays)
- Introduce checksum validation (SHA256) for downloaded update packages
- Migrate changelog source format from HTML to JSON
- Automatically generate HTML changelog and GitHub release notes from JSON via CI
- Consolidate release automation into a single GitHub Actions workflow
- Remove obsolete legacy update dialog and related code
- Expand localization coverage for update-related UI
2026-03-13 00:17:45 +02:00
achiez 17635ccba0 fix(confirmations): roll back the erroneously assigned ConfirmCommand in PurchaseConfirmation 2026-03-12 23:56:57 +02:00
bohdanbtw 69589075e5 feat(import): support SDA-encrypted mafiles (#17)
NebulaAuth previously supported only plain JSON `.mafile` files. Steam Desktop Authenticator (SDA) can encrypt these files and stores per-file salt and IV values in `manifest.json`, which prevented users from importing accounts protected with SDA encryption.

Add support for importing SDA-encrypted mafiles. The importer now detects encrypted blobs, automatically locates the SDA manifest, prompts for the SDA password once, and decrypts files during import.

The import flow was also refactored to unify handling of plain and encrypted mafiles. SDA detection and decryption logic is moved into a dedicated helper, batch imports reuse the entered password, and the ViewModel import logic was simplified with explicit result handling instead of exception-driven control flow.

This allows users to migrate accounts from Steam Desktop Authenticator to NebulaAuth even when their mafiles are encrypted.
2026-03-12 21:22:18 +02:00
bohdanbtw 4b547e19c4 feat(confirmations): reveal items in grouped market sell confirmations (#18)
Grouped market sell confirmations previously appeared as a single row showing only the item count (e.g. "Sell 4 pcs"). This made it difficult to verify which items were being confirmed or their prices.

Add an expandable grouped confirmation that reveals individual items with their icon, name and price. Also allow confirming or canceling items individually while keeping confirm-all and cancel-all actions.

Includes a scrollable list for large batches and minor UI/code cleanup.

Co-authored-by: bohdanbtw [bohdanbtw@DESKTOP-ANGF2KJ](mailto:bohdanbtw@DESKTOP-ANGF2KJ)
2026-03-12 17:27:03 +02:00
Achies a3804dd48d Update README.md
Add copyright disclaimer
2026-03-12 15:41:38 +02:00
achiez 053faec343 feat(export): add input trimming in export feature 2026-03-12 14:57:27 +02:00
github-actions b8ac76d2a9 chore(release): 1.8.2 2026-02-10 14:43:46 +00:00
Achies b81d45765a Merge pull request #15 from achiez/1.8.2
Release 1.8.2
2026-02-10 16:43:35 +02:00
achiez 372b8c6463 fix(maac): portable MacClient status reset and prepare release
- Add changelog/1.8.2.html with details for version 1.8.2
- Client status is properly updates after successful MAAC request
- Bump version to 1.8.2
2026-02-10 16:40:36 +02:00
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
364 changed files with 15449 additions and 7866 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
+220
View File
@@ -0,0 +1,220 @@
name: Build and Publish Release
on:
push:
branches:
- master
paths:
- 'src/NebulaAuth/NebulaAuth.csproj'
- 'changelog/*.json'
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
# --------------------------------------------------------
# Checkout
# --------------------------------------------------------
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.PAT_TOKEN }}
# --------------------------------------------------------
# Setup .NET
# --------------------------------------------------------
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
# --------------------------------------------------------
# Extract version via MSBuild
# --------------------------------------------------------
- name: Extract version from csproj
id: ver
run: |
VERSION=$(dotnet msbuild src/NebulaAuth/NebulaAuth.csproj -getProperty:AssemblyVersion)
echo "Detected version $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
# --------------------------------------------------------
# Validate JSON changelog
# --------------------------------------------------------
- name: Validate changelog JSON
run: |
VERSION="${{ steps.ver.outputs.version }}"
FILE="changelog/${VERSION}.json"
echo "Checking changelog file $FILE"
if [ ! -f "$FILE" ]; then
echo "ERROR: changelog JSON not found"
exit 1
fi
jq empty "$FILE"
# --------------------------------------------------------
# Generate HTML changelog (legacy AutoUpdater)
# --------------------------------------------------------
- name: Generate HTML changelog
run: |
VERSION="${{ steps.ver.outputs.version }}"
JSON="changelog/${VERSION}.json"
HTML="changelog/${VERSION}.html"
echo "Generating HTML changelog"
jq -r '
"<!DOCTYPE html>",
"<html>",
"<head>",
"<meta charset=\"UTF-8\">",
"<title>Changelog</title>",
"<style>",
"body { font-family: Segoe UI, sans-serif; background:#eeeeee; padding:20px; }",
".change { background:white; padding:25px; border-radius:10px; }",
"li { margin-bottom:6px; }",
"</style>",
"</head>",
"<body>",
"<div class=\"change\">",
"<ul>",
(.changes[] |
"<li><b>\(.type):</b> \(.text)" +
(if .link then " <a href=\"\(.link)\">details</a>" else "" end) +
"</li>"
),
"</ul>",
"</div>",
"</body>",
"</html>"
' "$JSON" > "$HTML"
echo "Generated $HTML"
# --------------------------------------------------------
# Build application
# --------------------------------------------------------
- name: Build NebulaAuth
run: |
dotnet publish src/NebulaAuth/NebulaAuth.csproj \
-c Release \
-r win-x64 \
--self-contained false \
-p:EnableWindowsTargeting=true \
-o build
# --------------------------------------------------------
# Package ZIP
# --------------------------------------------------------
- name: Package release
run: |
cd build
zip -r ../NebulaAuth.${{ steps.ver.outputs.version }}.zip *
# --------------------------------------------------------
# Generate SHA256 checksum
# --------------------------------------------------------
- name: Generate checksum
id: checksum
run: |
FILE="NebulaAuth.${{ steps.ver.outputs.version }}.zip"
HASH=$(sha256sum "$FILE" | awk '{print $1}')
echo "checksum=$HASH" >> $GITHUB_OUTPUT
# --------------------------------------------------------
# Generate update.xml
# --------------------------------------------------------
- name: Generate update.xml
run: |
VERSION="${{ steps.ver.outputs.version }}"
CHECKSUM="${{ steps.checksum.outputs.checksum }}"
cat > NebulaAuth/update.xml <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>$VERSION</version>
<url>https://github.com/${{ github.repository }}/releases/download/$VERSION/NebulaAuth.$VERSION.zip</url>
<changelog>https://achiez.github.io/${{ github.event.repository.name }}/changelog/$VERSION.html</changelog>
<mandatory>false</mandatory>
<checksum algorithm="SHA256">$CHECKSUM</checksum>
</item>
EOF
echo "Generated update.xml"
cat NebulaAuth/update.xml
# --------------------------------------------------------
# Generate GitHub Release Notes
# --------------------------------------------------------
- name: Generate release notes
id: notes
run: |
VERSION="${{ steps.ver.outputs.version }}"
FILE="changelog/${VERSION}.json"
BODY=$(jq -r '
.changes[] |
"- **\(.type):** \(.text)" +
(if .link then " (\(.link))" else "" end)
' "$FILE")
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$BODY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# --------------------------------------------------------
# Commit generated files
# --------------------------------------------------------
- name: Commit generated files
run: |
git config user.name "github-actions"
git config user.email "actions@github.com"
git add NebulaAuth/update.xml
git add changelog/*.html
git commit -m "chore(release): ${{ steps.ver.outputs.version }}" || echo "No changes"
git push
# --------------------------------------------------------
# Create git tag
# --------------------------------------------------------
- name: Create tag
run: |
VERSION="${{ steps.ver.outputs.version }}"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"
# --------------------------------------------------------
# Publish GitHub Release
# --------------------------------------------------------
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.ver.outputs.version }}
name: Release ${{ steps.ver.outputs.version }}
body: ${{ steps.notes.outputs.body }}
files: |
NebulaAuth.${{ steps.ver.outputs.version }}.zip
+3 -1
View File
@@ -360,4 +360,6 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
todo/
@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
</ItemGroup>
</Project>
-176
View File
@@ -1,176 +0,0 @@
using System.Reflection;
using AchiesUtilities.Extensions;
using NebulaAuth.LegacyConverter;
using Newtonsoft.Json;
using SteamLib.Utility.MafileSerialization;
try
{
var mafileSerializer = new MafileSerializer(new MafileSerializerSettings
{
DeserializationOptions =
{
AllowDeviceIdGeneration = true,
AllowSessionIdGeneration = true
}
});
var currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (currentPath != null)
Environment.CurrentDirectory = currentPath;
const string toStoreFolder = "ConvertedMafiles";
if (Directory.Exists(toStoreFolder) == false)
{
Directory.CreateDirectory(toStoreFolder);
}
else
{
var isEmpty = Directory.GetFiles(toStoreFolder).Length == 0;
Console.ForegroundColor = ConsoleColor.Yellow;
if (!isEmpty)
{
Console.WriteLine(
"WARNING! 'ConverterdMafiles' folder is not empty. Please backup data from it and then continue.");
Console.ResetColor();
Console.WriteLine("Press Y to continue");
while (Console.ReadKey(true).Key != ConsoleKey.Y)
{
}
}
}
var decryptMode = false;
while (true)
{
Console.WriteLine("Press 'D' to select decrypt mode. Press 'C' to convert mode. Press ESC to exit");
var key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.D:
decryptMode = true;
break;
case ConsoleKey.C:
break;
case ConsoleKey.Escape:
return;
default:
continue;
}
break;
}
Manifest? manifest = null;
string? password = null;
if (decryptMode)
{
var files = Directory.GetFiles(currentPath, "manifest.json");
var manifestPath = files.FirstOrDefault();
if (manifestPath == null)
{
Console.WriteLine("No manifest.json found in current directory");
return;
}
var manifestText = File.ReadAllText(manifestPath);
try
{
manifest = JsonConvert.DeserializeObject<Manifest>(manifestText)!;
}
catch (Exception ex)
{
Console.WriteLine("Can't read manifest: " + ex);
return;
}
if (manifest.Encrypted == false)
{
Console.WriteLine("Manifest is not encrypted");
return;
}
while (true)
{
Console.WriteLine("Please enter your encryption password: ");
password = Console.ReadLine();
if (string.IsNullOrWhiteSpace(password))
continue;
break;
}
}
Console.WriteLine(currentPath);
foreach (var path in args)
{
if (Path.Exists(path) == false)
{
Console.WriteLine($"NOT VALID PATH: '{path}'");
continue;
}
Console.WriteLine("Reading: " + path);
try
{
var text = File.ReadAllText(path);
if (decryptMode)
{
var fileName = Path.GetFileName(path);
text = DecryptMafile(fileName, text);
if (text == null)
{
Console.WriteLine(path + " not found in manifest. Skipped");
continue;
}
}
var data = mafileSerializer.Deserialize(text);
var maf = data.Data;
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
Write(legacy, fileNameWithoutExtension);
Console.WriteLine("DONE: " + fileNameWithoutExtension);
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex);
}
finally
{
Console.WriteLine("-----------------------------------------");
}
}
//Local Functions
void Write(string maf, string name)
{
var path = Path.Combine(toStoreFolder, name + "_legacy.mafile");
File.WriteAllText(path, maf);
}
string? DecryptMafile(string fileName, string cipherText)
{
if (password == null) return null;
var entry = manifest?.Entries.FirstOrDefault(x => x.Filename.EqualsIgnoreCase(fileName));
if (entry == null)
{
return null;
}
var iv = entry.EncryptionIv;
var salt = entry.EncryptionSalt;
return SDAEncryptor.DecryptData(password, salt, iv, cipherText);
}
}
finally
{
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
-59
View File
@@ -1,59 +0,0 @@
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
changelog\1.4.4.html = changelog\1.4.4.html
changelog\1.4.5.html = changelog\1.4.5.html
changelog\1.4.6.html = changelog\1.4.6.html
changelog\1.4.7.html = changelog\1.4.7.html
changelog\1.4.8.html = changelog\1.4.8.html
changelog\1.4.9.html = changelog\1.4.9.html
changelog\1.5.0.html = changelog\1.5.0.html
changelog\1.5.1.html = changelog\1.5.1.html
changelog\1.5.2.html = changelog\1.5.2.html
changelog\1.5.3.html = changelog\1.5.3.html
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
EndProjectSection
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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FA34DDD5-BDC0-4C0A-B347-4ECDBF446900}
EndGlobalSection
EndGlobal
+29
View File
@@ -0,0 +1,29 @@
<Solution>
<Folder Name="/changelog/">
<File Path="changelog/1.3.4.html" />
<File Path="changelog/1.4.4.html" />
<File Path="changelog/1.4.5.html" />
<File Path="changelog/1.4.6.html" />
<File Path="changelog/1.4.7.html" />
<File Path="changelog/1.4.8.html" />
<File Path="changelog/1.4.9.html" />
<File Path="changelog/1.5.0.html" />
<File Path="changelog/1.5.1.html" />
<File Path="changelog/1.5.2.html" />
<File Path="changelog/1.5.3.html" />
<File Path="changelog/1.5.4.html" />
<File Path="changelog/1.5.5.html" />
<File Path="changelog/1.5.6.html" />
<File Path="changelog/1.7.1.html" />
<File Path="changelog/1.7.2.html" />
<File Path="changelog/1.7.3.html" />
<File Path="changelog/1.7.4.html" />
<File Path="changelog/1.8.0.html" />
<File Path="changelog/1.8.1.html" />
<File Path="changelog/1.8.2.html" />
<File Path="changelog/1.8.3.json" />
<File Path="changelog/1.8.4.json" />
</Folder>
<Project Path="src/NebulaAuth/NebulaAuth.csproj" />
<Project Path="src/SteamLibForked/SteamLibForked.csproj" />
</Solution>
-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();
}
}
-96
View File
@@ -1,96 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.View;
using NebulaAuth.View.Dialogs;
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
{
UserName = username,
Password = currentPassword ?? string.Empty,
SavePassword = PHandler.IsPasswordSet
};
var content = new LoginAgainDialog
{
DataContext = vm
};
var result = await DialogHost.Show(content);
if (result is true)
{
return vm;
}
return null;
}
public static async Task<LoginAgainOnImportVM?> ShowLoginAgainOnImportDialog(Mafile mafile,
IEnumerable<MaProxy> proxies)
{
var vm = new LoginAgainOnImportVM(mafile, proxies);
var content = new LoginAgainOnImportDialog
{
DataContext = vm
};
var result = await DialogHost.Show(content);
if (result is true)
{
return vm;
}
return null;
}
public static async Task<bool> ShowProxyManager()
{
var vm = new ProxyManagerVM();
var view = new ProxyManagerView
{
DataContext = vm
};
await DialogHost.Show(view);
return vm.AnyChanges;
}
public static void CloseDialog()
{
DialogHost.Close(null);
}
public static async Task ShowLinkerDialog()
{
var vm = new LinkAccountVM();
var view = new LinkerView
{
DataContext = vm
};
await DialogHost.Show(view);
}
}
-75
View File
@@ -1,75 +0,0 @@
using System;
using System.IO;
using AutoUpdaterDotNET;
using NebulaAuth.Model;
namespace NebulaAuth.Core;
public static class UpdateManager
{
private const string UPDATE_URL =
"https://raw.githubusercontent.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/master/NebulaAuth/update.xml";
public static void CheckForUpdates()
{
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.Start(UPDATE_URL);
}
//static UpdateManager()
//{
// //AutoUpdater.CheckForUpdateEvent += AutoUpdaterOnCheckForUpdateEvent;
//}
//private static async void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args)
//{
// if (args.Error == null)
// {
// if (args.IsUpdateAvailable)
// {
// DialogResult dialogResult;
// var dialog = new UpdaterView()
// {
// DataContext = new UpdaterVM(args)
// };
// await DialogHost.Show(dialog);
// Application.Current.Shutdown();
// }
// else
// {
// }
// }
// else
// {
// if (args.Error is WebException)
// {
// }
// else
// {
// }
// }
//}
//private static void RunUpdate(UpdateInfoEventArgs args)
//{
// Application.Current.Dispatcher.Invoke(() =>
// {
// if (AutoUpdater.DownloadUpdate(args))
// {
// Application.Current.Shutdown();
// }
// }, DispatcherPriority.ContextIdle);
//}
}
-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>
-112
View File
@@ -1,112 +0,0 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Threading;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel;
using NebulaAuth.ViewModel.Other;
namespace NebulaAuth;
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainVM();
Application.Current.MainWindow = this;
TrayManager.InitializeTray();
ThemeManager.InitializeTheme();
Title = Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
Loaded += OnApplicationStarted;
}
private async void OnApplicationStarted(object? sender, EventArgs e)
{
if (Settings.Instance.IsPasswordSet == false) return;
await Dispatcher.BeginInvoke(ShowSetPasswordDialog, DispatcherPriority.ContextIdle);
}
private async Task ShowSetPasswordDialog()
{
var vm = new SetEncryptPasswordVM();
var dialog = new SetCryptPasswordDialog
{
DataContext = vm
};
var result = await DialogHost.Show(dialog);
var pass = vm.Password;
if (result is true && string.IsNullOrWhiteSpace(pass) == false)
{
PHandler.SetPassword(pass);
}
}
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.ToString())
{
UseShellExecute = true
});
}
#region Dran'n'Drop
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (int.TryParse(e.Text, out _) == false) e.Handled = true;
}
private void Rectangle_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false)
{
e.Handled = true;
return;
}
DragNDropPanel.Visibility = Visibility.Visible;
DragNDropOverlay.Visibility = Visibility.Visible;
}
private async void Rectangle_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
var filePaths = (string[]) e.Data.GetData(DataFormats.FileDrop)!;
if (filePaths.Length == 0) return;
if (DataContext is MainVM mainVm)
{
await mainVm.AddMafile(filePaths);
}
DragNDropPanel.Visibility = Visibility.Hidden;
DragNDropOverlay.Visibility = Visibility.Hidden;
}
private void Rectangle_DragLeave(object sender, DragEventArgs e)
{
DragNDropPanel.Visibility = Visibility.Hidden;
DragNDropOverlay.Visibility = Visibility.Hidden;
}
private void DialogHost_DialogOpened(object sender, DialogOpenedEventArgs eventArgs)
{
DragNDropBorder.AllowDrop = false;
}
private void DialogHost_DialogClosed(object sender, DialogClosedEventArgs eventArgs)
{
DragNDropBorder.AllowDrop = true;
}
#endregion
}
-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();
}
}
-55
View File
@@ -1,55 +0,0 @@
<UserControl x:Class="NebulaAuth.View.UpdaterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
d:DataContext="{d:DesignInstance other:UpdaterVM}"
Background="{DynamicResource WindowBackground}"
Padding="10"
MaxWidth="500">
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock FontSize="24" Text="Обновления" />
<Separator Grid.Row="1" />
<TextBlock FontSize="16" Margin="5,10,0,10" TextWrapping="WrapWithOverflow" Grid.Row="2">
<Run Text="Доступна новая версия:" />
<Run FontWeight="Bold" Text="{Binding UpdateInfoEventArgs.CurrentVersion}" />
<Run Text="&#x0a;Вы используете версию" />
<Run FontWeight="Bold" Text="{Binding UpdateInfoEventArgs.InstalledVersion}" />
<Run Text="Хотите обновить программу?" />
</TextBlock>
<Expander Header="Что изменилось?" Grid.Row="3">
<!--<wpf:WebView2 HorizontalAlignment="Stretch" Height="300"
Source="{Binding UpdateInfoEventArgs.ChangelogURL}"
/>-->
</Expander>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="OK" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" />
<Button IsCancel="True" Grid.Column="1" Content="Cancel"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" />
</Grid>
</Grid>
</UserControl>
-12
View File
@@ -1,12 +0,0 @@
namespace NebulaAuth.View;
/// <summary>
/// Логика взаимодействия для UpdaterView.xaml
/// </summary>
public partial class UpdaterView
{
public UpdaterView()
{
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);
}
}
-14
View File
@@ -1,14 +0,0 @@
using AutoUpdaterDotNET;
using CommunityToolkit.Mvvm.ComponentModel;
namespace NebulaAuth.ViewModel.Other;
public class UpdaterVM : ObservableObject
{
public UpdateInfoEventArgs UpdateInfoEventArgs { get; }
public UpdaterVM(UpdateInfoEventArgs args)
{
UpdateInfoEventArgs = args;
}
}
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<?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>
<version>1.8.4</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.4/NebulaAuth.1.8.4.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.4.html</changelog>
<mandatory>false</mandatory>
<checksum algorithm="SHA256">5ecad7a711bab7e98f5f3bfa92b7a00e21efc18aa92251bb30bd3e50a2f7d2f2</checksum>
</item>
-74
View File
@@ -1,74 +0,0 @@
# NebulaAuth
## Описание
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Скачать последнюю версию</a>
</h3>
<h3 align="center">NebulaAuth — это приложение для эмуляции действий из мобильного приложения Steam. Которая заменяет ваш смартфон при работе в Steam.</h3>
<h4 align="center"><a href="https://t.me/nebulaauth">Официальная группа в Telegram</a></h4>
## Основные преимущества
- **Локализация на трёх языках**: английском, русском и украинском.
- **Полная функциональность Steam Desktop Authenticator**, переосмысление [старого приложения](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Поддержка прокси** во всех процессах работы с аккаунтом.
- **Группировка мафайлов** для продвинутого контроля.
- **Автоматическое подтверждение трейдов/действий на ТП** для экономии времени.
- **Массовый импорт мафайлов** с помощью Drag'n'Drop или CTRL+V для удобства.
- **Настройка внешнего вида** для персонализации интерфейса.
- **Возможность подтвердить вход в учетную запись без ввода кода** для облегчения доступа.
- **Автообновление** программы для использования новейших функций.
- **Автоматический повторный вход в случае проблем с сессией** для непрерывной работы.
- **Интуитивно понятный интерфейс** с подсказками и удобствами
- **Постоянная поддержка** кода приложения и других функций.
## Установка
1. Если приложение не запускается, необходимо установить [.NET Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
2. [Скачать программу из релизов этого репозитория на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
* *Для сохранности ваших данных скачивайте приложение только отсюда*
4. Распакуйте ZIP-файл в любую папку.
5. Запустите файл **NebulaAuth.exe**.
## Использование
![gh-main-window-rus](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/6a84f414-0e24-40a4-8adb-f1923fbe8719)
1. Панель управления.
- управление файлами и настройки
- управление аккаунтом (вход, привязка, отвязка)
- группировка
- выбор прокси
- индикатор с подсказкой об используемом прокси (горит либо желтым, либо красным, при наведении отобразит дополнительную информацию)
- таймеры для автоматического подтверждения трейдов/продаж на торговой площадке
- интервал таймера подтверждений (в секундах)
2. Список ваших аккаунтов
3. Код подтверждения входа (нажмите, чтобы скопировать)
4. Главное окно подтверждений
5. Поиск по логину или SteamID (7xxxxxxxxxxxxx)
6. Подтвердить вход на другогом устройстве.
7. Гиперссылка на официальную страницу приложения с указанием авторства.
## Настройки
![gh-settings-rus](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/33246ed1-1e3d-4310-88c5-085e5c50bc6b)
1. Режим фона. Используйте, если вы хотите отключить фон или установить собственный (поместите файл «Background.png» в папку приложения)
2. Язык приложения
3. Отключить таймеры при переключении между аккаунтами
4. Скрывать в трей при сворачивании
5. Индикатор с цветом. Маленький кружочек на значке панели задач с произвольным цветом. Полезно при использовании нескольких окон.
6. Пользовательский цвет приложения.
7. Текущий пароль шифрования. Если установлено, вы можете сохранять зашифрованные пароли в mafile, чтобы облегчить повторный вход в систему при проблемах с сессией. (Не рекомендуется)
8. Режим устаревших файлов. Режим совместимости Mafile с другими клиентами (SDA и т.д.). Если установлено, приложение будет сохранять файлы в старом стандартном формате (по умолчанию: включено).
9. Разрешить автообновление без подтверждения
## [Лицензия](/LICENSE.md)
Коммерческое использование запрещено. При распространении измененного кода необходимо указывать оригинальное авторство.
-71
View File
@@ -1,71 +0,0 @@
# NebulaAuth
## Опис
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Завантажити останню версію</a>
</h3>
<h3 align="center">NebulaAuth - це програма для емуляції дій з мобільного додатку Steam. Яка замінює ваш смартфон під час роботи в Steam.</h3>
<h4 align="center"><a href="https://t.me/nebulaauth">Офіційна група в Telegram</a></h4>
## Основні переваги
- **Локалізація трьома мовами**: англійською, російською та українською.
- **Повна функціональність Steam Desktop Authenticator** переосмислення [старої програми](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Підтримка проксі** у всіх процесах роботи з обліковим записом.
- **Угруповання мафайлів** для просунутого контролю.
- **Автоматичне підтвердження трейдів/дій на маркеті** для економії часу.
- **Масовий імпорт мафайлів** за допомогою Drag'n'Drop або CTRL+V для зручності.
- **Налаштування зовнішнього вигляду** для персоналізації інтерфейсу.
- **Можливість підтвердити вхід до облікового запису без введення коду** для полегшення доступу.
- **Автооновлення** програми для використання новітніх функцій.
- **Автоматичний повторний вхід у разі проблем із сесією** для безперервної роботи.
- **Інтуїтивно зрозумілий інтерфейс** з підказками та зручностями
- **Постійна підтримка** коду програми та інших функцій.
## Монтаж
1. Якщо програма не запускається, необхідно встановити [.NET Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
2. [Завантажити програму з релізів цього репозиторію на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
* *Для збереження ваших даних завантажуйте програму тільки звідси*
4. Розпакуйте ZIP-файл у будь-яку папку.
5. Запустіть файл **NebulaAuth.exe**.
## Використання
![gh-main-window-ua](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/bf61ac61-b21c-4589-9b5a-751a6d983120)
1. Панель керування.
- керування файлами та налаштування
- керування акаунтом (вхід, прив'язка, відв'язування)
- угруповання
- вибір проксі
- індикатор з підказкою про проксі (світиться або жовтим, або червоним, при наведенні відобразить додаткову інформацію)
- таймери для автоматичного підтвердження трейдів/продажів на маркеті
- інтервал таймера підтверджень (у секундах)
2. Список ваших облікових записів
3. Код підтвердження входу (натисніть, щоб скопіювати)
4. Головне вікно підтвердження
5. Пошук за логіном або SteamID (7xxxxxxxxxxxxx)
6. Підтвердити вхід на іншому пристрою.
7. Гіперпосилання на офіційну сторінку додатку із зазначенням авторства.
## Налаштування
![gh-settings-ua](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/3f57e58d-d647-42a1-a4c9-ca53ab7b38a3)
1. Режим фону. Використовуйте, якщо ви хочете вимкнути фон або встановити власний (помістіть файл "Background.png" у папку програми)
2. Мова локалізації
3. Вимкнути таймери під час перемикання між обліковими записами
4. Приховувати у трей при згортанні
5. Індикатор із кольором. Маленький кружечок на піктограмі панелі завдань з довільним кольором. Корисно при використанні кількох вікон.
6. Власний колір програми.
7. Поточний пароль шифрування. Якщо встановлено, ви можете зберігати зашифровані паролі в mafile, щоб полегшити повторний вхід до системи у разі проблеми з сесією. (Не рекомендується)
8. Режим застарілих файлів. Режим сумісності Mafile з іншими клієнтами (SDA тощо). Якщо встановлено, програма зберігатиме файли у старому стандартному форматі (за замовчуванням: увімкнено).
9. Дозволити автооновлення без підтвердження
## [Ліцензія](/LICENSE.md)
Комерційне використання заборонено. У разі поширення зміненого коду необхідно вказувати оригінальне авторство.
+25 -46
View File
@@ -1,36 +1,44 @@
# NebulaAuth
* [Русский](README-RU.md)
* [Українська](README-UA.md)
## Description
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Download latest version</a>
</h3>
<h3 align="center">NebulaAuth is an application for emulating actions from the Steam Mobile App. Which replaces your smartphone when operating on Steam. </h3>
<h3 align="center">
NebulaAuth is an application for emulating actions from the Steam Mobile App, replacing your smartphone when operating on Steam.
</h3>
<p align="center">
<sub>NebulaAuth is an independent project and is not affiliated with Valve or Steam.</sub>
</p>
<h4 align="center"><a href="https://t.me/nebulaauth">Official Telegram Group</a></h4>
<h4 align="center"><a href="https://achiefy-project.gitbook.io/nebulaauth">Documentation</a></h4>
## Main advantages
- **Localization in three languages**: English, Russian and Ukrainian.
<p align="center">
<img src="misc/main_window.png" width="600"/>
</p>
- **Localization in six languages**: English, Russian, Ukrainian, Spanish, Turkish and Kazakh.
- **Full functionality of Steam Desktop Authenticator** reimagining [old app](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Proxy support** in all account work processes.
- **Mafile grouping** for improved management.
- **Automatic confirmations of trades/market actions** to save time.
- **Bulk import of .mafiles** via Drag'n'Drop or CTRL+V for convenience.
- **Bulk import of .mafiles** via Drag'n'Drop or Ctrl+V, including SDA-encrypted mafiles with automatic manifest detection.
- **Design customization** to personalize the interface.
- **Ability to confirm account login without entering a code** for easier access.
- **Auto-update** program to use the latest features.
- **Auto-update** with SHA256 checksum verification, changelog viewer and flexible update options.
- **Automatic relogin in case of problems with the session** for continuous operation.
- **Intuitive interface** with tips and conveniences
- **Continious support** of application code and other features.
- **Intuitive interface** with tips and conveniences.
- **Continuous support** of application code and other features.
## Documentation
You can find the documentation for the application [here](https://achiefy-project.gitbook.io/nebulaauth). Currently, only the Russian version is available, but the English version will be released soon.
## Installation
1. If the application does not start, you need to install [.NET Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
@@ -39,40 +47,11 @@
4. Unpack the .zip file to any folder
5. Run the file **NebulaAuth.exe**
## Usage
![gh-main-window-eng](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/15c0e870-1766-43a5-9e8c-2f34d5436beb)
1. Control panel.
- file management and settings
- account management (login, linking, unlinking)
- grouping
- proxy selection
- an indicator with a hint about the proxy used (lit either yellow or red, when hovered it will display additional information)
- timers for automatic confirmation of trade offers/sale offers on the marketplace
- confirmation timer interval (in seconds)
2. List of your accounts
3. Login confirmation code (click to copy)
4. Main confirmation window
5. Search by login or SteamID (7xxxxxxxxxxxxx)
6. Confirm login on another device
7. Hyperlink to the official application page with attribution
## Project Ownership
## Settings
![gh-settings-eng](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/cd704495-d2df-45a7-a73e-40c19410eb72)
NebulaAuth is the original project by Achiefy™.
1. Background mode. Use it if you want to disable default or set custom background of application (put file 'Background.png' to your application folder)
2. Localization language
3. Disable timers when switching between accounts
4. Hide to tray on minimize
5. Indicator with color. Small ellipse on your task-bar icon with custom color. Useful when using multiple windows
6. Custom background color of application
7. Current encryption password. If set you can save encrypted passwords to mafile to help re-login on session troubles. (Not recommended)
8. Legacy mafile mode. Mafile compability mode for another applications (SDA and etc). If checked application will save mafiles with old standart format (Default: checked)
9. Allow auto-update without confirmation
Please keep attribution and do not present modified versions as official.
## [License](/LICENSE.md)
Commercial use prohibited. When redistributing modified code, you must indicate the original authorship.
AGPL-3.0 — see [LICENSE](/LICENSE).
@@ -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>
+78
View File
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<div class="change">
<div class="version">Version 1.8.2</div>
<div class="date">10.02.2026</div>
<div class="description">
<ul>
<li><b>FIX:</b> AutoConfirm timer now correctly resets the status after a successful request (Warning → Ok).</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Changelog</title>
<style>
body { font-family: Segoe UI, sans-serif; background:#eeeeee; padding:20px; }
.change { background:white; padding:25px; border-radius:10px; }
li { margin-bottom:6px; }
</style>
</head>
<body>
<div class="change">
<ul>
<li><b>NEW:</b> Introduced a redesigned update system with a custom update dialog and integrated changelog viewer <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/19">details</a></li>
<li><b>NEW:</b> Added support for importing SDA-encrypted mafiles with automatic manifest detection <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/17">details</a></li>
<li><b>NEW:</b> Grouped market confirmations can now be expanded to reveal individual items <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/18">details</a></li>
<li><b>SECURITY:</b> Added SHA256 checksum verification for downloaded update packages</li>
<li><b>IMPROVEMENT:</b> Export feature now trims input automatically to prevent issues caused by invisible characters or extra spaces</li>
<li><b>IMPROVEMENT:</b> Improved update experience with 'Remind later' and 'Skip version' options</li>
<li><b>IMPROVEMENT:</b> Added visual update indicator and manual 'Check for updates' action</li>
<li><b>IMPROVEMENT:</b> Expanded localization support with Spanish, Turkish and Kazakh languages</li>
<li><b>INFO:</b> Read the full release notes for NebulaAuth 1.8.3 <a href="https://teletype.in/@achies_raw/o-RDwZKZkAU">details</a></li>
</ul>
</div>
</body>
</html>
+46
View File
@@ -0,0 +1,46 @@
{
"version": "1.8.3",
"date": "2026-03-13",
"changes": [
{
"type": "NEW",
"text": "Introduced a redesigned update system with a custom update dialog and integrated changelog viewer",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/19"
},
{
"type": "NEW",
"text": "Added support for importing SDA-encrypted mafiles with automatic manifest detection",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/17"
},
{
"type": "NEW",
"text": "Grouped market confirmations can now be expanded to reveal individual items",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/18"
},
{
"type": "SECURITY",
"text": "Added SHA256 checksum verification for downloaded update packages"
},
{
"type": "IMPROVEMENT",
"text": "Export feature now trims input automatically to prevent issues caused by invisible characters or extra spaces"
},
{
"type": "IMPROVEMENT",
"text": "Improved update experience with 'Remind later' and 'Skip version' options"
},
{
"type": "IMPROVEMENT",
"text": "Added visual update indicator and manual 'Check for updates' action"
},
{
"type": "IMPROVEMENT",
"text": "Expanded localization support with Spanish, Turkish and Kazakh languages"
},
{
"type": "INFO",
"text": "Read the full release notes for NebulaAuth 1.8.3",
"link": "https://teletype.in/@achies_raw/o-RDwZKZkAU"
}
]
}
+28
View File
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Changelog</title>
<style>
body { font-family: Segoe UI, sans-serif; background:#eeeeee; padding:20px; }
.change { background:white; padding:25px; border-radius:10px; }
li { margin-bottom:6px; }
</style>
</head>
<body>
<div class="change">
<ul>
<li><b>FIX:</b> Rebuilt release package to include the missing NebulaAuth.exe file. Version 1.8.4 is functionally identical to 1.8.3 and only fixes the packaging issue that caused the executable to be absent in the previous release.</li>
<li><b>NEW:</b> Introduced a redesigned update system with a custom update dialog and integrated changelog viewer <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/19">details</a></li>
<li><b>NEW:</b> Added support for importing SDA-encrypted mafiles with automatic manifest detection <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/17">details</a></li>
<li><b>NEW:</b> Grouped market confirmations can now be expanded to reveal individual items <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/18">details</a></li>
<li><b>SECURITY:</b> Added SHA256 checksum verification for downloaded update packages</li>
<li><b>IMPROVEMENT:</b> Export feature now trims input automatically to prevent issues caused by invisible characters or extra spaces</li>
<li><b>IMPROVEMENT:</b> Improved update experience with 'Remind later' and 'Skip version' options</li>
<li><b>IMPROVEMENT:</b> Added visual update indicator and manual 'Check for updates' action</li>
<li><b>IMPROVEMENT:</b> Expanded localization support with Spanish, Turkish and Kazakh languages</li>
<li><b>INFO:</b> Read the full release notes for NebulaAuth 1.8.3 <a href="https://teletype.in/@achies_raw/o-RDwZKZkAU">details</a></li>
</ul>
</div>
</body>
</html>
+50
View File
@@ -0,0 +1,50 @@
{
"version": "1.8.4",
"date": "2026-03-14",
"changes": [
{
"type": "FIX",
"text": "Rebuilt release package to include the missing NebulaAuth.exe file. Version 1.8.4 is functionally identical to 1.8.3 and only fixes the packaging issue that caused the executable to be absent in the previous release."
},
{
"type": "NEW",
"text": "Introduced a redesigned update system with a custom update dialog and integrated changelog viewer",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/19"
},
{
"type": "NEW",
"text": "Added support for importing SDA-encrypted mafiles with automatic manifest detection",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/17"
},
{
"type": "NEW",
"text": "Grouped market confirmations can now be expanded to reveal individual items",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/18"
},
{
"type": "SECURITY",
"text": "Added SHA256 checksum verification for downloaded update packages"
},
{
"type": "IMPROVEMENT",
"text": "Export feature now trims input automatically to prevent issues caused by invisible characters or extra spaces"
},
{
"type": "IMPROVEMENT",
"text": "Improved update experience with 'Remind later' and 'Skip version' options"
},
{
"type": "IMPROVEMENT",
"text": "Added visual update indicator and manual 'Check for updates' action"
},
{
"type": "IMPROVEMENT",
"text": "Expanded localization support with Spanish, Turkish and Kazakh languages"
},
{
"type": "INFO",
"text": "Read the full release notes for NebulaAuth 1.8.3",
"link": "https://teletype.in/@achies_raw/o-RDwZKZkAU"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

+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;
}
}

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