mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 14:24:32 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2daaeb64e | |||
| fd3e131d33 | |||
| f7cc7dcf99 | |||
| 16f0cc2b2d | |||
| cd0416705f | |||
| 2d06cb81f9 | |||
| ed5d3c2eda | |||
| 07eb9f15a9 | |||
| c39660fc55 | |||
| 038f62b4db | |||
| 5029f78247 | |||
| b9452c4baf | |||
| ec71b9881e | |||
| 4b727af1fa | |||
| 9897ca21a6 | |||
| 631068e2f3 | |||
| 0cc9a3d100 | |||
| 7348bc2673 | |||
| 3a9d929c9a | |||
| 25818997af | |||
| 5836ed4508 | |||
| 537d5d907f | |||
| 0e90f1826b | |||
| b5d3b4a8af | |||
| 30cf049f23 | |||
| ce55005d44 | |||
| a23d7017a7 | |||
| c879dd462c | |||
| e681ca07f1 | |||
| 9227b383bb | |||
| a3804dd48d |
@@ -111,10 +111,12 @@ jobs:
|
||||
|
||||
- name: Build NebulaAuth
|
||||
run: |
|
||||
dotnet publish src/NebulaAuth/NebulaAuth.csproj \
|
||||
dotnet publish src/NebulaAuth/NebulaAuth.csproj \
|
||||
-c Release \
|
||||
-o build \
|
||||
-p:EnableWindowsTargeting=true
|
||||
-r win-x64 \
|
||||
--self-contained false \
|
||||
-p:EnableWindowsTargeting=true \
|
||||
-o build
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Package ZIP
|
||||
|
||||
@@ -1,3 +1,314 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
|
||||
<TypePattern DisplayName="Non-reorderable types" Priority="99999999">
|
||||
<TypePattern.Match>
|
||||
<Or>
|
||||
<And>
|
||||
<Kind Is="Interface" />
|
||||
<Or>
|
||||
<HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" />
|
||||
<HasAttribute Name="System.Runtime.InteropServices.ComImport" />
|
||||
</Or>
|
||||
</And>
|
||||
<Kind Is="Struct" />
|
||||
<HasAttribute Name="System.Runtime.InteropServices.StructLayoutAttribute" />
|
||||
<HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" />
|
||||
</Or>
|
||||
</TypePattern.Match>
|
||||
</TypePattern>
|
||||
<TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All">
|
||||
<TypePattern.Match>
|
||||
<And>
|
||||
<Kind Is="Class" />
|
||||
<HasMember>
|
||||
<And>
|
||||
<Kind Is="Method" />
|
||||
<HasAttribute Inherited="True" Name="Xunit.FactAttribute" />
|
||||
<HasAttribute Inherited="True" Name="Xunit.TheoryAttribute" />
|
||||
</And>
|
||||
</HasMember>
|
||||
</And>
|
||||
</TypePattern.Match>
|
||||
<Entry DisplayName="Fields">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Field" />
|
||||
<Not>
|
||||
<Static />
|
||||
</Not>
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Readonly />
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Constructors">
|
||||
<Entry.Match>
|
||||
<Kind Is="Constructor" />
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Static />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Teardown Methods">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Method" />
|
||||
<ImplementsInterface Name="System.IDisposable" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
<Entry DisplayName="All other members" />
|
||||
<Entry DisplayName="Test Methods" Priority="100">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Method" />
|
||||
<HasAttribute Name="Xunit.FactAttribute" />
|
||||
<HasAttribute Name="Xunit.TheoryAttribute" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
</TypePattern>
|
||||
<TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All">
|
||||
<TypePattern.Match>
|
||||
<And>
|
||||
<Kind Is="Class" />
|
||||
<Or>
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureAttribute" />
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureSourceAttribute" />
|
||||
<HasMember>
|
||||
<And>
|
||||
<Kind Is="Method" />
|
||||
<HasAttribute Name="NUnit.Framework.TestAttribute" />
|
||||
<HasAttribute Name="NUnit.Framework.TestCaseAttribute" />
|
||||
<HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" />
|
||||
</And>
|
||||
</HasMember>
|
||||
</Or>
|
||||
</And>
|
||||
</TypePattern.Match>
|
||||
<Entry DisplayName="Setup/Teardown Methods">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Method" />
|
||||
<Or>
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.SetUpAttribute" />
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.TearDownAttribute" />
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureSetUpAttribute" />
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.TestFixtureTearDownAttribute" />
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.OneTimeSetUpAttribute" />
|
||||
<HasAttribute Inherited="True" Name="NUnit.Framework.OneTimeTearDownAttribute" />
|
||||
</Or>
|
||||
</And>
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
<Entry DisplayName="All other members" />
|
||||
<Entry DisplayName="Test Methods" Priority="100">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Method" />
|
||||
<HasAttribute Name="NUnit.Framework.TestAttribute" />
|
||||
<HasAttribute Name="NUnit.Framework.TestCaseAttribute" />
|
||||
<HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
</TypePattern>
|
||||
<TypePattern DisplayName="Grains">
|
||||
<TypePattern.Match>
|
||||
<And>
|
||||
<Or>
|
||||
<ImplementsInterface Name="^Orleans\.IGrain$" />
|
||||
<ImplementsInterface Name="Orleans.IGrainBase" />
|
||||
<ImplementsInterface Name="Achiefy.Orleans.Tenancy.Abstractions.ITenantGrain" />
|
||||
<InheritsFromType Name="Orleans.Grain" />
|
||||
<InheritsFromType Name="Achiefy.Tenant.Application.Stateful.Core.TenantGrain" />
|
||||
</Or>
|
||||
<Not>
|
||||
<Abstract />
|
||||
</Not>
|
||||
<Kind Is="Class" />
|
||||
</And>
|
||||
</TypePattern.Match>
|
||||
<Entry DisplayName="Public Delegates" Priority="100">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Access Is="Public" />
|
||||
<Kind Is="Delegate" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Public Enums" Priority="100">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Access Is="Public" />
|
||||
<Kind Is="Enum" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Nested Records">
|
||||
<Entry.Match>
|
||||
<Kind Is="Record" />
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
<Group DisplayName="Group">
|
||||
<Entry DisplayName="Static Fields and Constants">
|
||||
<Entry.Match>
|
||||
<Or>
|
||||
<Kind Is="Constant" />
|
||||
<And>
|
||||
<Kind Is="Field" />
|
||||
<Static />
|
||||
</And>
|
||||
</Or>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Kind Is="0" Order="Constant Field" />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Properties, Indexers">
|
||||
<Entry.Match>
|
||||
<Or>
|
||||
<Kind Is="Property" />
|
||||
<Kind Is="Indexer" />
|
||||
</Or>
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
<Entry DisplayName="Fields">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Field" />
|
||||
<Not>
|
||||
<Static />
|
||||
</Not>
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Readonly />
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
</Group>
|
||||
<Entry DisplayName="Constructors">
|
||||
<Entry.Match>
|
||||
<Kind Is="Constructor" />
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Static />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Lifecycle Methods">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Method" />
|
||||
<Name Is="^On(Activate|Deactivate)Async.*$" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
<Entry DisplayName="All other members" />
|
||||
<Entry DisplayName="Nested Types">
|
||||
<Entry.Match>
|
||||
<Kind Is="Type" />
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
</TypePattern>
|
||||
<TypePattern DisplayName="Default Pattern">
|
||||
<Entry DisplayName="Public Enums" Priority="100">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Access Is="Public" />
|
||||
<Kind Is="Enum" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Public Delegates" Priority="100">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Access Is="Public" />
|
||||
<Kind Is="Delegate" />
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Nested Records">
|
||||
<Entry.Match>
|
||||
<Kind Is="Record" />
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Static />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Group DisplayName="Group">
|
||||
<Entry DisplayName="Static Fields and Constants">
|
||||
<Entry.Match>
|
||||
<Or>
|
||||
<Kind Is="Constant" />
|
||||
<And>
|
||||
<Kind Is="Field" />
|
||||
<Static />
|
||||
</And>
|
||||
</Or>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Kind Is="0" Order="Constant Field" />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="Properties, Indexers">
|
||||
<Entry.Match>
|
||||
<Or>
|
||||
<Kind Is="Property" />
|
||||
<Kind Is="Indexer" />
|
||||
</Or>
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
<Entry DisplayName="Fields">
|
||||
<Entry.Match>
|
||||
<And>
|
||||
<Kind Is="Field" />
|
||||
<Not>
|
||||
<Static />
|
||||
</Not>
|
||||
</And>
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Readonly />
|
||||
<Name />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
</Group>
|
||||
<Entry DisplayName="Constructors">
|
||||
<Entry.Match>
|
||||
<Kind Is="Constructor" />
|
||||
</Entry.Match>
|
||||
<Entry.SortBy>
|
||||
<Static />
|
||||
</Entry.SortBy>
|
||||
</Entry>
|
||||
<Entry DisplayName="All other members" />
|
||||
<Entry DisplayName="Nested Types">
|
||||
<Entry.Match>
|
||||
<Kind Is="Type" />
|
||||
</Entry.Match>
|
||||
</Entry>
|
||||
</TypePattern>
|
||||
</Patterns></s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=MAAC/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -22,6 +22,7 @@
|
||||
<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" />
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Website": "https://achiefy.pro/?nebula=true",
|
||||
"Documentation": "https://achiefy-project.gitbook.io/nebulaauth"
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>1.8.2</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.2/NebulaAuth.1.8.2.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.2.html</changelog>
|
||||
<item>
|
||||
<version>1.8.5</version>
|
||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.5/NebulaAuth.1.8.5.zip</url>
|
||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.5.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
<checksum algorithm="SHA256">7b2d7768ff5497664c0223a535e35006022d7aed62ebf5400fbf9be3d2893bd5</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 eight languages**: English, Russian, Ukrainian, Simplified Chinese, French, 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).
|
||||
|
||||
+18
-18
@@ -63,25 +63,25 @@
|
||||
</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 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>
|
||||
@@ -63,16 +63,16 @@
|
||||
</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 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,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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!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> Bulk proxy assignment: assign proxies to multiple accounts at once via flexible login:proxy, login:ID or login-only input <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22">details</a></li>
|
||||
<li><b>NEW:</b> Import dialog now allows assigning imported mafiles to a group directly; added option to skip confirmation when no conflicts are detected <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/24">details</a></li>
|
||||
<li><b>NEW:</b> Assign random free proxy to the account from context menu <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22">details</a></li>
|
||||
<li><b>NEW:</b> Export: added ZIP archive output, export-all mode when the input field is empty, and support for login:password input format <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/28">details</a></li>
|
||||
<li><b>IMPROVEMENT:</b> When a session permanently expires during a user-initiated action, a login dialog is now shown instead of a generic error notification <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/29">details</a></li>
|
||||
<li><b>IMPROVEMENT:</b> Proxy manager: account count badges now show how many accounts are assigned to each proxy entry <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22">details</a></li>
|
||||
<li><b>IMPROVEMENT:</b> Added login:pass@ip:port proxy format support <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/25">details</a></li>
|
||||
<li><b>IMPROVEMENT:</b> Improved dialog header design <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/27">details</a></li>
|
||||
<li><b>IMPROVEMENT:</b> Website and Documentation links are now available from the links window <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/30">details</a></li>
|
||||
<li><b>FIX:</b> Application no longer fails to start when Steam time synchronization is unavailable (network issues, firewall)</li>
|
||||
<li><b>FIX:</b> Fixed incorrect Steam Guard error message shown in the Mafile Mover dialog <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/23">details</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"version": "1.8.5",
|
||||
"date": "2026-06-19",
|
||||
"changes": [
|
||||
{
|
||||
"type": "NEW",
|
||||
"text": "Bulk proxy assignment: assign proxies to multiple accounts at once via flexible login:proxy, login:ID or login-only input",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22"
|
||||
},
|
||||
{
|
||||
"type": "NEW",
|
||||
"text": "Import dialog now allows assigning imported mafiles to a group directly; added option to skip confirmation when no conflicts are detected",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/24"
|
||||
},
|
||||
{
|
||||
"type": "NEW",
|
||||
"text": "Assign random free proxy to the account from context menu",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22"
|
||||
},
|
||||
{
|
||||
"type": "NEW",
|
||||
"text": "Export: added ZIP archive output, export-all mode when the input field is empty, and support for login:password input format",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/28"
|
||||
},
|
||||
{
|
||||
"type": "IMPROVEMENT",
|
||||
"text": "When a session permanently expires during a user-initiated action, a login dialog is now shown instead of a generic error notification",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/29"
|
||||
},
|
||||
{
|
||||
"type": "IMPROVEMENT",
|
||||
"text": "Proxy manager: account count badges now show how many accounts are assigned to each proxy entry",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/22"
|
||||
},
|
||||
{
|
||||
"type": "IMPROVEMENT",
|
||||
"text": "Added login:pass@ip:port proxy format support",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/25"
|
||||
},
|
||||
{
|
||||
"type": "IMPROVEMENT",
|
||||
"text": "Improved dialog header design",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/27"
|
||||
},
|
||||
{
|
||||
"type": "IMPROVEMENT",
|
||||
"text": "Website and Documentation links are now available from the links window",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/30"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"text": "Application no longer fails to start when Steam time synchronization is unavailable (network issues, firewall)"
|
||||
},
|
||||
{
|
||||
"type": "FIX",
|
||||
"text": "Fixed incorrect Steam Guard error message shown in the Mafile Mover dialog",
|
||||
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/23"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 335 KiB |
@@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,49 @@
|
||||
<UserControl x:Class="NebulaAuth.DialogHeader"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
x:Name="Root">
|
||||
<StackPanel>
|
||||
<Border Background="{DynamicResource Base300Brush}" Padding="10,6,10,6">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon
|
||||
Kind="{Binding IconKind, ElementName=Root}"
|
||||
Width="20" Height="20" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}">
|
||||
<materialDesign:PackIcon.Style>
|
||||
<Style TargetType="materialDesign:PackIcon">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Kind" Value="None">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</materialDesign:PackIcon.Style>
|
||||
</materialDesign:PackIcon>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}"
|
||||
FontSize="18">
|
||||
<Run Text="{Binding Title, ElementName=Root}" />
|
||||
<Run FontWeight="Bold" Text="{Binding TitleAccent, ElementName=Root}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30"
|
||||
Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right"
|
||||
IsEnabled="{Binding IsCloseEnabled, ElementName=Root}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
||||
CommandParameter="{Binding CloseParameter, ElementName=Root}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
|
||||
namespace NebulaAuth;
|
||||
|
||||
public partial class DialogHeader : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty IconKindProperty =
|
||||
DependencyProperty.Register(nameof(IconKind), typeof(PackIconKind), typeof(DialogHeader),
|
||||
new PropertyMetadata(PackIconKind.None));
|
||||
|
||||
public static readonly DependencyProperty TitleProperty =
|
||||
DependencyProperty.Register(nameof(Title), typeof(string), typeof(DialogHeader),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty TitleAccentProperty =
|
||||
DependencyProperty.Register(nameof(TitleAccent), typeof(string), typeof(DialogHeader),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty CloseParameterProperty =
|
||||
DependencyProperty.Register(nameof(CloseParameter), typeof(object), typeof(DialogHeader),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty IsCloseEnabledProperty =
|
||||
DependencyProperty.Register(nameof(IsCloseEnabled), typeof(bool), typeof(DialogHeader),
|
||||
new PropertyMetadata(true));
|
||||
|
||||
public PackIconKind IconKind
|
||||
{
|
||||
get => (PackIconKind) GetValue(IconKindProperty);
|
||||
set => SetValue(IconKindProperty, value);
|
||||
}
|
||||
|
||||
public string? Title
|
||||
{
|
||||
get => (string?) GetValue(TitleProperty);
|
||||
set => SetValue(TitleProperty, value);
|
||||
}
|
||||
|
||||
public string? TitleAccent
|
||||
{
|
||||
get => (string?) GetValue(TitleAccentProperty);
|
||||
set => SetValue(TitleAccentProperty, value);
|
||||
}
|
||||
|
||||
public object? CloseParameter
|
||||
{
|
||||
get => GetValue(CloseParameterProperty);
|
||||
set => SetValue(CloseParameterProperty, value);
|
||||
}
|
||||
|
||||
public bool IsCloseEnabled
|
||||
{
|
||||
get => (bool) GetValue(IsCloseEnabledProperty);
|
||||
set => SetValue(IsCloseEnabledProperty, value);
|
||||
}
|
||||
|
||||
public DialogHeader()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,10 @@ public partial class HintBox : UserControl
|
||||
IconKind = PackIconKind.ErrorOutline;
|
||||
IconBrush = (Brush) Application.Current.FindResource("ErrorBrush")!;
|
||||
break;
|
||||
case HintBoxSeverity.Warning:
|
||||
IconKind = PackIconKind.WarningOutline;
|
||||
IconBrush = (Brush) Application.Current.FindResource("WarningBrush")!;
|
||||
break;
|
||||
default:
|
||||
IconKind = PackIconKind.InfoCircleOutline;
|
||||
IconBrush = (Brush) Application.Current.FindResource("InfoBrush")!;
|
||||
@@ -94,5 +98,6 @@ public partial class HintBox : UserControl
|
||||
public enum HintBoxSeverity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
<converters:BoolToMafileNamingConverter x:Key="BoolToMafileNamingConverter" />
|
||||
<converters:BoolToValueConverter x:Key="BoolToValueConverter" />
|
||||
<converters:NullableToBooleanConverter x:Key="NullableToBooleanConverter" />
|
||||
<converters:ProxyAccountCountConverter x:Key="ProxyAccountCountConverter" />
|
||||
<!-- Background converters-->
|
||||
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
|
||||
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter" />
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using NebulaAuth.Model;
|
||||
|
||||
namespace NebulaAuth.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a proxy ID (int) to the number of accounts assigned to it.
|
||||
/// Returns empty string when count is zero so the badge is invisible.
|
||||
/// </summary>
|
||||
public class ProxyAccountCountConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not int proxyId) return string.Empty;
|
||||
var count = ProxyAssignmentCache.GetAccountCount(proxyId);
|
||||
return count > 0 ? count.ToString() : string.Empty;
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,15 @@ namespace NebulaAuth.Core;
|
||||
|
||||
public static class DialogsController
|
||||
{
|
||||
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null)
|
||||
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username, string? currentPassword = null,
|
||||
bool showNoProxyWarning = false)
|
||||
{
|
||||
var vm = new LoginAgainVM
|
||||
{
|
||||
UserName = username,
|
||||
Password = currentPassword ?? string.Empty,
|
||||
SavePassword = PHandler.IsPasswordSet
|
||||
SavePassword = PHandler.IsPasswordSet,
|
||||
ShowNoProxyWarning = showNoProxyWarning
|
||||
};
|
||||
var content = new LoginAgainDialog
|
||||
{
|
||||
@@ -73,6 +75,22 @@ public static class DialogsController
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<MafileImportDialogResult?> ShowMafileImportDialog(IEnumerable<string> groups,
|
||||
int totalCount, int conflictCount)
|
||||
{
|
||||
var vm = new MafileImportDialogVM(groups, totalCount, conflictCount);
|
||||
var content = new MafileImportDialog
|
||||
{
|
||||
DataContext = vm
|
||||
};
|
||||
var result = await DialogHost.Show(content);
|
||||
return result is MafileImportDialogVM
|
||||
? new MafileImportDialogResult(
|
||||
!vm.AddToGroup || string.IsNullOrWhiteSpace(vm.Group) ? null : vm.Group.Trim(),
|
||||
vm.OverwriteConflicts)
|
||||
: null;
|
||||
}
|
||||
|
||||
public static async Task<bool> ShowProxyManager()
|
||||
{
|
||||
var vm = new ProxyManagerVM();
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using NebulaAuth.Model;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NebulaAuth.Core;
|
||||
|
||||
public static class LinksManager
|
||||
{
|
||||
private const string LINKS_URL =
|
||||
"https://raw.githubusercontent.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/master/NebulaAuth/links.json";
|
||||
|
||||
private static readonly HttpClient HttpClient = new();
|
||||
|
||||
public static string? WebsiteUrl { get; private set; }
|
||||
public static string? DocumentationUrl { get; private set; }
|
||||
|
||||
public static async Task FetchAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await HttpClient.GetStringAsync(LINKS_URL).ConfigureAwait(false);
|
||||
var links = JsonConvert.DeserializeObject<RemoteLinks>(json);
|
||||
WebsiteUrl = links?.Website;
|
||||
DocumentationUrl = links?.Documentation;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shell.Logger.Debug(ex, "Failed to fetch remote links");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,16 @@
|
||||
"es": "Grupo",
|
||||
"tr": "Grup",
|
||||
"kk": "Топ"
|
||||
},
|
||||
"ExportToZip": {
|
||||
"ru": "Экспортировать в ZIP архив",
|
||||
"en": "Export to ZIP archive",
|
||||
"uk": "Експортувати в ZIP архів",
|
||||
"fr": "Exporter en archive ZIP",
|
||||
"zh": "导出为 ZIP 压缩包",
|
||||
"es": "Exportar a archivo ZIP",
|
||||
"tr": "ZIP arşivine aktar",
|
||||
"kk": "ZIP мұрағатқа экспорттау"
|
||||
}
|
||||
},
|
||||
"Tooltips": {
|
||||
@@ -288,17 +298,27 @@
|
||||
"es": "Incluir datos de grupo compatibles con Nebula.",
|
||||
"tr": "Nebula uyumlu grup verilerini dahil et.",
|
||||
"kk": "Nebula үйлесімді топ деректерін қосу."
|
||||
},
|
||||
"ExportToZip": {
|
||||
"ru": "Экспортировать в ZIP архив вместо папки. Архив будет сохранён в выбранной директории с именем вида N_timestamp.zip.",
|
||||
"en": "Export to a ZIP archive instead of a folder. The archive will be saved in the selected directory as N_timestamp.zip.",
|
||||
"uk": "Експортувати в ZIP архів замість папки. Архів буде збережено у вибраній директорії з назвою N_timestamp.zip.",
|
||||
"fr": "Exporter dans une archive ZIP plutôt que dans un dossier. L'archive sera enregistrée dans le répertoire sélectionné sous le nom N_timestamp.zip.",
|
||||
"zh": "导出为 ZIP 压缩包而非文件夹。压缩包将保存在所选目录中,名称格式为 N_timestamp.zip。",
|
||||
"es": "Exportar a un archivo ZIP en lugar de una carpeta. El archivo se guardará en el directorio seleccionado como N_timestamp.zip.",
|
||||
"tr": "Klasör yerine ZIP arşivine aktar. Arşiv, seçilen dizine N_timestamp.zip adıyla kaydedilecektir.",
|
||||
"kk": "Қалта орнына ZIP мұрағатына экспорттау. Мұрағат таңдалған каталогта N_timestamp.zip атауымен сақталады."
|
||||
}
|
||||
},
|
||||
"ExportAccountsPlaceholder": {
|
||||
"ru": "Введите логины аккаунтов или SteamID (по одному на строку) для экспорта в выбранную директорию.",
|
||||
"en": "Enter account logins or SteamIDs (one per line) to export to the selected directory.",
|
||||
"uk": "Введіть логіни акаунтів або SteamID (по одному на рядок) для експорту у вибрану директорію.",
|
||||
"fr": "Entrez les identifiants de compte ou les SteamID (un par ligne) pour exporter vers le répertoire sélectionné.",
|
||||
"zh": "输入要导出到所选目录的账户登录名或 SteamID(每行一个)。",
|
||||
"es": "Introduce los logins de las cuentas o SteamID (uno por línea) para exportarlos al directorio seleccionado.",
|
||||
"tr": "Seçilen dizine dışa aktarmak için hesap girişlerini veya SteamID’leri (her satıra bir tane) girin.",
|
||||
"kk": "Таңдалған каталогқа экспорттау үшін аккаунт логиндерін немесе SteamID (әр жолға біреуден) енгізіңіз."
|
||||
"ru": "Введите логины или SteamID (по одному на строку). Поддерживается формат логин:пароль — пароль будет проигнорирован. Оставьте пустым для экспорта всех аккаунтов.",
|
||||
"en": "Enter logins or SteamIDs (one per line). Supports login:password format — the password will be ignored. Leave empty to export all accounts.",
|
||||
"uk": "Введіть логіни або SteamID (по одному на рядок). Підтримується формат логін:пароль — пароль буде проігноровано. Залиште порожнім для експорту всіх акаунтів.",
|
||||
"fr": "Entrez les identifiants ou SteamID (un par ligne). Supporte le format login:mot_de_passe — le mot de passe sera ignoré. Laissez vide pour exporter tous les comptes.",
|
||||
"zh": "输入登录名或 SteamID(每行一个)。支持 login:password 格式——密码将被忽略。留空则导出所有账户。",
|
||||
"es": "Introduce logins o SteamID (uno por línea). Soporta el formato login:contraseña — la contraseña será ignorada. Déjalo vacío para exportar todas las cuentas.",
|
||||
"tr": "Girişleri veya SteamID’leri girin (her satıra bir tane). login:şifre formatı desteklenir — şifre yok sayılır. Tüm hesapları dışa aktarmak için boş bırakın.",
|
||||
"kk": "Логиндерді немесе SteamID (әр жолға біреуден) енгізіңіз. логин:құпиясөз форматы қолданылады — құпиясөз еленбейді. Барлық аккаунттарды экспорттау үшін бос қалдырыңыз."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,5 +35,67 @@
|
||||
"tr": "Değer girin",
|
||||
"kk": "Мән енгізіңіз"
|
||||
}
|
||||
},
|
||||
"MafileImportDialog": {
|
||||
"Title": {
|
||||
"en": "Mafile import",
|
||||
"ru": "Импорт мафайлов",
|
||||
"zh": "导入 mafile",
|
||||
"uk": "Імпорт mafile",
|
||||
"fr": "Import de mafiles",
|
||||
"es": "Importación de mafiles",
|
||||
"tr": "Mafile içe aktarma",
|
||||
"kk": "Mafile импорттау"
|
||||
},
|
||||
"TotalCount": {
|
||||
"en": "Mafiles to import",
|
||||
"ru": "Мафайлов к импорту",
|
||||
"zh": "要导入的 mafile:",
|
||||
"uk": "Mafile до імпорту:",
|
||||
"fr": "Mafiles à importer :",
|
||||
"es": "Mafiles para importar:",
|
||||
"tr": "İçe aktarılacak mafile:",
|
||||
"kk": "Импортталатын mafile:"
|
||||
},
|
||||
"ConflictCount": {
|
||||
"en": "Already exist",
|
||||
"ru": "Уже существуют",
|
||||
"zh": "已存在:",
|
||||
"uk": "Уже існують:",
|
||||
"fr": "Déjà existants :",
|
||||
"es": "Ya existen:",
|
||||
"tr": "Zaten mevcut:",
|
||||
"kk": "Бұрыннан бар:"
|
||||
},
|
||||
"OverwriteConflicts": {
|
||||
"en": "Overwrite conflicting mafiles",
|
||||
"ru": "Перезаписать конфликтующие мафайлы",
|
||||
"zh": "覆盖冲突的 mafile",
|
||||
"uk": "Перезаписати конфліктні mafile",
|
||||
"fr": "Remplacer les mafiles en conflit",
|
||||
"es": "Sobrescribir mafiles en conflicto",
|
||||
"tr": "Çakışan mafile'ların üzerine yaz",
|
||||
"kk": "Қақтығысатын mafile файлдарын қайта жазу"
|
||||
},
|
||||
"AddToGroup": {
|
||||
"en": "Add imported accounts to a group",
|
||||
"ru": "Добавить импортированные аккаунты в группу",
|
||||
"zh": "将导入的账号添加到组",
|
||||
"uk": "Додати імпортовані акаунти до групи",
|
||||
"fr": "Ajouter les comptes importés à un groupe",
|
||||
"es": "Añadir las cuentas importadas a un grupo",
|
||||
"tr": "İçe aktarılan hesapları bir gruba ekle",
|
||||
"kk": "Импортталған аккаунттарды топқа қосу"
|
||||
},
|
||||
"GroupFieldHint": {
|
||||
"en": "Existing or new group",
|
||||
"ru": "Существующая или новая группа",
|
||||
"zh": "现有或新组",
|
||||
"uk": "Наявна або нова група",
|
||||
"fr": "Groupe existant ou nouveau",
|
||||
"es": "Grupo existente o nuevo",
|
||||
"tr": "Mevcut veya yeni grup",
|
||||
"kk": "Бар немесе жаңа топ"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,16 @@
|
||||
"es": "Pulsa 'DEL' para eliminar",
|
||||
"tr": "Silmek için 'DEL' tuşuna bas",
|
||||
"kk": "Жою үшін 'DEL' пернесін басыңыз"
|
||||
},
|
||||
"NoProxyWarning": {
|
||||
"en": "No proxy is assigned",
|
||||
"ru": "Прокси не установлен",
|
||||
"zh": "未分配代理",
|
||||
"uk": "Проксі не призначено",
|
||||
"fr": "Aucun proxy assigné",
|
||||
"es": "No hay ningún proxy asignado",
|
||||
"tr": "Proxy atanmamış",
|
||||
"kk": "Прокси тағайындалмаған"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,14 +42,14 @@
|
||||
"kk": "RCode: {0}\r\nФайл атауы: {1}.mafile"
|
||||
},
|
||||
"GuardIsNotActive": {
|
||||
"ru": "Steam Guard установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
|
||||
"en": "Steam Guard is set on this account. To link mafile to the account, use the 'Link' window",
|
||||
"zh": "此账户已启用令牌。要将 mafile 绑定到此账户,请使用“绑定”窗口。",
|
||||
"uk": "Steam Guard встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'",
|
||||
"fr": "Steam Guard est configuré sur ce compte. Pour lier mafile au compte, utilisez la fenêtre 'Lien'",
|
||||
"es": "Steam Guard está activado en esta cuenta. Para vincular el mafile a la cuenta, usa la ventana 'Vincular'",
|
||||
"tr": "Steam Guard bu hesapta zaten etkin. Mafile'ı hesaba bağlamak için 'Bağla' penceresini kullanın",
|
||||
"kk": "Бұл аккаунтта Steam Guard орнатылған. Mafile-ды аккаунтқа байланыстыру үшін 'Байланыстыру' терезесін пайдаланыңыз"
|
||||
"ru": "Steam Guard НЕ установлен на данном аккаунте. Чтобы привязать mafile к аккаунту, воспользуйтесь окном \"Привязать\"",
|
||||
"en": "Steam Guard is NOT set on this account. To link mafile to the account, use the 'Link' window",
|
||||
"zh": "此账户尚未启用令牌。要将 mafile 绑定到此账户,请使用“绑定”窗口。",
|
||||
"uk": "Steam Guard НЕ встановлено на цьому акаунті. Щоб прив'язати mafile до акаунту, скористайтеся вікном 'Прив'язати'",
|
||||
"fr": "Steam Guard n'est PAS configuré sur ce compte. Pour lier le mafile au compte, utilisez la fenêtre 'Lier'",
|
||||
"es": "Steam Guard NO está activado en esta cuenta. Para vincular el mafile a la cuenta, usa la ventana 'Vincular'",
|
||||
"tr": "Steam Guard bu hesapta etkin DEĞİL. Mafile'ı hesaba bağlamak için 'Bağla' penceresini kullanın",
|
||||
"kk": "Бұл аккаунтта Steam Guard орнатылмаған. Mafile-ды аккаунтқа байланыстыру үшін 'Байланыстыру' терезесін пайдаланыңыз"
|
||||
},
|
||||
"SeemsNoPhoneNumber": {
|
||||
"en": "The account has no phone number linked, returned Fail code (2)",
|
||||
|
||||
@@ -40,6 +40,26 @@
|
||||
"es": "El proxy tiene un formato incorrecto",
|
||||
"tr": "Proxy yanlış formatta",
|
||||
"kk": "Прокси қате форматта"
|
||||
},
|
||||
"AssignmentSuccess": {
|
||||
"ru": "Прокси назначены: {0}",
|
||||
"en": "Assigned: {0}",
|
||||
"zh": "已分配:{0}",
|
||||
"uk": "Проксі призначено: {0}",
|
||||
"fr": "Attribués: {0}",
|
||||
"es": "Asignados: {0}",
|
||||
"tr": "Atandı: {0}",
|
||||
"kk": "Тағайындалды: {0}"
|
||||
},
|
||||
"AssignmentPartialSuccess": {
|
||||
"ru": "Назначено: {0}, не найдено: {1}, ошибки: {2}",
|
||||
"en": "Assigned: {0}, not found: {1}, errors: {2}",
|
||||
"zh": "已分配:{0},未找到:{1},错误:{2}",
|
||||
"uk": "Призначено: {0}, не знайдено: {1}, помилки: {2}",
|
||||
"fr": "Attribués: {0}, non trouvés: {1}, erreurs: {2}",
|
||||
"es": "Asignados: {0}, no encontrados: {1}, errores: {2}",
|
||||
"tr": "Atandı: {0}, bulunamadı: {1}, hatalar: {2}",
|
||||
"kk": "Тағайындалды: {0}, табылмады: {1}, қателер: {2}"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -93,6 +113,96 @@
|
||||
"es": "Mostrar credenciales",
|
||||
"tr": "Kimlik bilgilerini göster",
|
||||
"kk": "Тіркелгі деректерін көрсету"
|
||||
},
|
||||
"TabManager": {
|
||||
"en": "Manager",
|
||||
"ru": "Менеджер",
|
||||
"zh": "管理",
|
||||
"uk": "Менеджер",
|
||||
"fr": "Gestionnaire",
|
||||
"es": "Administrador",
|
||||
"tr": "Yönetici",
|
||||
"kk": "Менеджер"
|
||||
},
|
||||
"TabAssignment": {
|
||||
"en": "Assignment",
|
||||
"ru": "Назначение",
|
||||
"zh": "分配",
|
||||
"uk": "Призначення",
|
||||
"fr": "Attribution",
|
||||
"es": "Asignación",
|
||||
"tr": "Atama",
|
||||
"kk": "Тағайындау"
|
||||
},
|
||||
"TotalAccounts": {
|
||||
"en": "Accounts",
|
||||
"ru": "Аккаунтов",
|
||||
"zh": "账户",
|
||||
"uk": "Акаунтів",
|
||||
"fr": "Comptes",
|
||||
"es": "Cuentas",
|
||||
"tr": "Hesaplar",
|
||||
"kk": "Аккаунттар"
|
||||
},
|
||||
"TotalProxies": {
|
||||
"en": "Proxies",
|
||||
"ru": "Прокси",
|
||||
"zh": "代理",
|
||||
"uk": "Проксі",
|
||||
"fr": "Proxies",
|
||||
"es": "Proxies",
|
||||
"tr": "Proxy'ler",
|
||||
"kk": "Проксилер"
|
||||
},
|
||||
"AssignedProxies": {
|
||||
"en": "Assigned",
|
||||
"ru": "Назначено",
|
||||
"zh": "已分配",
|
||||
"uk": "Призначено",
|
||||
"fr": "Attribués",
|
||||
"es": "Asignados",
|
||||
"tr": "Atandı",
|
||||
"kk": "Тағайындалды"
|
||||
},
|
||||
"UnspecifiedBehavior": {
|
||||
"en": "Unspecified proxy:",
|
||||
"ru": "Не указанный прокси:",
|
||||
"zh": "未指定代理:",
|
||||
"uk": "Не вказаний проксі:",
|
||||
"fr": "Proxy non spécifié:",
|
||||
"es": "Proxy no especificado:",
|
||||
"tr": "Belirtilmemiş proxy:",
|
||||
"kk": "Көрсетілмеген прокси:"
|
||||
},
|
||||
"UnspecifiedRandom": {
|
||||
"en": "Assign random",
|
||||
"ru": "Назначить случайный",
|
||||
"zh": "随机分配",
|
||||
"uk": "Призначити випадковий",
|
||||
"fr": "Attribuer aléatoirement",
|
||||
"es": "Asignar aleatorio",
|
||||
"tr": "Rastgele ata",
|
||||
"kk": "Кездейсоқ тағайындау"
|
||||
},
|
||||
"UnspecifiedRemove": {
|
||||
"en": "Remove proxy",
|
||||
"ru": "Удалить прокси",
|
||||
"zh": "删除代理",
|
||||
"uk": "Видалити проксі",
|
||||
"fr": "Supprimer le proxy",
|
||||
"es": "Eliminar proxy",
|
||||
"tr": "Proxy'yi kaldır",
|
||||
"kk": "Проксиді жою"
|
||||
},
|
||||
"ApplyAssignment": {
|
||||
"en": "Apply",
|
||||
"ru": "Применить",
|
||||
"zh": "应用",
|
||||
"uk": "Застосувати",
|
||||
"fr": "Appliquer",
|
||||
"es": "Aplicar",
|
||||
"tr": "Uygula",
|
||||
"kk": "Қолдану"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,25 +262,25 @@
|
||||
"kk": "Дискке сақталмайды"
|
||||
}
|
||||
},
|
||||
"LegacyMafileMode": {
|
||||
"en": "Common mafiles format",
|
||||
"ru": "Общепринятый формат мафайлов",
|
||||
"zh": "常见的邮件文件格式",
|
||||
"uk": "Загальноприйнятий формат мафайлів",
|
||||
"fr": "Format de mafiles commun",
|
||||
"es": "Formato común de mafiles",
|
||||
"tr": "Ortak mafile formatı",
|
||||
"kk": "Жалпы mafile форматы"
|
||||
"ConfirmMafileImport": {
|
||||
"en": "Ask confirmation when importing mafiles",
|
||||
"ru": "Спрашивать подтверждение при импорте мафайлов",
|
||||
"zh": "导入 mafile 时请求确认",
|
||||
"uk": "Запитувати підтвердження під час імпорту mafile",
|
||||
"fr": "Demander confirmation lors de l'import des mafiles",
|
||||
"es": "Pedir confirmación al importar mafiles",
|
||||
"tr": "Mafile içe aktarırken onay sor",
|
||||
"kk": "Mafile импорттау кезінде растауды сұрау"
|
||||
},
|
||||
"LegacyMafileModeHint": {
|
||||
"en": "Save mafiles in compatibility format with Steam Desktop Authenticator and other applications",
|
||||
"ru": "Сохранять мафайлы в старом формате для совместимости со Steam Desktop Authenticator и другими приложениями",
|
||||
"zh": "以与 Steam 桌面认证器及其他应用程序兼容的格式保存 mafiles",
|
||||
"uk": "Зберігати мафайли у старому форматі для сумісності зі Steam Desktop Authenticator та іншими додатками",
|
||||
"fr": "Enregistrer les mafiles dans un format compatible avec Steam Desktop Authenticator et d'autres applications",
|
||||
"es": "Guardar mafiles en formato compatible con Steam Desktop Authenticator y otras aplicaciones",
|
||||
"tr": "Mafile'ları Steam Desktop Authenticator ve diğer uygulamalarla uyumlu formatta kaydet",
|
||||
"kk": "Mafile файлдарын Steam Desktop Authenticator және басқа қолданбалармен үйлесімді форматта сақтау"
|
||||
"ConfirmMafileImportHint": {
|
||||
"en": "Allows selecting a group when importing maFiles. Does not affect showing the dialog when file conflicts occur",
|
||||
"ru": "Позволяет выбрать группу при импорте maFiles. Не влияет на показ окна при конфликте файлов",
|
||||
"zh": "允许在导入 maFiles 时选择分组。不影响发生文件冲突时窗口的显示",
|
||||
"uk": "Дозволяє вибрати групу під час імпорту maFiles. Не впливає на показ вікна при конфлікті файлів",
|
||||
"fr": "Permet de choisir un groupe lors de l'import des maFiles. N'affecte pas l'affichage de la fenêtre en cas de conflit de fichiers",
|
||||
"es": "Permite seleccionar un grupo al importar maFiles. No afecta a la aparición de la ventana cuando hay conflictos de archivos",
|
||||
"tr": "maFiles içe aktarılırken grup seçilmesine izin verir. Dosya çakışmalarında pencerenin gösterilmesini etkilemez",
|
||||
"kk": "maFiles импорттау кезінде топ таңдауға мүмкіндік береді. Файл қайшылықтары болғанда терезенің көрсетілуіне әсер етпейді"
|
||||
},
|
||||
"AllowAutoUpdate": {
|
||||
"en": "Allow auto update",
|
||||
@@ -333,4 +333,4 @@
|
||||
"kk": "Steam техникалық жұмыстар жүргізген кезде әр сейсенбіде (GMT бойынша сәрсенбі түні) пайда болатын және авторастау таймерлерін қажетсіз өшіретін қателерді елемеу"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,16 +101,6 @@
|
||||
"tr": "Onay hatası",
|
||||
"kk": "Растау қатесі"
|
||||
},
|
||||
"ConfirmMafileOverwrite": {
|
||||
"ru": "Внимание, мафайл(-ы) уже присутствует в папке mafiles.\r\nПерезаписать мафайлы, которые конфликтуют?",
|
||||
"en": "Attention, mafile(-s) already exists in mafiles folder.\r\nOverwrite conflicting mafiles?",
|
||||
"zh": "注意,mafile 已经存在于 mafiles 文件夹中。\r\n覆盖冲突的 mafiles 吗?",
|
||||
"uk": "Увага, мафайл(-и) вже присутній у папці mafiles.\r\nПерезаписати мафайли, які конфліктують?",
|
||||
"fr": "Attention, mafiles déjà présents dans le dossier mafiles.\r\nRemplacer les mafiles en conflit?",
|
||||
"es": "Atención, los mafiles ya existen en la carpeta mafiles.\r\n¿Sobrescribir los mafiles en conflicto?",
|
||||
"tr": "Dikkat, mafile(ler) zaten mafiles klasöründe mevcut.\r\nÇakışan mafile'lar üzerine yazılsın mı?",
|
||||
"kk": "Назар аударыңыз, mafile(дер) mafiles қалтасында бар.\r\nҚайшылық тудырған mafile файлдарының үстінен жазу керек пе?"
|
||||
},
|
||||
"MafileImportError": {
|
||||
"ru": "Ошибка при импорте мафайла",
|
||||
"en": "Mafile import error",
|
||||
@@ -904,6 +894,16 @@
|
||||
"es": "Desvincular proxy",
|
||||
"tr": "Proxy bağlantısını kaldır",
|
||||
"kk": "Проксиді ажырату"
|
||||
},
|
||||
"AssignFreeProxy": {
|
||||
"en": "Assign free proxy",
|
||||
"ru": "Назначить свободный прокси",
|
||||
"zh": "分配空闲代理",
|
||||
"uk": "Призначити вільний проксі",
|
||||
"fr": "Attribuer un proxy libre",
|
||||
"es": "Asignar proxy libre",
|
||||
"tr": "Serbest proxy ata",
|
||||
"kk": "Бос проксиді тағайындау"
|
||||
}
|
||||
},
|
||||
"Proxy": {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
DialogClosed="DialogHost_DialogClosed"
|
||||
ApplyBlurBackground="{Binding Settings.ApplyBlurBackground}"
|
||||
BlurRadius="12"
|
||||
DialogContentUniformCornerRadius="12"
|
||||
x:Name="DialogHostInstance">
|
||||
|
||||
<Grid ZIndex="0">
|
||||
@@ -420,6 +421,9 @@
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.UnattachProxy}"
|
||||
Command="{Binding RemoveProxyCommand}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AssignFreeProxy}"
|
||||
Command="{Binding AssignFreeProxyCommand}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||
</ContextMenu>
|
||||
</FrameworkElement.ContextMenu>
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public static class MAACRequestHandler
|
||||
{
|
||||
return withSessionHandler
|
||||
? Result<T>.Success(await SessionHandler.Handle(req, client.Mafile, client.Chp(),
|
||||
GetTimerPrefix(client.Mafile)))
|
||||
GetTimerPrefix(client.Mafile), false))
|
||||
: Result<T>.Success(await req());
|
||||
}
|
||||
catch (SessionInvalidException ex)
|
||||
|
||||
@@ -62,7 +62,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable
|
||||
SetStatus(newStatus);
|
||||
|
||||
ClientHandler.CookieContainer.ClearAllCookies();
|
||||
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData);
|
||||
ClientHandler.CookieContainer.SetSteamMobileCookies(sessionData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public static class MaClient
|
||||
{
|
||||
ClientHandler.CookieContainer.ClearAllCookies();
|
||||
if (account == null) return;
|
||||
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(account.SessionData);
|
||||
ClientHandler.CookieContainer.SetSteamMobileCookies(account.SessionData);
|
||||
Proxy.SetData(account.Proxy?.Data);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,4 +13,5 @@ public class MafileExportTemplate
|
||||
public bool IncludeNebulaPassword { get; set; }
|
||||
public bool IncludeNebulaGroup { get; set; }
|
||||
public string? Path { get; set; }
|
||||
public bool ExportToZip { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AchiesUtilities.Extensions;
|
||||
using NebulaAuth.Core;
|
||||
@@ -36,48 +39,79 @@ public static class MafileExporter
|
||||
return new ExportResult(LocManager.GetCodeBehindOrDefault("AccessDenied", "MafileExporter.AccessDenied"));
|
||||
}
|
||||
|
||||
|
||||
var exported = new Dictionary<Mafile, string>();
|
||||
var notFound = new List<string>();
|
||||
var conflict = new List<string>();
|
||||
|
||||
var toExport = new List<(string key, Mafile mafile)>();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
SteamId? steamId = null;
|
||||
if (SteamId64.TryParse(key, out var id64))
|
||||
{
|
||||
steamId = id64;
|
||||
}
|
||||
|
||||
var maf = Storage.MaFiles.FirstOrDefault(m => m.AccountName.EqualsIgnoreCase(key) || m.SteamId == steamId);
|
||||
if (maf != null)
|
||||
toExport.Add((key, maf));
|
||||
else
|
||||
notFound.Add(key);
|
||||
}
|
||||
|
||||
if (template.ExportToZip)
|
||||
{
|
||||
if (toExport.Count > 0)
|
||||
{
|
||||
var fileName = await ExportMafile(template.Path, template, maf);
|
||||
if (fileName == null)
|
||||
var timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var zipName = $"{toExport.Count}_{timestamp}.zip";
|
||||
var zipPath = Path.Combine(template.Path, zipName);
|
||||
|
||||
await using var zipStream = File.Create(zipPath);
|
||||
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
|
||||
var addedEntries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var (key, maf) in toExport)
|
||||
{
|
||||
var (content, fileName) = BuildMafileContent(template, maf);
|
||||
if (!addedEntries.Add(fileName))
|
||||
{
|
||||
conflict.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
var entry = archive.CreateEntry(fileName);
|
||||
await using var entryStream = entry.Open();
|
||||
await using var writer = new StreamWriter(entryStream, Encoding.UTF8);
|
||||
await writer.WriteAsync(content);
|
||||
exported[maf] = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var (key, maf) in toExport)
|
||||
{
|
||||
var (content, fileName) = BuildMafileContent(template, maf);
|
||||
var fullPath = Path.Combine(template.Path, fileName);
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
conflict.Add(key);
|
||||
continue;
|
||||
}
|
||||
|
||||
exported[maf] = fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
notFound.Add(key);
|
||||
await File.WriteAllTextAsync(fullPath, content);
|
||||
exported[maf] = fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
return new ExportResult(exported, notFound, conflict);
|
||||
}
|
||||
|
||||
private static async Task<string?> ExportMafile(string path, MafileExportTemplate template, Mafile mafile)
|
||||
private static (string content, string fileName) BuildMafileContent(MafileExportTemplate template, Mafile mafile)
|
||||
{
|
||||
// We preserve SteamId even if other info is not included
|
||||
var session = template.IncludeSessionData
|
||||
? mafile.SessionData
|
||||
: new MobileSessionData(null!, mafile.SteamId, default, null, tokens: null);
|
||||
|
||||
|
||||
var serializeMaf = new Mafile
|
||||
{
|
||||
SharedSecret = template.IncludeSharedSecret ? mafile.SharedSecret : null!,
|
||||
@@ -102,14 +136,7 @@ public static class MafileExporter
|
||||
var serialized = NebulaSerializer.SerializeMafile(serializeMaf);
|
||||
var strategy = template.UseLoginAsMafileName ? MafileNamingStrategy.Login : MafileNamingStrategy.SteamId;
|
||||
var fileName = strategy.GetMafileName(serializeMaf);
|
||||
var fullPath = Path.Combine(path, fileName);
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(fullPath, serialized);
|
||||
return fullPath;
|
||||
return (serialized, fileName);
|
||||
}
|
||||
|
||||
private static bool PathIsValid(string path)
|
||||
|
||||
@@ -85,11 +85,6 @@ public static class NebulaSerializer
|
||||
|
||||
public static string SerializeMafile(MobileDataExtended data, Dictionary<string, object?>? properties)
|
||||
{
|
||||
if (Settings.Instance.LegacyMode)
|
||||
{
|
||||
return MafileSerializer.SerializeLegacy(data, Serializer.Settings.SerializationOptions, properties);
|
||||
}
|
||||
|
||||
return Serializer.Serialize(data);
|
||||
return MafileSerializer.SerializeLegacy(data, Serializer.Settings.SerializationOptions, properties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AchiesUtilities.Collections;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using NebulaAuth.Model.Entities;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory cache tracking which proxy IDs are currently assigned to accounts.
|
||||
/// Provides fast lookup for free proxy selection without iterating all mafiles.
|
||||
/// Initialized at startup and kept in sync whenever proxy assignments change.
|
||||
/// </summary>
|
||||
public static class ProxyAssignmentCache
|
||||
{
|
||||
private static readonly Dictionary<int, HashSet<string>> _proxyToAccounts = new();
|
||||
|
||||
public static void Initialize(IEnumerable<Mafile> mafiles)
|
||||
{
|
||||
_proxyToAccounts.Clear();
|
||||
foreach (var maf in mafiles)
|
||||
{
|
||||
if (maf.Proxy != null)
|
||||
AddInternal(maf.Proxy.Id, maf.AccountName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call whenever a proxy assignment on a mafile changes and is persisted to disk.
|
||||
/// </summary>
|
||||
public static void UpdateAssignment(string? accountName, int? oldProxyId, int? newProxyId)
|
||||
{
|
||||
if (accountName == null) return;
|
||||
if (oldProxyId.HasValue) RemoveInternal(oldProxyId.Value, accountName);
|
||||
if (newProxyId.HasValue) AddInternal(newProxyId.Value, accountName);
|
||||
}
|
||||
|
||||
public static bool IsProxyFree(int proxyId)
|
||||
{
|
||||
return !_proxyToAccounts.TryGetValue(proxyId, out var set) || set.Count == 0;
|
||||
}
|
||||
|
||||
public static int GetAccountCount(int proxyId)
|
||||
{
|
||||
return _proxyToAccounts.TryGetValue(proxyId, out var set) ? set.Count : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random free proxy (not the default proxy), or null if none available.
|
||||
/// </summary>
|
||||
public static KeyValuePair<int, ProxyData>? GetRandomFreeProxy(
|
||||
ObservableDictionary<int, ProxyData> proxies,
|
||||
int? excludeDefaultProxyId)
|
||||
{
|
||||
var free = proxies
|
||||
.Where(kvp => kvp.Key != excludeDefaultProxyId && IsProxyFree(kvp.Key))
|
||||
.ToList();
|
||||
if (free.Count == 0) return null;
|
||||
return free[Random.Shared.Next(free.Count)];
|
||||
}
|
||||
|
||||
private static void AddInternal(int proxyId, string accountName)
|
||||
{
|
||||
if (!_proxyToAccounts.TryGetValue(proxyId, out var set))
|
||||
{
|
||||
set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
_proxyToAccounts[proxyId] = set;
|
||||
}
|
||||
|
||||
set.Add(accountName);
|
||||
}
|
||||
|
||||
private static void RemoveInternal(int proxyId, string accountName)
|
||||
{
|
||||
if (_proxyToAccounts.TryGetValue(proxyId, out var set))
|
||||
set.Remove(accountName);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,13 @@ public static class ProxyStorage
|
||||
PatternRequirement.Optional,
|
||||
PatternRequirement.Optional);
|
||||
|
||||
public static readonly ProxyParser SignAtScheme = new(
|
||||
ProxyDefaultFormats.UniversalSignAt, false, ProxyProtocol.HTTP,
|
||||
ProxyPatternProtocol.All,
|
||||
ProxyPatternHostFormat.Domain | ProxyPatternHostFormat.IPv4,
|
||||
PatternRequirement.Required,
|
||||
PatternRequirement.Required);
|
||||
|
||||
|
||||
public static ObservableDictionary<int, ProxyData> Proxies { get; } = new();
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace NebulaAuth.Model;
|
||||
|
||||
public class RemoteLinks
|
||||
{
|
||||
public string? Website { get; set; }
|
||||
public string? Documentation { get; set; }
|
||||
}
|
||||
@@ -6,7 +6,9 @@ using AchiesUtilities.Web.Models;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Utility;
|
||||
using NebulaAuth.View.Dialogs;
|
||||
using NebulaAuth.ViewModel.Other;
|
||||
using SteamLib.Exceptions.Authorization;
|
||||
|
||||
namespace NebulaAuth.Model;
|
||||
@@ -16,13 +18,13 @@ public static partial class SessionHandler
|
||||
private static readonly SemaphoreSlim Semaphore = new(1, 1);
|
||||
|
||||
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile,
|
||||
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null)
|
||||
HttpClientHandlerPair? chp = null, string? snackbarPrefix = null, bool allowInteractiveLogin = true)
|
||||
{
|
||||
chp ??= MaClient.GetHttpClientHandlerPair(mafile);
|
||||
await Semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix);
|
||||
return await HandleInternal(func, chp.Value, mafile, snackbarPrefix, allowInteractiveLogin);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -31,7 +33,7 @@ public static partial class SessionHandler
|
||||
}
|
||||
|
||||
private static async Task<T> HandleInternal<T>(Func<Task<T>> func, HttpClientHandlerPair chp, Mafile mafile,
|
||||
string? snackbarPrefix = null)
|
||||
string? snackbarPrefix = null, bool allowInteractiveLogin = true)
|
||||
{
|
||||
using var scope = Shell.Logger.PushScopeProperty("Scope", "SessionHandler");
|
||||
Exception? currentException = null;
|
||||
@@ -86,6 +88,32 @@ public static partial class SessionHandler
|
||||
}
|
||||
}
|
||||
|
||||
if (allowInteractiveLogin && !DialogHost.IsDialogOpen(null))
|
||||
{
|
||||
var noProxy = mafile.Proxy == null && MaClient.DefaultProxy == null;
|
||||
var currentPassword = GetPassword(mafile);
|
||||
|
||||
Task<LoginAgainVM?> dialogTask = null!;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
dialogTask = DialogsController.ShowLoginAgainDialog(mafile.AccountName, currentPassword, noProxy);
|
||||
});
|
||||
|
||||
var vm = await dialogTask;
|
||||
if (vm != null)
|
||||
{
|
||||
Shell.Logger.Info("User provided credentials interactively for {name}, attempting login",
|
||||
mafile.AccountName);
|
||||
var logged = await LoginAgainInternal(chp, mafile, vm.Password, vm.SavePassword);
|
||||
if (logged)
|
||||
{
|
||||
Shell.Logger.Debug("Interactive login for {name} succeeded, retrying operation",
|
||||
mafile.AccountName);
|
||||
return await func();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new SessionPermanentlyExpiredException(SessionPermanentlyExpiredException.SESSION_EXPIRED_MSG,
|
||||
currentException);
|
||||
}
|
||||
@@ -118,6 +146,7 @@ public static partial class SessionHandler
|
||||
{
|
||||
Shell.Logger.Debug(ex, "Failed to relogin mafile {name} {steamid}", mafile.AccountName,
|
||||
mafile.SessionData?.SteamId);
|
||||
ExceptionHandler.Handle(ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -30,7 +30,7 @@ public partial class SessionHandler //API
|
||||
//Trigger PropertyChanged event for PortableMaClient handling session updated from MaClient
|
||||
mafile.SetSessionData(mafile.SessionData);
|
||||
await Storage.UpdateMafileAsync(mafile);
|
||||
chp.Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(mafile.SessionData);
|
||||
chp.Handler.CookieContainer.SetSteamMobileCookies(mafile.SessionData);
|
||||
}
|
||||
|
||||
public static async Task LoginAgain(HttpClientHandlerPair chp, Mafile mafile, string password, bool savePassword)
|
||||
|
||||
@@ -91,7 +91,7 @@ public partial class Settings : ObservableObject
|
||||
[ObservableProperty] private Color? _iconColor;
|
||||
[ObservableProperty] private bool _isPasswordSet;
|
||||
[ObservableProperty] private LocalizationLanguage _language = LocalizationLanguage.English;
|
||||
[ObservableProperty] private bool _legacyMode = true;
|
||||
[ObservableProperty] private bool _confirmMafileImport = true;
|
||||
[ObservableProperty] private bool _allowAutoUpdate;
|
||||
[ObservableProperty] private bool _useAccountNameAsMafileName;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.Exceptions;
|
||||
using NebulaAuth.Model.MAAC;
|
||||
using NebulaAuth.Model.MafileExport;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
@@ -40,13 +39,16 @@ public static class Shell
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new CantAlignTimeException("", ex);
|
||||
Logger.Error(ex, "Failed to align time with Steam");
|
||||
TimeAligner.SetTimeDifference(0);
|
||||
}
|
||||
|
||||
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
|
||||
await Storage.Initialize(threads);
|
||||
ProxyAssignmentCache.Initialize(Storage.MaFiles);
|
||||
MAACStorage.Initialize();
|
||||
MafileExporterStorage.Initialize();
|
||||
_ = LinksManager.FetchAsync();
|
||||
|
||||
ExtensionsLogger.LogDebug("Application started");
|
||||
}
|
||||
|
||||
@@ -3,20 +3,20 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace NebulaAuth.Model.Update;
|
||||
|
||||
public class ChangelogEntry
|
||||
{
|
||||
[JsonProperty("version")] public string Version { get; set; } = string.Empty;
|
||||
public class ChangelogEntry
|
||||
{
|
||||
[JsonProperty("version")] public string Version { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("date")] public string Date { get; set; } = string.Empty;
|
||||
[JsonProperty("date")] public string Date { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("changes")] public List<ChangeItem> Changes { get; set; } = new();
|
||||
}
|
||||
[JsonProperty("changes")] public List<ChangeItem> Changes { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ChangeItem
|
||||
{
|
||||
[JsonProperty("type")] public string Type { get; set; } = string.Empty;
|
||||
public class ChangeItem
|
||||
{
|
||||
[JsonProperty("type")] public string Type { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("text")] public string Text { get; set; } = string.Empty;
|
||||
[JsonProperty("text")] public string Text { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("link")] public string? Link { get; set; }
|
||||
}
|
||||
[JsonProperty("link")] public string? Link { get; set; }
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
|
||||
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
|
||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||
<AssemblyVersion>1.8.3</AssemblyVersion>
|
||||
<AssemblyVersion>1.8.5</AssemblyVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -89,4 +89,7 @@
|
||||
Color="{StaticResource SecondaryColor}" />
|
||||
<SolidColorBrush x:Key="MaterialDesign.Brush.Secondary.Dark.Foreground"
|
||||
Color="{StaticResource BaseContentColor}" />
|
||||
|
||||
<SolidColorBrush x:Key="MaterialDesign.Brush.RadioButton.Outline"
|
||||
Color="{StaticResource BaseContentColor}" />
|
||||
</ResourceDictionary>
|
||||
@@ -36,7 +36,8 @@
|
||||
<!-- Window Buttons -->
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
Orientation="Horizontal"
|
||||
TextElement.Foreground="{DynamicResource PrimaryContentBrush}">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="materialDesign:PackIcon">
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
|
||||
@@ -93,7 +93,7 @@ public static class ExceptionHandler
|
||||
}
|
||||
case LoginException e:
|
||||
{
|
||||
return "LoginException".GetCodeBehindLocalization() + ": " +
|
||||
return "LoginException".GetCodeBehindLocalization() +
|
||||
ErrorTranslatorHelper.TranslateLoginError(e.Error);
|
||||
}
|
||||
case UnsupportedAuthTypeException e:
|
||||
|
||||
@@ -12,31 +12,10 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!--<materialDesign:PackIcon Kind="WarningCircleOutline" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />-->
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr ConfirmCancelDialog.Title}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" CommandParameter="{StaticResource False}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Opacity="0.7" />
|
||||
<Grid Grid.Row="2" Margin="12">
|
||||
<nebulaAuth:DialogHeader Title="{Tr ConfirmCancelDialog.Title}"
|
||||
CloseParameter="{StaticResource False}" />
|
||||
<Grid Grid.Row="1" Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
|
||||
mc:Ignorable="d"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
||||
@@ -15,53 +16,39 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Login" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr LoginAgainDialog.Title}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" CommandParameter="{StaticResource False}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Opacity="0.7" />
|
||||
<Grid Grid.Row="2" Margin="12">
|
||||
<nebulaAuth:DialogHeader IconKind="Login"
|
||||
Title="{Tr LoginAgainDialog.Title}"
|
||||
CloseParameter="{StaticResource False}" />
|
||||
<Grid Grid.Row="1" Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox TabIndex="0" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
|
||||
<nebulaAuth:HintBox Grid.Row="0"
|
||||
Margin="10,10,10,0"
|
||||
Severity="Warning"
|
||||
FontSize="14"
|
||||
Visibility="{Binding ShowNoProxyWarning, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Text="{Tr LoginAgainDialog.NoProxyWarning}" />
|
||||
<TextBox TabIndex="0" Grid.Row="1" Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
|
||||
|
||||
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||
Padding="10"
|
||||
FontSize="15"
|
||||
Margin="10,10,10,0"
|
||||
Grid.Row="0"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
|
||||
materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"
|
||||
materialDesign:HintAssist.IsFloating="False"
|
||||
materialDesign:TextFieldAssist.LeadingIcon="Key"
|
||||
materialDesign:TextFieldAssist.HasLeadingIcon="True" />
|
||||
<CheckBox FontSize="15" Grid.Row="1" Margin="10,5,10,0"
|
||||
<CheckBox FontSize="15" Grid.Row="2" Margin="10,5,10,0"
|
||||
IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}"
|
||||
IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}" />
|
||||
<Grid Grid.Row="2" Margin="10,10,10,0">
|
||||
<Grid Grid.Row="3" Margin="10,10,10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
||||
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
|
||||
mc:Ignorable="d"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
||||
@@ -16,32 +17,12 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Login" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontSize="16" VerticalAlignment="Center" Foreground="{DynamicResource BaseContentBrush}"
|
||||
Margin="7,0,0,0" HorizontalAlignment="Left">
|
||||
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}" />
|
||||
<Run FontWeight="Bold" Text="{Binding UserName}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" CommandParameter="{StaticResource False}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Opacity="0.7" />
|
||||
<Grid Grid.Row="2" Margin="12">
|
||||
<nebulaAuth:DialogHeader IconKind="Login"
|
||||
Title="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}"
|
||||
TitleAccent="{Binding UserName}"
|
||||
CloseParameter="{StaticResource False}" />
|
||||
<Grid Grid.Row="1" Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<UserControl x:Class="NebulaAuth.View.Dialogs.MafileImportDialog"
|
||||
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"
|
||||
Height="Auto" Width="Auto" MaxWidth="520"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
<Grid MinHeight="240" MinWidth="440">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<nebulaAuth:DialogHeader Title="{Tr MafileImportDialog.Title}" />
|
||||
|
||||
<Grid Grid.Row="1" Margin="14,14,14,16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Margin="6,0,6,12" Padding="12,10"
|
||||
CornerRadius="8"
|
||||
BorderThickness="1">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{DynamicResource BaseContentColor}" Opacity="0.07" />
|
||||
</Border.Background>
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource BaseContentColor}" Opacity="0.12" />
|
||||
</Border.BorderBrush>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="{Tr MafileImportDialog.TotalCount}" Opacity="0.82" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding TotalCount, Mode=OneWay}" FontWeight="SemiBold" />
|
||||
|
||||
<TextBlock Grid.Row="1" Text="{Tr MafileImportDialog.ConflictCount}" Opacity="0.82"
|
||||
Visibility="{Binding HasConflicts, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding ConflictCount, Mode=OneWay}"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource WarningBrush}"
|
||||
Visibility="{Binding HasConflicts, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<CheckBox Grid.Row="1"
|
||||
Margin="10,4,10,8"
|
||||
IsChecked="{Binding OverwriteConflicts}"
|
||||
Visibility="{Binding HasConflicts, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Content="{Tr MafileImportDialog.OverwriteConflicts}" />
|
||||
|
||||
<Separator Grid.Row="2"
|
||||
Margin="10,2,10,2" Opacity="0.35"
|
||||
Visibility="{Binding HasConflicts, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||
|
||||
<CheckBox Grid.Row="3"
|
||||
Margin="10,4,10,0"
|
||||
IsChecked="{Binding AddToGroup}"
|
||||
Content="{Tr MafileImportDialog.AddToGroup}" />
|
||||
|
||||
<ComboBox x:Name="GroupBox"
|
||||
Grid.Row="4"
|
||||
Margin="42,0,10,0"
|
||||
IsEditable="True"
|
||||
ItemsSource="{Binding Groups}"
|
||||
|
||||
Text="{Binding Group, UpdateSourceTrigger=PropertyChanged}"
|
||||
materialDesign:HintAssist.Hint="{Tr MafileImportDialog.GroupFieldHint}"
|
||||
materialDesign:HintAssist.IsFloating="False"
|
||||
Visibility="{Binding AddToGroup, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Style="{StaticResource MaterialDesignFloatingHintComboBox}" />
|
||||
|
||||
<Grid Grid.Row="5" Margin="10,16,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Width="130"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="2,0,4,0"
|
||||
Style="{StaticResource MaterialDesignFlatDarkBgButton}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Content="{Tr Common.OK}"
|
||||
IsDefault="True" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Width="130"
|
||||
Margin="0,0,2,0"
|
||||
Style="{StaticResource MaterialDesignOutlinedButton}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
||||
CommandParameter="{x:Null}"
|
||||
Content="{Tr Common.Cancel}"
|
||||
IsCancel="True" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace NebulaAuth.View.Dialogs;
|
||||
|
||||
public partial class MafileImportDialog
|
||||
{
|
||||
public MafileImportDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
|
||||
mc:Ignorable="d"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
d:DataContext="{d:DesignInstance other:SdaPasswordDialogVM}"
|
||||
@@ -14,29 +15,11 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Lock" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontSize="16" VerticalAlignment="Center" Foreground="{DynamicResource BaseContentBrush}"
|
||||
Margin="7,0,0,0" HorizontalAlignment="Left"
|
||||
Text="{Tr SdaPasswordDialog.Title}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" CommandParameter="{StaticResource False}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Opacity="0.7" />
|
||||
<Grid Grid.Row="2" Margin="12">
|
||||
<nebulaAuth:DialogHeader IconKind="Lock"
|
||||
Title="{Tr SdaPasswordDialog.Title}"
|
||||
CloseParameter="{StaticResource False}" />
|
||||
<Grid Grid.Row="1" Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}">
|
||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
|
||||
|
||||
<Grid MinHeight="100" MinWidth="300" MaxWidth="400">
|
||||
@@ -15,38 +17,19 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Encryption" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr SetEncryptionPasswordDialog.Title}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" CommandParameter="{StaticResource False}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Opacity="0.7" />
|
||||
<TextBlock Grid.Row="2" IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal"
|
||||
<nebulaAuth:DialogHeader IconKind="Encryption"
|
||||
Title="{Tr SetEncryptionPasswordDialog.Title}"
|
||||
CloseParameter="{StaticResource False}" />
|
||||
<TextBlock Grid.Row="1" IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal"
|
||||
FontSize="16"
|
||||
Text="{Tr SetEncryptionPasswordDialog.DialogText}"
|
||||
Margin="10" HorizontalAlignment="Left" />
|
||||
<PasswordBox TabIndex="0" FontSize="16" x:Name="PasswordBox"
|
||||
materialDesign:PasswordBoxAssist.Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}"
|
||||
Margin="10" Grid.Row="3" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
|
||||
Margin="10" Grid.Row="2" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
|
||||
materialDesign:HintAssist.Hint="{Tr SetEncryptionPasswordDialog.Password}" />
|
||||
<Grid Grid.Row="4" Margin="10">
|
||||
<Grid Grid.Row="3" Margin="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
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"
|
||||
Height="Auto" Width="Auto" MaxWidth="500"
|
||||
Background="{DynamicResource WindowBackground}">
|
||||
@@ -11,31 +12,10 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!--<materialDesign:PackIcon Kind="WarningCircleOutline" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />-->
|
||||
<TextBlock x:Name="TitleTextBlock" FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr TextFieldDialog.Title}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" CommandParameter="{x:Null}"
|
||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Opacity="0.7" />
|
||||
<Grid Grid.Row="2" Margin="12">
|
||||
<nebulaAuth:DialogHeader x:Name="TitleHeader"
|
||||
Title="{Tr TextFieldDialog.Title}" />
|
||||
<Grid Grid.Row="1" Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
@@ -14,7 +14,7 @@ public partial class TextFieldDialog
|
||||
InitializeComponent();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
TitleTextBlock.Text = title;
|
||||
TitleHeader.Title = title;
|
||||
|
||||
if (!string.IsNullOrEmpty(msg))
|
||||
HintAssist.SetHint(TextField, msg);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
||||
xmlns:nebulaAuth="clr-namespace:NebulaAuth"
|
||||
mc:Ignorable="d"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
d:DataContext="{d:DesignInstance other:UpdateDialogVM}"
|
||||
@@ -15,37 +16,15 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Update" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18">
|
||||
<Run Text="{Tr UpdateDialog.Title, IsDynamic=False}" />
|
||||
<Run FontWeight="Bold" Text="{Binding Version, Mode=OneWay}" />
|
||||
</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 Grid.Row="1" Opacity="0.7" />
|
||||
<nebulaAuth:DialogHeader IconKind="Update"
|
||||
Title="{Tr UpdateDialog.Title, IsDynamic=False}"
|
||||
TitleAccent="{Binding Version, Mode=OneWay}" />
|
||||
|
||||
<!-- Changelog -->
|
||||
<Grid Grid.Row="2" Margin="12,10,12,4">
|
||||
<Grid Grid.Row="1" Margin="12,10,12,4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -120,7 +99,7 @@
|
||||
</Grid>
|
||||
|
||||
<!-- Buttons -->
|
||||
<Grid Grid.Row="3" Margin="12,6,12,14">
|
||||
<Grid Grid.Row="2" Margin="12,6,12,14">
|
||||
|
||||
<!-- Normal buttons -->
|
||||
<StackPanel
|
||||
|
||||
@@ -56,30 +56,7 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="LinkBoxOutline" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
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" Opacity="0.4" Grid.Row="1" />
|
||||
<nebulaAuth:DialogHeader IconKind="LinkBoxOutline" Title="{Tr LinkerDialog.Title}" />
|
||||
<nebulaAuth:HintBox FontSize="14"
|
||||
Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||
Grid.Row="2" Margin="10,10,10,0"
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
Background="Transparent"
|
||||
RenderOptions.BitmapScalingMode="HighQuality">
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Loaded="LinksView_Loaded">
|
||||
|
||||
<materialDesign:Card Padding="24" HorizontalAlignment="Center" VerticalAlignment="Center" UniformCornerRadius="12">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
@@ -49,16 +50,20 @@
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="0,0,0,5" FontSize="18" Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Click="Website_Click" IsEnabled="False">
|
||||
<Button x:Name="WebsiteButton" Margin="0,0,0,5" FontSize="18"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsEnabled="False"
|
||||
Click="Website_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="Web" Width="24" Height="24" Margin="0 0 8 0" />
|
||||
<TextBlock Text="Web-site" VerticalAlignment="Center" />
|
||||
<TextBlock Text="Achiefy" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Margin="0,0,0,5" FontSize="18" Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Click="Documentation_Click" IsEnabled="False">
|
||||
<Button x:Name="DocumentationButton" Margin="0,0,0,5" FontSize="18"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsEnabled="False"
|
||||
Click="Documentation_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Viewbox Width="22" Height="22" Margin="0 0 8 0">
|
||||
<Canvas Width="22" Height="22">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
@@ -13,6 +13,12 @@ public partial class LinksView : UserControl
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void LinksView_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WebsiteButton.IsEnabled = LinksManager.WebsiteUrl != null;
|
||||
DocumentationButton.IsEnabled = LinksManager.DocumentationUrl != null;
|
||||
}
|
||||
|
||||
private void Telegram_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("https://t.me/nebulaauth") {UseShellExecute = true});
|
||||
@@ -26,12 +32,14 @@ public partial class LinksView : UserControl
|
||||
|
||||
private void Website_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("https://yourwebsite.com") {UseShellExecute = true});
|
||||
if (LinksManager.WebsiteUrl != null)
|
||||
Process.Start(new ProcessStartInfo(LinksManager.WebsiteUrl) {UseShellExecute = true});
|
||||
}
|
||||
|
||||
private void Documentation_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("https://yourwebsite.com") {UseShellExecute = true});
|
||||
if (LinksManager.DocumentationUrl != null)
|
||||
Process.Start(new ProcessStartInfo(LinksManager.DocumentationUrl) {UseShellExecute = true});
|
||||
}
|
||||
|
||||
private void CheckForUpdates_Click(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
mc:Ignorable="d"
|
||||
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
||||
MinWidth="400"
|
||||
MaxWidth="400"
|
||||
MinWidth="500"
|
||||
MaxWidth="500"
|
||||
Background="{DynamicResource WindowBackground}"
|
||||
d:DataContext="{d:DesignInstance other:MafileExporterVM}"
|
||||
d:Background="#141119"
|
||||
@@ -19,32 +19,12 @@
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="FileExport" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr ExportDialog.ExportTitle}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
||||
IsCancel="True">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Background="DarkGray" Opacity="0.4" Grid.Row="1" />
|
||||
<Grid Grid.Row="2" Margin="15" TextElement.FontSize="14">
|
||||
<nebulaAuth:DialogHeader IconKind="FileExport" Title="{Tr ExportDialog.ExportTitle}" />
|
||||
<Grid Grid.Row="1" Margin="15" TextElement.FontSize="14">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
@@ -133,6 +113,12 @@
|
||||
IsEnabled="{Binding CurrentTemplate, Converter={StaticResource NullableToBooleanConverter}}">
|
||||
|
||||
<StackPanel>
|
||||
<CheckBox IsChecked="{Binding CurrentTemplate.ExportToZip, FallbackValue=False}"
|
||||
Margin="3,0,0,3"
|
||||
Content="{Tr ExportDialog.ExportOptions.ExportToZip}"
|
||||
ToolTipService.InitialShowDelay="200"
|
||||
ToolTip="{Tr ExportDialog.Tooltips.ExportToZip}" />
|
||||
|
||||
<TextBlock FontWeight="DemiBold"
|
||||
Text="{Tr ExportDialog.ExportSettingsCommon}" />
|
||||
|
||||
@@ -233,8 +219,8 @@
|
||||
|
||||
|
||||
</Grid>
|
||||
<Separator Grid.Row="3" />
|
||||
<Button Command="{Binding ExportCommand}" Style="{StaticResource MaterialDesignFlatDarkBgButton}" Grid.Row="4"
|
||||
<Separator Grid.Row="2" />
|
||||
<Button Command="{Binding ExportCommand}" Style="{StaticResource MaterialDesignFlatDarkBgButton}" Grid.Row="3"
|
||||
Margin="20,7,20,7" MaxWidth="150" Content="{Tr ExportDialog.ExportButton}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -29,9 +29,6 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.Resources>
|
||||
<Style TargetType="materialDesign:PackIcon">
|
||||
@@ -58,35 +55,17 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="FileMove" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr MafileMoverView.Title}" />
|
||||
</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" Opacity="0.4" Grid.Row="1" />
|
||||
<nebulaAuth:DialogHeader IconKind="FileMove" Title="{Tr MafileMoverView.Title}" />
|
||||
<nebulaAuth:HintBox FontSize="14"
|
||||
Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||
Grid.Row="2" Margin="10,10,10,0"
|
||||
Grid.Row="1" Margin="10,10,10,0"
|
||||
Text="{Binding Tip}" />
|
||||
<ContentControl
|
||||
Grid.Row="3" Margin="20" Content="{Binding CurrentStep}" />
|
||||
Grid.Row="2" Margin="20" Content="{Binding CurrentStep}" />
|
||||
|
||||
<nebulaAuth:HintBox FontSize="14"
|
||||
Visibility="{Binding Error, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||
Grid.Row="4" Margin="10,0,10,10"
|
||||
Grid.Row="3" Margin="10,0,10,10"
|
||||
Text="{Binding Error}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -20,115 +20,46 @@
|
||||
FontSize="16">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<md:PackIcon Kind="Proxy" Width="20" Height="20" Margin="0,0,5,0" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr ProxyManagerDialog.Title}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" Command="{x:Static md:DialogHost.CloseDialogCommand}"
|
||||
IsCancel="True">
|
||||
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" />
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="15">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Stretch">
|
||||
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}" />
|
||||
<Run Text="{Binding DefaultProxy.Key, StringFormat='
0:', FallbackValue='-', Mode=OneWay}" />
|
||||
<Run>
|
||||
<Run.Text>
|
||||
<MultiBinding Mode="OneWay"
|
||||
Converter="{StaticResource ProxyDataTextMultiConverter}">
|
||||
<Binding Path="DefaultProxy.Value" />
|
||||
<Binding Path="DataContext.DisplayProtocol"
|
||||
RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
<Binding Path="DataContext.DisplayCredentials"
|
||||
RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
</TextBlock>
|
||||
|
||||
<Button Grid.Column="1" Command="{Binding RemoveDefaultCommand}">
|
||||
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
|
||||
</Button>
|
||||
<nebulaAuth:DialogHeader IconKind="Proxy" Title="{Tr ProxyManagerDialog.Title}" />
|
||||
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="1" Margin="15,0,15,15">
|
||||
<CheckBox Content="{Tr ProxyManagerDialog.DisplayProxyProtocol}" IsChecked="{Binding DisplayProtocol}" />
|
||||
<CheckBox Content="{Tr ProxyManagerDialog.DisplayProxyCredentials}"
|
||||
IsChecked="{Binding DisplayCredentials}" />
|
||||
</StackPanel>
|
||||
</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}" />
|
||||
<MenuItem Header="{Tr Common.Delete}"
|
||||
Command="{Binding PlacementTarget.Tag.RemoveProxyCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
CommandParameter="{Binding Value}" />
|
||||
</ContextMenu>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<!-- Tabs -->
|
||||
<TabControl Grid.Row="1"
|
||||
md:ColorZoneAssist.Background="{DynamicResource Base100Brush}"
|
||||
Style="{StaticResource MaterialDesignUniformTabControl}">
|
||||
|
||||
<!-- Tab 1: Proxy Manager -->
|
||||
<TabItem Header="{Tr ProxyManagerDialog.TabManager}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<!-- Default proxy + display options -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="15">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<Run Text="{Binding Key, Mode=OneWay}" /><Run Text=": " />
|
||||
<TextBlock HorizontalAlignment="Stretch">
|
||||
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}" />
|
||||
<Run
|
||||
Text="{Binding DefaultProxy.Key, StringFormat='
0:', FallbackValue='-', Mode=OneWay}" />
|
||||
<Run>
|
||||
<Run.Text>
|
||||
<MultiBinding Mode="OneWay"
|
||||
Converter="{StaticResource ProxyDataTextMultiConverter}">
|
||||
<Binding Path="Value" />
|
||||
<Binding Path="DefaultProxy.Value" />
|
||||
<Binding Path="DataContext.DisplayProtocol"
|
||||
RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
<Binding Path="DataContext.DisplayCredentials"
|
||||
@@ -136,66 +67,228 @@
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
|
||||
</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.Column="1" Command="{Binding RemoveDefaultCommand}">
|
||||
<md:PackIcon Kind="ClearBox" Width="20" Height="20" />
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="1" Margin="15,0,15,15">
|
||||
<CheckBox Content="{Tr ProxyManagerDialog.DisplayProxyProtocol}"
|
||||
IsChecked="{Binding DisplayProtocol}" />
|
||||
<CheckBox Content="{Tr ProxyManagerDialog.DisplayProxyCredentials}"
|
||||
IsChecked="{Binding DisplayCredentials}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</md:Card>
|
||||
<Grid Grid.Row="4" Margin="5,0,15,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<nebulaAuth:HintBox
|
||||
Visibility="{Binding ErrorText, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||
Margin="15,0,15,0"
|
||||
Grid.Row="0"
|
||||
Severity="Error"
|
||||
FontSize="14"
|
||||
Text="{Binding ErrorText}"
|
||||
CloseCommand="{Binding ClearErrorCommand}"
|
||||
ShowCloseButton="True" />
|
||||
<!-- Proxy list -->
|
||||
<md:Card Grid.Row="1" 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}" />
|
||||
<MenuItem Header="{Tr Common.Delete}"
|
||||
Command="{Binding PlacementTarget.Tag.RemoveProxyCommand, 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" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<Run Text="{Binding Key, Mode=OneWay}" /><Run Text=": " />
|
||||
<Run>
|
||||
<Run.Text>
|
||||
<MultiBinding Mode="OneWay"
|
||||
Converter="{StaticResource ProxyDataTextMultiConverter}">
|
||||
<Binding Path="Value" />
|
||||
<Binding Path="DataContext.DisplayProtocol"
|
||||
RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
<Binding Path="DataContext.DisplayCredentials"
|
||||
RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
</MultiBinding>
|
||||
</Run.Text>
|
||||
</Run>
|
||||
</TextBlock>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="10"
|
||||
Margin="0,0,2,0"
|
||||
Foreground="{DynamicResource MaterialDesign.Brush.ForegroundLight}"
|
||||
Text="{Binding Key, Converter={StaticResource ProxyAccountCountConverter}}" />
|
||||
<Button Style="{StaticResource MaterialDesignIconButton}" Padding="0"
|
||||
Width="24"
|
||||
Height="24" md:RippleAssist.IsDisabled="True" Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
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>
|
||||
|
||||
<TextBox Text="{Binding AddProxyField}"
|
||||
KeyDown="ProxyInput_KeyDown"
|
||||
FontSize="12"
|
||||
md:TextFieldAssist.HasClearButton="True"
|
||||
AcceptsReturn="True"
|
||||
MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
Margin="15"
|
||||
Height="90"
|
||||
md:HintAssist.IsFloating="False"
|
||||
md:HintAssist.Hint="IP:PORT
IP:PORT:USER:PASS
PROTOCOL://IP:PORT:USER:PASS
IP:PORT:USER:PASS{ID}
|
||||
<!-- Add / Remove area -->
|
||||
<Grid Grid.Row="2" Margin="5,0,15,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<nebulaAuth:HintBox
|
||||
Visibility="{Binding ErrorText, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||
Margin="15,0,15,0"
|
||||
Grid.Row="0"
|
||||
Severity="Error"
|
||||
FontSize="14"
|
||||
Text="{Binding ErrorText}"
|
||||
CloseCommand="{Binding ClearErrorCommand}"
|
||||
ShowCloseButton="True" />
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Text="{Binding AddProxyField}"
|
||||
KeyDown="ProxyInput_KeyDown"
|
||||
FontSize="12"
|
||||
md:TextFieldAssist.HasClearButton="True"
|
||||
AcceptsReturn="True"
|
||||
MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
Margin="15"
|
||||
Height="90"
|
||||
md:HintAssist.IsFloating="False"
|
||||
md:HintAssist.Hint="IP:PORT
IP:PORT:USER:PASS
PROTOCOL://IP:PORT:USER:PASS
IP:PORT:USER:PASS{ID}
|
||||
" />
|
||||
<Button x:Name="AddProxyBtn" ToolTip="CTRL+ENTER" IsDefault="True" Grid.Column="1" Height="90"
|
||||
Command="{Binding AddProxyCommand}">
|
||||
<md:PackIcon Kind="Add" />
|
||||
</Button>
|
||||
<Button Grid.Column="2" Height="90" Command="{Binding RemoveProxyCommand}" CommandParameter="{x:Null}">
|
||||
<md:PackIcon Kind="Trash" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Button x:Name="AddProxyBtn" ToolTip="CTRL+ENTER" IsDefault="True" Grid.Column="1"
|
||||
Height="90" Command="{Binding AddProxyCommand}">
|
||||
<md:PackIcon Kind="Add" />
|
||||
</Button>
|
||||
<Button Grid.Column="2" Height="90" Command="{Binding RemoveProxyCommand}"
|
||||
CommandParameter="{x:Null}">
|
||||
<md:PackIcon Kind="Trash" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
</Grid>
|
||||
<!-- Tab 2: Bulk Assignment -->
|
||||
<TabItem Header="{Tr ProxyManagerDialog.TabAssignment}">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Stats counters -->
|
||||
<UniformGrid Grid.Row="0" Columns="3" Margin="0,8,0,12">
|
||||
<Border Margin="0,0,4,0" Padding="10,8" CornerRadius="8"
|
||||
Background="{DynamicResource MaterialDesign.Brush.Card.Background}">
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock FontSize="20" FontWeight="Bold" HorizontalAlignment="Center"
|
||||
Text="{Binding TotalAccounts, Mode=OneWay}" />
|
||||
<TextBlock FontSize="11" HorizontalAlignment="Center"
|
||||
Text="{Tr ProxyManagerDialog.TotalAccounts}"
|
||||
Foreground="{DynamicResource MaterialDesign.Brush.ForegroundLight}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Margin="4,0,4,0" Padding="10,8" CornerRadius="8"
|
||||
Background="{DynamicResource MaterialDesign.Brush.Card.Background}">
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock FontSize="20" FontWeight="Bold" HorizontalAlignment="Center"
|
||||
Text="{Binding TotalProxies, Mode=OneWay}" />
|
||||
<TextBlock FontSize="11" HorizontalAlignment="Center"
|
||||
Text="{Tr ProxyManagerDialog.TotalProxies}"
|
||||
Foreground="{DynamicResource MaterialDesign.Brush.ForegroundLight}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Margin="4,0,0,0" Padding="10,8" CornerRadius="8"
|
||||
Background="{DynamicResource MaterialDesign.Brush.Card.Background}">
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock FontSize="20" FontWeight="Bold" HorizontalAlignment="Center"
|
||||
Text="{Binding AssignedProxies, Mode=OneWay}" />
|
||||
<TextBlock FontSize="11" HorizontalAlignment="Center"
|
||||
Text="{Tr ProxyManagerDialog.AssignedProxies}"
|
||||
Foreground="{DynamicResource MaterialDesign.Brush.ForegroundLight}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UniformGrid>
|
||||
|
||||
<!-- Toggle: behaviour for unspecified proxy -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,10">
|
||||
<TextBlock Text="{Tr ProxyManagerDialog.UnspecifiedBehavior}" FontSize="13"
|
||||
Margin="0,0,0,4"
|
||||
Foreground="{DynamicResource MaterialDesign.Brush.ForegroundLight}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<RadioButton Content="{Tr ProxyManagerDialog.UnspecifiedRandom}"
|
||||
IsChecked="{Binding UnspecifiedAssignRandom}"
|
||||
Margin="0,0,24,0" />
|
||||
<RadioButton Content="{Tr ProxyManagerDialog.UnspecifiedRemove}"
|
||||
IsChecked="{Binding UnspecifiedAssignRandom, Converter={StaticResource ReverseBooleanConverter}}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Input -->
|
||||
<TextBox Grid.Row="2"
|
||||
Text="{Binding AssignmentInput, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="12"
|
||||
md:TextFieldAssist.HasClearButton="True"
|
||||
AcceptsReturn="True"
|
||||
Style="{StaticResource MaterialDesignOutlinedTextBox}"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
md:HintAssist.IsFloating="False"
|
||||
VerticalContentAlignment="Top"
|
||||
Cursor="IBeam"
|
||||
|
||||
md:HintAssist.Hint="login:IP:PORT
login:IP:PORT:USER:PASS
login:socks5://USER:PASS@IP:PORT
login:ID
login" />
|
||||
|
||||
<!-- Result HintBox -->
|
||||
<nebulaAuth:HintBox Grid.Row="3"
|
||||
Visibility="{Binding AssignmentTip, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||
Margin="0,8,0,0"
|
||||
Severity="Info"
|
||||
FontSize="14"
|
||||
Text="{Binding AssignmentTip}"
|
||||
CloseCommand="{Binding ClearAssignmentTipCommand}"
|
||||
ShowCloseButton="True" />
|
||||
|
||||
<!-- Apply button -->
|
||||
<Button Grid.Row="4" Margin="0,10,0,0" HorizontalAlignment="Right"
|
||||
Command="{Binding ApplyAssignmentCommand}"
|
||||
Content="{Tr ProxyManagerDialog.ApplyAssignment}" />
|
||||
|
||||
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -20,7 +20,6 @@
|
||||
FontSize="16">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
@@ -29,36 +28,10 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ===== HEADER ===== -->
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<md:PackIcon Kind="ShieldKey" Width="20" Height="20"
|
||||
Margin="0,0,5,0" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock Text="{Tr SetAccountPasswordsView.Title}"
|
||||
FontSize="18"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource BaseContentBrush}" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Column="1" Width="30" Height="30"
|
||||
Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Static md:DialogHost.CloseDialogCommand}"
|
||||
IsCancel="True">
|
||||
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Separator Grid.Row="1" />
|
||||
<nebulaAuth:DialogHeader IconKind="ShieldKey" Title="{Tr SetAccountPasswordsView.Title}" />
|
||||
|
||||
<!-- ===== ENCRYPTION PASSWORD SECTION ===== -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
@@ -109,7 +82,7 @@
|
||||
|
||||
|
||||
<!-- ===== ACCOUNTS INPUT ===== -->
|
||||
<TextBox Grid.Row="3"
|
||||
<TextBox Grid.Row="2"
|
||||
Margin="15,0,15,10"
|
||||
Text="{Binding AccountsPasswords, UpdateSourceTrigger=PropertyChanged}"
|
||||
FontSize="13"
|
||||
@@ -124,14 +97,14 @@
|
||||
md:TextFieldAssist.TextBoxViewVerticalAlignment="Top"
|
||||
VerticalContentAlignment="Top" />
|
||||
<!-- ===== INFO TEXT ===== -->
|
||||
<TextBlock Grid.Row="4"
|
||||
<TextBlock Grid.Row="3"
|
||||
Margin="20,0,20,15"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource BaseContentBrush}"
|
||||
Text="{Tr SetAccountPasswordsView.EncryptionHint}" />
|
||||
|
||||
<Button Grid.Row="5"
|
||||
<Button Grid.Row="4"
|
||||
Margin="10,0,0,10"
|
||||
Width="120"
|
||||
Content="{Tr Common.Save}"
|
||||
@@ -139,7 +112,7 @@
|
||||
Style="{StaticResource MaterialDesignOutlinedButton}" Cursor="Hand" />
|
||||
|
||||
<!-- ===== HINT / RESULT BOX ===== -->
|
||||
<nebulaAuth:HintBox Grid.Row="6"
|
||||
<nebulaAuth:HintBox Grid.Row="5"
|
||||
Margin="15,0,15,10"
|
||||
Visibility="{Binding Tip, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||
CloseCommand="{Binding ClearTipCommand}"
|
||||
|
||||
@@ -18,36 +18,15 @@
|
||||
<Grid>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Margin="10,10,10,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Settings" Width="20" Height="20" Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource PrimaryContentBrush}" />
|
||||
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" FontSize="18" Text="{Tr SettingsDialog.Title}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Width="30" Height="30"
|
||||
Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
||||
IsEnabled="{Binding ApplyRenameSettingCommand.IsRunning, Converter={StaticResource ReverseBooleanConverter}, Mode=OneWay}"
|
||||
IsCancel="True">
|
||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Margin="0,10,0,0" />
|
||||
<nebulaAuth:DialogHeader IconKind="Settings"
|
||||
Title="{Tr SettingsDialog.Title}"
|
||||
IsCloseEnabled="{Binding ApplyRenameSettingCommand.IsRunning, Converter={StaticResource ReverseBooleanConverter}, Mode=OneWay}" />
|
||||
|
||||
<TabControl materialDesign:ColorZoneAssist.Background="{DynamicResource Base100Brush}"
|
||||
Style="{StaticResource MaterialDesignUniformTabControl}" Grid.Row="2">
|
||||
Style="{StaticResource MaterialDesignUniformTabControl}" Grid.Row="1">
|
||||
<TabItem Header="{Tr SettingsDialog.MainSettings}">
|
||||
<StackPanel Width="400" Margin="10,18,10,40" Orientation="Vertical" HorizontalAlignment="Center">
|
||||
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,15,0,0"
|
||||
@@ -59,9 +38,9 @@
|
||||
Content="{Tr SettingsDialog.MinimizeToTray}" />
|
||||
|
||||
|
||||
<CheckBox Margin="0,5,0,0" IsChecked="{Binding LegacyMode}"
|
||||
Content="{Tr SettingsDialog.LegacyMafileMode}"
|
||||
ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}" />
|
||||
<CheckBox Margin="0,5,0,0" IsChecked="{Binding ConfirmMafileImport}"
|
||||
Content="{Tr SettingsDialog.ConfirmMafileImport}"
|
||||
ToolTip="{Tr SettingsDialog.ConfirmMafileImportHint}" />
|
||||
<!--<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>-->
|
||||
|
||||
<!--<CheckBox Margin="0,12,0,0" IsChecked="{Binding UseAccountNameAsMafileName}">
|
||||
|
||||
@@ -42,7 +42,7 @@ public partial class MainVM : ObservableObject
|
||||
new MaProxy(kvp.Key, kvp.Value)));
|
||||
Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged;
|
||||
QueryGroups();
|
||||
UpdateManager.CheckForUpdates(false);
|
||||
UpdateManager.CheckForUpdates();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -25,6 +26,13 @@ namespace NebulaAuth.ViewModel;
|
||||
|
||||
public partial class MainVM //File //TODO: Refactor
|
||||
{
|
||||
private record MafileReadResult(
|
||||
Mafile? Mafile,
|
||||
bool SdaPasswordPrompted,
|
||||
SDAEncryptionHelper.Context? SdaContext);
|
||||
|
||||
private record MafileImportPlanItem(Mafile Mafile, bool RequiresRelogin, bool HasConflict);
|
||||
|
||||
public Settings Settings => Settings.Instance;
|
||||
|
||||
|
||||
@@ -71,10 +79,11 @@ public partial class MainVM //File //TODO: Refactor
|
||||
: Task.CompletedTask;
|
||||
}
|
||||
|
||||
//TODO: extract flow and refactor, this method is too long and does too many things
|
||||
public async Task AddMafile(string[] path)
|
||||
{
|
||||
bool? confirmOverwrite = null;
|
||||
var summary = new MafileImportSummary();
|
||||
var items = new List<MafileImportPlanItem>();
|
||||
SDAEncryptionHelper.Context? sdaContext = null;
|
||||
var sdaPasswordPrompted = false;
|
||||
foreach (var str in path)
|
||||
@@ -90,16 +99,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
continue;
|
||||
}
|
||||
|
||||
var overwrite = confirmOverwrite ?? false;
|
||||
var res = await Storage.AddNewMafileFromData(mafile, overwrite);
|
||||
if (res == AddMafileResult.AlreadyExist && confirmOverwrite == null)
|
||||
{
|
||||
confirmOverwrite =
|
||||
await DialogsController.ShowConfirmCancelDialog(GetLocalization("ConfirmMafileOverwrite"));
|
||||
res = await Storage.AddNewMafileFromData(mafile, confirmOverwrite.Value);
|
||||
}
|
||||
|
||||
summary.Apply(res);
|
||||
items.Add(new MafileImportPlanItem(mafile, false, HasImportConflict(mafile)));
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
@@ -109,18 +109,11 @@ public partial class MainVM //File //TODO: Refactor
|
||||
{
|
||||
if (path.Length == 1 && ex.Mafile != null)
|
||||
{
|
||||
var mafile = ex.Mafile;
|
||||
if (await HandleAddMafileWithoutSession(mafile))
|
||||
{
|
||||
summary.AddedOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
summary.ErrorOne();
|
||||
}
|
||||
items.Add(new MafileImportPlanItem(ex.Mafile, true, HasImportConflict(ex.Mafile)));
|
||||
}
|
||||
else
|
||||
{
|
||||
summary.ErrorOne();
|
||||
SnackbarController.SendSnackbar(
|
||||
$"{GetLocalization("MafileImportError")} {Path.GetFileName(str)}{GetLocalization("MissingSessionInMafile")}",
|
||||
TimeSpan.FromSeconds(4));
|
||||
@@ -128,9 +121,78 @@ public partial class MainVM //File //TODO: Refactor
|
||||
}
|
||||
}
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
ShowImportSummary(summary);
|
||||
return;
|
||||
}
|
||||
|
||||
var conflictCount = items.Count(i => i.HasConflict);
|
||||
var showImportDialog = Settings.ConfirmMafileImport || conflictCount > 0;
|
||||
MafileImportDialogResult? importOptions = null;
|
||||
if (showImportDialog)
|
||||
{
|
||||
importOptions = await DialogsController.ShowMafileImportDialog(Groups, items.Count, conflictCount);
|
||||
if (importOptions == null)
|
||||
{
|
||||
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Canceled", "Canceled"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var importGroup = importOptions?.Group;
|
||||
var overwriteConflicts = importOptions?.OverwriteConflicts ?? false;
|
||||
foreach (var item in items)
|
||||
{
|
||||
ApplyImportGroup(item.Mafile, importGroup);
|
||||
if (item.RequiresRelogin)
|
||||
{
|
||||
if (item.HasConflict && !overwriteConflicts)
|
||||
{
|
||||
summary.SkippedOne();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (await HandleAddMafileWithoutSession(item.Mafile))
|
||||
{
|
||||
summary.AddedOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
summary.ErrorOne();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var res = await Storage.AddNewMafileFromData(item.Mafile, overwriteConflicts);
|
||||
summary.Apply(res);
|
||||
}
|
||||
|
||||
if (summary.Added > 0)
|
||||
{
|
||||
QueryGroups();
|
||||
if (!string.IsNullOrWhiteSpace(importGroup))
|
||||
{
|
||||
SelectedGroup = importGroup;
|
||||
}
|
||||
}
|
||||
|
||||
ShowImportSummary(summary);
|
||||
}
|
||||
|
||||
private static void ApplyImportGroup(Mafile mafile, string? group)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(group)) return;
|
||||
mafile.Group = group;
|
||||
}
|
||||
|
||||
private static bool HasImportConflict(Mafile mafile)
|
||||
{
|
||||
var fileName = MafilesStorageHelper.CreateMafileFileName(mafile, Settings.Instance.UseAccountNameAsMafileName);
|
||||
return File.Exists(Path.Combine(Storage.MafilesDirectory, fileName));
|
||||
}
|
||||
|
||||
|
||||
private async Task<MafileReadResult> TryReadMafile(string path, SDAEncryptionHelper.Context? sdaContext,
|
||||
bool sdaPasswordPrompted)
|
||||
@@ -213,6 +275,7 @@ public partial class MainVM //File //TODO: Refactor
|
||||
|
||||
var result = data.SessionData != null;
|
||||
if (!result) return result;
|
||||
data.Filename = null;
|
||||
await Storage.SaveMafileAsync(data);
|
||||
{
|
||||
ResetQuery();
|
||||
@@ -347,9 +410,4 @@ public partial class MainVM //File //TODO: Refactor
|
||||
if (mafile is not Mafile maf) return false;
|
||||
return maf.Password != null && PHandler.IsPasswordSet;
|
||||
}
|
||||
|
||||
private record MafileReadResult(
|
||||
Mafile? Mafile,
|
||||
bool SdaPasswordPrompted,
|
||||
SDAEncryptionHelper.Context? SdaContext);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -38,9 +39,10 @@ public partial class MainVM
|
||||
}
|
||||
|
||||
if (!system && SelectedMafile != null)
|
||||
|
||||
{
|
||||
var oldProxyId = SelectedMafile.Proxy?.Id;
|
||||
SelectedMafile.Proxy = SelectedProxy;
|
||||
ProxyAssignmentCache.UpdateAssignment(SelectedMafile.AccountName, oldProxyId, SelectedProxy?.Id);
|
||||
Storage.UpdateMafile(SelectedMafile);
|
||||
}
|
||||
|
||||
@@ -74,7 +76,9 @@ public partial class MainVM
|
||||
{
|
||||
mafile ??= SelectedMafile;
|
||||
if (mafile?.Proxy == null) return;
|
||||
var oldProxyId = mafile.Proxy.Id;
|
||||
mafile.Proxy = null;
|
||||
ProxyAssignmentCache.UpdateAssignment(mafile.AccountName, oldProxyId, null);
|
||||
if (mafile == SelectedMafile)
|
||||
SetProxy(null, false); //Not system, triggered by user
|
||||
}
|
||||
@@ -85,6 +89,38 @@ public partial class MainVM
|
||||
return mafile is {Proxy: not null};
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(AssignFreeProxyCanExecute))]
|
||||
private void AssignFreeProxy(Mafile? mafile)
|
||||
{
|
||||
mafile ??= SelectedMafile;
|
||||
if (mafile == null) return;
|
||||
|
||||
int? defaultProxyId = null;
|
||||
if (MaClient.DefaultProxy != null)
|
||||
{
|
||||
var defKvp = ProxyStorage.Proxies.FirstOrDefault(kvp => kvp.Value.Equals(MaClient.DefaultProxy));
|
||||
if (defKvp.Value != null)
|
||||
defaultProxyId = defKvp.Key;
|
||||
}
|
||||
|
||||
var freeProxy = ProxyAssignmentCache.GetRandomFreeProxy(ProxyStorage.Proxies, defaultProxyId);
|
||||
if (freeProxy == null) return;
|
||||
|
||||
var newProxy = new MaProxy(freeProxy.Value.Key, freeProxy.Value.Value);
|
||||
var oldProxyId = mafile.Proxy?.Id;
|
||||
mafile.Proxy = newProxy;
|
||||
ProxyAssignmentCache.UpdateAssignment(mafile.AccountName, oldProxyId, newProxy.Id);
|
||||
Storage.UpdateMafile(mafile);
|
||||
|
||||
if (mafile == SelectedMafile)
|
||||
SetProxy(newProxy, true); // system=true: update UI only, assignment already saved
|
||||
}
|
||||
|
||||
private bool AssignFreeProxyCanExecute(Mafile? mafile)
|
||||
{
|
||||
return ProxyStorage.Proxies.Count > 0;
|
||||
}
|
||||
|
||||
private void CheckProxyExist()
|
||||
{
|
||||
if (SelectedProxy == null)
|
||||
|
||||
@@ -11,5 +11,7 @@ public partial class LoginAgainVM : ObservableObject
|
||||
|
||||
[ObservableProperty] private bool _savePassword;
|
||||
|
||||
[ObservableProperty] private bool _showNoProxyWarning;
|
||||
|
||||
[ObservableProperty] private string _userName = null!;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
@@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Core;
|
||||
using NebulaAuth.Model.MafileExport;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
@@ -24,8 +25,7 @@ public partial class MafileExporterVM : ObservableObject
|
||||
|
||||
public ObservableCollection<MafileExportTemplateVM> Templates { get; }
|
||||
|
||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ExportCommand))]
|
||||
private string? _accountsText;
|
||||
[ObservableProperty] private string? _accountsText;
|
||||
|
||||
private string? _cachedName;
|
||||
private MafileExportTemplateVM? _currentTemplate;
|
||||
@@ -160,6 +160,7 @@ public partial class MafileExporterVM : ObservableObject
|
||||
IncludeNebulaProxy = true,
|
||||
IncludeNebulaPassword = true,
|
||||
IncludeNebulaGroup = true,
|
||||
ExportToZip = false,
|
||||
Path = null
|
||||
};
|
||||
MafileExporterStorage.AddTemplate(template);
|
||||
@@ -211,21 +212,26 @@ public partial class MafileExporterVM : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(ExportCanExecute))]
|
||||
[RelayCommand(CanExecute = nameof(CurrentTemplateNotNull))]
|
||||
private async Task Export()
|
||||
{
|
||||
var lines = AccountsText;
|
||||
var template = CurrentTemplate;
|
||||
if (string.IsNullOrWhiteSpace(lines) || template == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (template == null) return;
|
||||
|
||||
var split = Regex
|
||||
.Split(lines, "\r\n|\r|\n")
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.ToArray();
|
||||
string[] split;
|
||||
if (string.IsNullOrWhiteSpace(AccountsText))
|
||||
{
|
||||
split = Storage.MaFiles.Select(m => m.AccountName).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
split = Regex
|
||||
.Split(AccountsText, "\r\n|\r|\n")
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(ExtractLogin)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
ResetHintText();
|
||||
ExportResult res;
|
||||
@@ -268,6 +274,14 @@ public partial class MafileExporterVM : ObservableObject
|
||||
AccountsText = text;
|
||||
}
|
||||
|
||||
// Strips the password part from "login:password" input lines.
|
||||
// SteamId64 values contain no colon, so any colon signals the login:password format.
|
||||
private static string ExtractLogin(string line)
|
||||
{
|
||||
var idx = line.IndexOf(':');
|
||||
return idx > 0 ? line[..idx] : line;
|
||||
}
|
||||
|
||||
private void SetHintText(string? text, HintBoxSeverity severity = HintBoxSeverity.Error)
|
||||
{
|
||||
HintText = text;
|
||||
@@ -281,12 +295,6 @@ public partial class MafileExporterVM : ObservableObject
|
||||
|
||||
#region CanExecute
|
||||
|
||||
private bool ExportCanExecute()
|
||||
{
|
||||
return CurrentTemplateNotNull() && !string.IsNullOrWhiteSpace(AccountsText);
|
||||
}
|
||||
|
||||
|
||||
private bool CurrentTemplateNotNull()
|
||||
{
|
||||
return CurrentTemplate != null;
|
||||
@@ -307,6 +315,7 @@ public partial class MafileExporterVM : ObservableObject
|
||||
|
||||
public partial class MafileExportTemplateVM : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private bool _exportToZip;
|
||||
[ObservableProperty] private bool _includeIdentitySecret;
|
||||
[ObservableProperty] private bool _includeNebulaGroup;
|
||||
[ObservableProperty] private bool _includeNebulaPassword;
|
||||
@@ -334,6 +343,7 @@ public partial class MafileExportTemplateVM : ObservableObject
|
||||
IncludeNebulaProxy = x.IncludeNebulaProxy,
|
||||
IncludeNebulaPassword = x.IncludeNebulaPassword,
|
||||
IncludeNebulaGroup = x.IncludeNebulaGroup,
|
||||
ExportToZip = x.ExportToZip,
|
||||
Path = x.Path
|
||||
};
|
||||
}
|
||||
@@ -352,6 +362,7 @@ public partial class MafileExportTemplateVM : ObservableObject
|
||||
IncludeNebulaProxy = IncludeNebulaProxy,
|
||||
IncludeNebulaPassword = IncludeNebulaPassword,
|
||||
IncludeNebulaGroup = IncludeNebulaGroup,
|
||||
ExportToZip = ExportToZip,
|
||||
Path = Path
|
||||
};
|
||||
}
|
||||
@@ -368,6 +379,7 @@ public partial class MafileExportTemplateVM : ObservableObject
|
||||
model.IncludeNebulaProxy = IncludeNebulaProxy;
|
||||
model.IncludeNebulaPassword = IncludeNebulaPassword;
|
||||
model.IncludeNebulaGroup = IncludeNebulaGroup;
|
||||
model.ExportToZip = ExportToZip;
|
||||
model.Path = Path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public partial class MafileImportDialogVM : ObservableObject
|
||||
{
|
||||
public ObservableCollection<string> Groups { get; }
|
||||
public int TotalCount { get; }
|
||||
public int ConflictCount { get; }
|
||||
public bool HasConflicts => ConflictCount > 0;
|
||||
[ObservableProperty] private bool _addToGroup;
|
||||
[ObservableProperty] private string? _group;
|
||||
[ObservableProperty] private bool _overwriteConflicts;
|
||||
|
||||
public MafileImportDialogVM(IEnumerable<string> groups, int totalCount, int conflictCount)
|
||||
{
|
||||
Groups = new ObservableCollection<string>(groups);
|
||||
TotalCount = totalCount;
|
||||
ConflictCount = conflictCount;
|
||||
}
|
||||
}
|
||||
|
||||
public record MafileImportDialogResult(string? Group, bool OverwriteConflicts);
|
||||
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AchiesUtilities.Web.Proxy;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using NebulaAuth.Model;
|
||||
using NebulaAuth.Model.Entities;
|
||||
using NebulaAuth.Model.Mafiles;
|
||||
using SteamLibForked.Models.SteamIds;
|
||||
|
||||
namespace NebulaAuth.ViewModel.Other;
|
||||
|
||||
public partial class ProxyManagerVM
|
||||
{
|
||||
public int TotalAccounts => Storage.MaFiles.Count;
|
||||
public int TotalProxies => ProxyStorage.Proxies.Count;
|
||||
public int AssignedProxies => Storage.MaFiles.Count(m => m.Proxy != null);
|
||||
|
||||
[ObservableProperty] private string? _assignmentInput;
|
||||
[ObservableProperty] private string? _assignmentTip;
|
||||
|
||||
/// <summary>
|
||||
/// true = assign a random free proxy to unspecified accounts; false = remove proxy from unspecified accounts.
|
||||
/// </summary>
|
||||
[ObservableProperty] private bool _unspecifiedAssignRandom = true;
|
||||
|
||||
public void RefreshAssignmentCounters()
|
||||
{
|
||||
OnPropertyChanged(nameof(TotalAccounts));
|
||||
OnPropertyChanged(nameof(TotalProxies));
|
||||
OnPropertyChanged(nameof(AssignedProxies));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ApplyAssignment()
|
||||
{
|
||||
AssignmentTip = null;
|
||||
var input = AssignmentInput;
|
||||
if (string.IsNullOrWhiteSpace(input)) return;
|
||||
|
||||
var lines = input.Split(["\r\n", "\n"], StringSplitOptions.RemoveEmptyEntries);
|
||||
var mafs = Storage.MaFiles.ToList();
|
||||
var defaultProxyId = GetDefaultProxyId();
|
||||
|
||||
var success = 0;
|
||||
var errors = 0;
|
||||
var notFound = 0;
|
||||
|
||||
foreach (var rawLine in lines)
|
||||
{
|
||||
var line = rawLine.Trim();
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
string login;
|
||||
// resolvedProxy: null means unspecified (apply toggle behaviour)
|
||||
MaProxy? resolvedProxy;
|
||||
|
||||
var colonIdx = line.IndexOf(':');
|
||||
if (colonIdx >= 0)
|
||||
{
|
||||
login = line[..colonIdx].Trim();
|
||||
var proxyPart = line[(colonIdx + 1)..].Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(proxyPart))
|
||||
{
|
||||
resolvedProxy = null; // unspecified
|
||||
}
|
||||
else if (int.TryParse(proxyPart, out var parsedId))
|
||||
{
|
||||
// Mode: login:ID — proxy ID
|
||||
if (!ProxyStorage.Proxies.TryGetValue(parsedId, out var byId))
|
||||
{
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
resolvedProxy = new MaProxy(parsedId, byId);
|
||||
}
|
||||
else if (ProxyStorage.DefaultScheme.TryParse(proxyPart, out var parsedProxy) || ProxyStorage.SignAtScheme.TryParse(proxyPart, out parsedProxy))
|
||||
{
|
||||
// Mode: login:proxy_string — find existing or add
|
||||
resolvedProxy = FindOrAddProxy(parsedProxy);
|
||||
}
|
||||
else
|
||||
{
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
login = line;
|
||||
resolvedProxy = null; // unspecified
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(login))
|
||||
{
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var maf = FindMafile(mafs, login);
|
||||
if (maf == null)
|
||||
{
|
||||
notFound++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var newProxy = resolvedProxy ?? (UnspecifiedAssignRandom
|
||||
? BuildRandomFreeProxy(defaultProxyId)
|
||||
: null);
|
||||
|
||||
var oldProxyId = maf.Proxy?.Id;
|
||||
maf.Proxy = newProxy;
|
||||
ProxyAssignmentCache.UpdateAssignment(maf.AccountName, oldProxyId, newProxy?.Id);
|
||||
await Storage.UpdateMafileAsync(maf);
|
||||
success++;
|
||||
}
|
||||
catch
|
||||
{
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
AnyChanges = true;
|
||||
RefreshAssignmentCounters();
|
||||
AssignmentTip = BuildAssignmentTip(success, notFound, errors);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ClearAssignmentTip()
|
||||
{
|
||||
AssignmentTip = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a proxy equal to <paramref name="proxyData" /> in storage.
|
||||
/// If not found, adds it and returns the new entry.
|
||||
/// </summary>
|
||||
private static MaProxy FindOrAddProxy(ProxyData proxyData)
|
||||
{
|
||||
var existing = ProxyStorage.Proxies.FirstOrDefault(kvp => kvp.Value.Equals(proxyData));
|
||||
if (existing.Value != null)
|
||||
return new MaProxy(existing.Key, existing.Value);
|
||||
|
||||
ProxyStorage.SetProxy(null, proxyData);
|
||||
|
||||
// Fetch the entry we just added (SetProxy guarantees it was stored)
|
||||
var added = ProxyStorage.Proxies.First(kvp => kvp.Value.Equals(proxyData));
|
||||
return new MaProxy(added.Key, added.Value);
|
||||
}
|
||||
|
||||
private static MaProxy? BuildRandomFreeProxy(int? defaultProxyId)
|
||||
{
|
||||
var free = ProxyAssignmentCache.GetRandomFreeProxy(ProxyStorage.Proxies, defaultProxyId);
|
||||
return free.HasValue ? new MaProxy(free.Value.Key, free.Value.Value) : null;
|
||||
}
|
||||
|
||||
private static int? GetDefaultProxyId()
|
||||
{
|
||||
if (MaClient.DefaultProxy == null) return null;
|
||||
var defKvp = ProxyStorage.Proxies.FirstOrDefault(kvp => kvp.Value.Equals(MaClient.DefaultProxy));
|
||||
return defKvp.Value != null ? defKvp.Key : null;
|
||||
}
|
||||
|
||||
private static Mafile? FindMafile(List<Mafile> mafs, string login)
|
||||
{
|
||||
SteamId64? steamId = null;
|
||||
if (SteamId64.TryParse(login, out var id64))
|
||||
steamId = id64;
|
||||
if (steamId != null)
|
||||
return mafs.FirstOrDefault(m => m.SteamId == steamId || m.AccountName == login);
|
||||
return mafs.FirstOrDefault(m =>
|
||||
string.Equals(m.AccountName, login, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static string BuildAssignmentTip(int success, int notFound, int errors)
|
||||
{
|
||||
if (success > 0 && errors == 0 && notFound == 0)
|
||||
return string.Format(GetLocalizationOrDefault("AssignmentSuccess"), success);
|
||||
return string.Format(GetLocalizationOrDefault("AssignmentPartialSuccess"), success, notFound, errors);
|
||||
}
|
||||
}
|
||||
@@ -168,10 +168,10 @@ public partial class SettingsVM : ObservableObject
|
||||
}
|
||||
}
|
||||
|
||||
public bool LegacyMode
|
||||
public bool ConfirmMafileImport
|
||||
{
|
||||
get => Settings.LegacyMode;
|
||||
set => Settings.LegacyMode = value;
|
||||
get => Settings.ConfirmMafileImport;
|
||||
set => Settings.ConfirmMafileImport = value;
|
||||
}
|
||||
|
||||
public bool AllowAutoUpdate
|
||||
|
||||
@@ -54,33 +54,20 @@ public static class AdmissionHelper
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AddRefreshToken(container, sessionData.RefreshToken);
|
||||
container.SetSteamRefreshToken(sessionData.RefreshToken);
|
||||
|
||||
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
|
||||
container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, sessionData.SessionId, "/"));
|
||||
container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage, "/"));
|
||||
TransferCommunityCookies(container);
|
||||
foreach (var domain in SteamDomains.AuthDomains)
|
||||
foreach (var domain in SteamDomains.WebDomains)
|
||||
{
|
||||
var token = sessionData.GetToken(domain);
|
||||
if (token == null) continue;
|
||||
AddTokenCookie(container, token.Value);
|
||||
container.SetSteamAccessToken(token.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDomainCookie(this CookieContainer container, SteamDomain domain, SteamAuthToken token)
|
||||
{
|
||||
var uri = SteamDomains.GetDomainUri(domain);
|
||||
foreach (var cookie in container.GetCookies(uri)
|
||||
.Where(c => !c.Expired && c.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)))
|
||||
{
|
||||
cookie.Expired = true;
|
||||
}
|
||||
|
||||
AddTokenCookie(container, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear and set new session
|
||||
/// </summary>
|
||||
@@ -95,70 +82,22 @@ public static class AdmissionHelper
|
||||
return;
|
||||
}
|
||||
|
||||
AddRefreshToken(container, mobileSession.RefreshToken);
|
||||
container.SetSteamRefreshToken(mobileSession.RefreshToken);
|
||||
|
||||
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
|
||||
container.Add(community, new Cookie("steamid", mobileSession.SteamId.Steam64.ToString()));
|
||||
container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, mobileSession.SessionId));
|
||||
container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage));
|
||||
TransferCommunityCookies(container);
|
||||
foreach (var domain in SteamDomains.AuthDomains)
|
||||
foreach (var domain in SteamDomains.WebDomains)
|
||||
{
|
||||
var token = mobileSession.GetToken(domain);
|
||||
if (token == null) continue;
|
||||
AddTokenCookie(container, token.Value);
|
||||
var domainUri = SteamDomains.GetDomainUri(domain);
|
||||
container.SetSteamAccessTokenUnsafe(token.Value, domainUri);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear and set new session. Not recommended. Uses <see cref="IMobileSessionData.GetMobileToken()" /> for domain
|
||||
/// <see cref="SteamDomain.Community" /> instead of its own cookie. It's okay to use it only for confirmations. But
|
||||
/// Market, Trading and other pages won't be authorized
|
||||
/// </summary>
|
||||
public static void SetSteamMobileCookiesWithMobileToken(this CookieContainer container,
|
||||
IMobileSessionData? mobileSession,
|
||||
string setLanguage = "english")
|
||||
{
|
||||
container.ClearSteamCookies(setLanguage);
|
||||
container.AddMinimalMobileCookies();
|
||||
|
||||
if (mobileSession == null)
|
||||
{
|
||||
TransferCommunityCookies(container);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AddRefreshToken(container, mobileSession.RefreshToken);
|
||||
|
||||
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
|
||||
container.Add(community, new Cookie("steamid", mobileSession.SteamId.Steam64.ToString()));
|
||||
container.Add(community, new Cookie(SESSION_ID_COOKIE_NAME, mobileSession.SessionId));
|
||||
container.Add(community, new Cookie(LANGUAGE_COOKIE_NAME, setLanguage));
|
||||
TransferCommunityCookies(container);
|
||||
|
||||
var domainCookieSet = false;
|
||||
foreach (var domain in SteamDomains.AllDomains)
|
||||
{
|
||||
var token = mobileSession.GetToken(domain);
|
||||
if (token == null || token.Value.IsExpired) continue;
|
||||
if (domain == SteamDomain.Community)
|
||||
domainCookieSet = true;
|
||||
AddTokenCookie(container, token.Value);
|
||||
}
|
||||
|
||||
var mobileToken = mobileSession.GetMobileToken();
|
||||
if (!domainCookieSet && mobileToken is {IsExpired: false})
|
||||
{
|
||||
var domain = SteamDomains.GetDomainUri(SteamDomain.Community);
|
||||
container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, mobileToken.Value.SignedToken)
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = true,
|
||||
Expires = mobileToken.Value.Expires.ToLocalDateTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddMinimalMobileCookies(this CookieContainer container)
|
||||
{
|
||||
@@ -229,27 +168,93 @@ public static class AdmissionHelper
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddRefreshToken(CookieContainer container, SteamAuthToken token)
|
||||
/// <summary>
|
||||
/// Sets a Steam refresh token as a cookie to the specified cookie container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The added cookie will have its expiration set according to the token's expiration time. This
|
||||
/// method is typically used to enable authenticated requests to Steam services that require a refresh
|
||||
/// token.
|
||||
/// </remarks>
|
||||
/// <param name="container">The cookie container to which the Steam refresh token cookie will be added. Cannot be null.</param>
|
||||
/// <param name="token">
|
||||
/// The Steam authentication token to add as a refresh cookie. Must be of type Refresh or
|
||||
/// MobileRefresh.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentException">Thrown if the token is not of type Refresh or MobileRefresh.</exception>
|
||||
public static void SetSteamRefreshToken(this CookieContainer container, SteamAuthToken token)
|
||||
{
|
||||
if (token.Type is not (SteamAccessTokenType.Refresh or SteamAccessTokenType.MobileRefresh))
|
||||
throw new ArgumentException(
|
||||
$"Token must be of type Refresh or MobileRefresh. Provided token has type: {token.Type}",
|
||||
nameof(token));
|
||||
var refreshToken = token.SignedToken;
|
||||
container.Add(SteamLoginUri, new Cookie(REFRESH_COOKIE_NAME, refreshToken)
|
||||
SetSteamRefreshTokenUnsafe(container, token, SteamLoginUri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Steam refresh token as a cookie to the specified cookie container for the given domain.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method does not perform validation on the input parameters. Callers must ensure that the
|
||||
/// provided values are valid and appropriate for use.
|
||||
/// </remarks>
|
||||
/// <param name="container">The cookie container to which the refresh token cookie will be added. Cannot be null.</param>
|
||||
/// <param name="token">
|
||||
/// The Steam authentication token containing the signed token value and expiration information. Cannot
|
||||
/// be null.
|
||||
/// </param>
|
||||
/// <param name="domainUri">The URI of the domain for which the refresh token cookie should be set. Cannot be null.</param>
|
||||
public static void SetSteamRefreshTokenUnsafe(CookieContainer container, SteamAuthToken token, Uri domainUri)
|
||||
{
|
||||
container.Add(domainUri, new Cookie(REFRESH_COOKIE_NAME, token.SignedToken)
|
||||
{
|
||||
Expires = token.Expires.ToLocalDateTime()
|
||||
});
|
||||
}
|
||||
|
||||
public static void AddTokenCookie(CookieContainer container, SteamAuthToken token)
|
||||
/// <summary>
|
||||
/// Sets a Steam access token to the specified cookie container for use with Steam web requests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is intended for standard web access tokens bound to a specific Steam web domain.
|
||||
/// Mobile tokens are intentionally rejected, since their audiences are capability-based rather
|
||||
/// than domain-based and may grant access to multiple web domains.
|
||||
/// </remarks>
|
||||
/// <param name="container">The cookie container to which the Steam access token will be added. Cannot be null.</param>
|
||||
/// <param name="token">
|
||||
/// The Steam access token to add. Must be of type AccessToken and associated with a valid Steam
|
||||
/// domain.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentException">Thrown if the token is not of type AccessToken.</exception>
|
||||
public static void SetSteamAccessToken(this CookieContainer container, SteamAuthToken token)
|
||||
{
|
||||
if (token.Type == SteamAccessTokenType.Mobile)
|
||||
throw new ArgumentException(
|
||||
$"Mobile access tokens cannot be added using this method. Use SetSteamAccessTokenUnsafe instead. Provided token has type: {token.Type}",
|
||||
nameof(token));
|
||||
if (token.Type is not SteamAccessTokenType.AccessToken)
|
||||
throw new ArgumentException($"Token must be of type AccessToken. Provided token has type: {token.Type}",
|
||||
nameof(token));
|
||||
|
||||
var domain = SteamDomains.GetDomainUri(token.Domain);
|
||||
container.Add(domain, new Cookie(ACCESS_COOKIE_NAME, token.SignedToken)
|
||||
var domainUri = SteamDomains.GetDomainUri(token.Domain);
|
||||
container.SetSteamAccessTokenUnsafe(token, domainUri);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets a Steam access token as a secure, HTTP-only cookie to the specified cookie container for the given domain.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method does not perform validation on the input parameters and should only be used when
|
||||
/// input values are trusted. The added cookie is marked as secure and HTTP-only, and its expiration is set
|
||||
/// according to the token's expiration time.
|
||||
/// </remarks>
|
||||
/// <param name="container">The cookie container to which the Steam access token cookie will be added. Cannot be null.</param>
|
||||
/// <param name="token">The Steam access token to add as a cookie. Must contain a valid signed token and expiration.</param>
|
||||
/// <param name="domainUri">The URI of the domain for which the cookie will be set. Cannot be null.</param>
|
||||
public static void SetSteamAccessTokenUnsafe(this CookieContainer container, SteamAuthToken token, Uri domainUri)
|
||||
{
|
||||
container.Add(domainUri, new Cookie(ACCESS_COOKIE_NAME, token.SignedToken)
|
||||
{
|
||||
HttpOnly = true,
|
||||
Secure = true,
|
||||
@@ -257,7 +262,6 @@ public static class AdmissionHelper
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static bool IsSteamCookie(Cookie cookie)
|
||||
{
|
||||
return cookie.Domain.Contains("steamcommunity.com") || cookie.Domain.Contains("steampowered.com") ||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Immutable;
|
||||
using SteamLibForked.Models.Core;
|
||||
|
||||
namespace SteamLib.Core;
|
||||
@@ -6,44 +6,38 @@ namespace SteamLib.Core;
|
||||
public static class SteamDomains
|
||||
{
|
||||
public static IReadOnlyDictionary<SteamDomain, string> Domains { get; } =
|
||||
new ReadOnlyDictionary<SteamDomain, string>(
|
||||
new Dictionary<SteamDomain, string>
|
||||
{
|
||||
{SteamDomain.Community, SteamConstants.STEAM_COMMUNITY},
|
||||
{SteamDomain.Store, SteamConstants.STEAM_STORE},
|
||||
{SteamDomain.Help, SteamConstants.STEAM_HELP},
|
||||
{SteamDomain.TV, SteamConstants.STEAM_TV},
|
||||
{SteamDomain.Checkout, SteamConstants.STEAM_CHECKOUT},
|
||||
{SteamDomain.Login, SteamConstants.STEAM_LOGIN},
|
||||
{SteamDomain.API, SteamConstants.STEAM_API}
|
||||
});
|
||||
new Dictionary<SteamDomain, string>
|
||||
{
|
||||
{SteamDomain.Community, SteamConstants.STEAM_COMMUNITY},
|
||||
{SteamDomain.Store, SteamConstants.STEAM_STORE},
|
||||
{SteamDomain.Help, SteamConstants.STEAM_HELP},
|
||||
{SteamDomain.TV, SteamConstants.STEAM_TV},
|
||||
{SteamDomain.Checkout, SteamConstants.STEAM_CHECKOUT},
|
||||
{SteamDomain.Login, SteamConstants.STEAM_LOGIN},
|
||||
{SteamDomain.API, SteamConstants.STEAM_API}
|
||||
}.ToImmutableDictionary();
|
||||
|
||||
public static IReadOnlyDictionary<SteamDomain, Uri> DomainUris { get; }
|
||||
= new ReadOnlyDictionary<SteamDomain, Uri>(
|
||||
Domains.ToDictionary(x => x.Key, x => new Uri(x.Value))
|
||||
);
|
||||
= Domains
|
||||
.ToDictionary(x => x.Key, x => new Uri(x.Value))
|
||||
.ToImmutableDictionary();
|
||||
|
||||
|
||||
public static IEnumerable<SteamDomain> AllDomains { get; } =
|
||||
[
|
||||
SteamDomain.Community,
|
||||
SteamDomain.Store,
|
||||
SteamDomain.Help,
|
||||
SteamDomain.TV,
|
||||
SteamDomain.Checkout,
|
||||
SteamDomain.Login,
|
||||
SteamDomain.API
|
||||
];
|
||||
|
||||
public static IEnumerable<SteamDomain> AuthDomains { get; } =
|
||||
[
|
||||
SteamDomain.Community,
|
||||
SteamDomain.Store,
|
||||
SteamDomain.Help,
|
||||
SteamDomain.TV,
|
||||
SteamDomain.Checkout
|
||||
];
|
||||
/// <summary>
|
||||
/// All known public Steam domains.
|
||||
/// </summary>
|
||||
public static IEnumerable<SteamDomain> AllDomains { get; } = ImmutableHashSet.Create(SteamDomain.Community,
|
||||
SteamDomain.Store, SteamDomain.Help, SteamDomain.TV, SteamDomain.Checkout, SteamDomain.Login, SteamDomain.API);
|
||||
|
||||
/// <summary>
|
||||
/// Steam web domains that participate in the standard login authorization flow.
|
||||
/// <para>
|
||||
/// These domains use the <c>web:*</c> audience format and receive
|
||||
/// authentication cookies/tokens during session initialization.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static IReadOnlySet<SteamDomain> WebDomains { get; } = ImmutableHashSet.Create(SteamDomain.Community,
|
||||
SteamDomain.Store, SteamDomain.Help, SteamDomain.TV, SteamDomain.Checkout);
|
||||
|
||||
public static Uri GetDomainUri(SteamDomain domain)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using SteamLib.Core;
|
||||
using SteamLibForked.Abstractions;
|
||||
using SteamLibForked.Models.Core;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace SteamLibForked.Models.Session;
|
||||
|
||||
//WARNING: Any changes here should be reflected in MafileSerializer.cs
|
||||
|
||||
public sealed class MobileSessionData : SessionData, IMobileSessionData
|
||||
public class MobileSessionData : SessionData, IMobileSessionData
|
||||
{
|
||||
public SteamAuthToken? MobileToken { get; private set; }
|
||||
|
||||
@@ -31,9 +32,23 @@ public sealed class MobileSessionData : SessionData, IMobileSessionData
|
||||
return MobileToken;
|
||||
}
|
||||
|
||||
public override SteamAuthToken? GetToken(SteamDomain domain)
|
||||
{
|
||||
var isWeb = SteamDomains.WebDomains.Contains(domain);
|
||||
if (isWeb)
|
||||
{
|
||||
// Mobile-issued tokens usually contain the "web" audience,
|
||||
// so we assume they can also be used for all web:* domains.
|
||||
// See: SteamAuthToken 'TODO' for more details
|
||||
return MobileToken ?? base.GetToken(domain);
|
||||
}
|
||||
|
||||
return base.GetToken(domain);
|
||||
}
|
||||
|
||||
|
||||
[MemberNotNull(nameof(MobileToken))]
|
||||
public void SetMobileToken(SteamAuthToken token)
|
||||
public virtual void SetMobileToken(SteamAuthToken token)
|
||||
{
|
||||
if (token.Type != SteamAccessTokenType.Mobile)
|
||||
throw new ArgumentException("Token must be of type MobileAccess", nameof(token))
|
||||
@@ -44,13 +59,13 @@ public sealed class MobileSessionData : SessionData, IMobileSessionData
|
||||
MobileToken = token;
|
||||
}
|
||||
|
||||
public override MobileSessionData Clone()
|
||||
{
|
||||
return (MobileSessionData) ((ISessionData) this).Clone();
|
||||
}
|
||||
|
||||
object ICloneable.Clone()
|
||||
{
|
||||
return new MobileSessionData(SessionId, SteamId, RefreshToken, MobileToken, Tokens);
|
||||
}
|
||||
|
||||
public override MobileSessionData Clone()
|
||||
{
|
||||
return (MobileSessionData) ((ISessionData) this).Clone();
|
||||
}
|
||||
}
|
||||
@@ -80,4 +80,10 @@ public static class TimeAligner
|
||||
client.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTimeDifference(int timeDifference)
|
||||
{
|
||||
_timeDifference = timeDifference;
|
||||
_aligned = true;
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,11 @@ namespace SteamLib.Utility.MafileSerialization;
|
||||
|
||||
internal class LegacyMafile
|
||||
{
|
||||
[JsonProperty("shared_secret")]
|
||||
public string SharedSecret { get; set; } = null!;
|
||||
[JsonProperty("shared_secret")] public string SharedSecret { get; set; } = null!;
|
||||
|
||||
[JsonProperty("identity_secret")]
|
||||
public string IdentitySecret { get; set; } = null!;
|
||||
[JsonProperty("identity_secret")] public string IdentitySecret { get; set; } = null!;
|
||||
|
||||
[JsonProperty("device_id")]
|
||||
public string DeviceId { get; set; } = null!;
|
||||
[JsonProperty("device_id")] public string DeviceId { get; set; } = null!;
|
||||
|
||||
[JsonProperty("revocation_code")] public string RevocationCode { get; set; } = null!;
|
||||
[JsonProperty("account_name")] public string AccountName { get; set; } = null!;
|
||||
|
||||
@@ -31,7 +31,7 @@ public static class ClientBuilder
|
||||
}
|
||||
else
|
||||
{
|
||||
container.SetSteamMobileCookiesWithMobileToken(sessionData);
|
||||
container.SetSteamMobileCookies(sessionData);
|
||||
}
|
||||
|
||||
//Nebula tweak:
|
||||
|
||||
Reference in New Issue
Block a user