mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 22:31:42 +00:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5836ed4508 | |||
| ce55005d44 | |||
| a23d7017a7 | |||
| c879dd462c | |||
| e681ca07f1 | |||
| 9227b383bb | |||
| 0130737789 | |||
| 1ab2f99783 | |||
| da00fa5c96 | |||
| 0a46950ca9 | |||
| 9ac4ffdc34 | |||
| 17635ccba0 | |||
| 69589075e5 | |||
| 4b547e19c4 | |||
| a3804dd48d | |||
| 053faec343 | |||
| b8ac76d2a9 | |||
| b81d45765a | |||
| 372b8c6463 | |||
| 5de26381bb | |||
| 16fd4992f8 | |||
| 7835a53717 | |||
| f15632f2d0 | |||
| b56c0e578d | |||
| 969590d9f2 | |||
| 3e87a0d50e | |||
| 690c98527f | |||
| 5242b0009b | |||
| 1f14bd77e7 | |||
| f2b1cdfcc9 | |||
| ad1df3227b | |||
| 05f132ab1b | |||
| ee858921dc | |||
| 750292bfba | |||
| 982bb4d0c8 | |||
| 8e5fea54cd | |||
| 882d39b8f3 | |||
| ffcc7405a7 | |||
| b3e7436e95 | |||
| d319fc19f9 | |||
| a56757302e | |||
| 1ad30ba49d | |||
| 04e2188437 | |||
| b4c4f52fd1 | |||
| f079488840 | |||
| 4bae3f0250 | |||
| 4c9ec85d6f | |||
| 4761fc70fe | |||
| 3abc12f4bc | |||
| 99982cacb6 | |||
| 344ab81728 | |||
| 2e8862be8b | |||
| 2a641e99ae | |||
| 9a43166d49 | |||
| 3f54df252c | |||
| 2054bd12d5 | |||
| 8ddec3cdfe | |||
| 89d7fcb816 | |||
| 31c38ac8ad |
@@ -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
|
||||
@@ -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
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,58 +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
|
||||
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
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
//}
|
||||
}
|
||||
@@ -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=" " />
|
||||
<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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.6</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>
|
||||
@@ -1,9 +0,0 @@
|
||||
• Есть люди которым нужна функция просмотра и завершения сессий аккаунта. Скорее всего в следующей версии это будет реализовано
|
||||
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
|
||||
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
|
||||
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
|
||||
• Ускорить появление подсказок в интерфейсе
|
||||
• Добавить запоминание пароля при привязке мафайла
|
||||
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
|
||||
• Добавить полное шифрование мафайлов по аналогии с SDA
|
||||
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
|
||||
@@ -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>
|
||||
@@ -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 |
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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='
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>
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для ProxyManagerView.xaml
|
||||
/// </summary>
|
||||
public partial class ProxyManagerView
|
||||
{
|
||||
public ProxyManagerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для SettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class SettingsView
|
||||
{
|
||||
public SettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -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="
Вы используете версию" />
|
||||
<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>
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace NebulaAuth.View;
|
||||
|
||||
/// <summary>
|
||||
/// Логика взаимодействия для UpdaterView.xaml
|
||||
/// </summary>
|
||||
public partial class UpdaterView
|
||||
{
|
||||
public UpdaterView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>1.5.6.0</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.6/NebulaAuth.1.5.6.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.6.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
</item>
|
||||
<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>
|
||||
|
||||
@@ -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**.
|
||||
|
||||
## Использование
|
||||
|
||||

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

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

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

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

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

|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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,256 +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 globalData = await SteamWebApi.GetMarketGlobalInfo(client, cancellationToken);
|
||||
var sessionId = globalData.SessionId;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,29 +0,0 @@
|
||||
using SteamLib.Core;
|
||||
using SteamLib.Exceptions.General;
|
||||
using SteamLib.Web.Models.GlobalMarketInfo;
|
||||
using SteamLib.Web.Scrappers.HTML;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Changelog</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #eeeeee;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.changelog-container {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
margin: 25px auto;
|
||||
max-width: 800px;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.change {
|
||||
margin-bottom: 20px;
|
||||
padding: 0;
|
||||
border-left: 4px solid #a50ec7;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.version {
|
||||
font-weight: 600;
|
||||
font-size: 1.5em;
|
||||
color: #a50ec7;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-style: italic;
|
||||
color: #888;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1em;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.description ul {
|
||||
list-style: inside square;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.changelog-container {
|
||||
width: 90%;
|
||||
margin: 25px auto;
|
||||
padding: 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="changelog-container">
|
||||
<!-- Changelog entry -->
|
||||
<div class="change">
|
||||
<div class="version">Version 1.5.7</div>
|
||||
<div class="date">23.05.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>Removed request to /market/ upon login to prevent 429 error on banned IP</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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 |
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
+4
-4
@@ -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)
|
||||
{
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user