Compare commits

...

13 Commits

Author SHA1 Message Date
Achies 7348bc2673 Merge branch 'dev' into feat/group-on-import 2026-06-19 19:03:16 +03:00
Achies 3a9d929c9a Merge pull request #23 from achiez/fix/mafile-mover-loc
fix(localization): update mafile mover Steam Guard error message
2026-06-19 19:01:40 +03:00
Achies 25818997af Merge pull request #22 from achiez/feat/proxy-assignment
feat(proxy): add bulk assignment and proxy usage stats
2026-06-19 19:00:47 +03:00
achiez 537d5d907f feat(import): add group assignment and skip-confirmation setting; remove obsolete format setting
- Add ability to assign imported mafiles to a group directly from the import dialog
- Add setting to skip the import confirmation dialog when no conflicts are detected
- Remove the deprecated "legacy mafile format" setting as it is no longer relevant
2026-06-19 18:54:37 +03:00
achiez 0e90f1826b fix(localization): update mafile mover Steam Guard error message 2026-05-19 22:42:58 +03:00
achiez b5d3b4a8af feat(proxy): add bulk assignment and proxy usage stats
- Implement bulk proxy assignment for accounts with flexible input (login:proxy, login:ID, or login)
- Add UI for mass assignment with counters and behavior selection for unspecified proxies
- Show assigned account count badges in proxy list for better visibility
- Enable quick assignment of a free proxy to an account
- Ensure fast and consistent proxy assignment state via in-memory cache
- Perform ReSharper cleanup
2026-05-14 16:44:14 +03:00
achiez 30cf049f23 refactor(auth): redesign Steam audience and web domain authorization model
- Rename AuthorizedDomains to WebDomains
- Introduce explicit Steam audience constants and web audience mappings
- Improve Steam token/domain resolution and cookie installation flow
- Allow mobile-issued tokens with "web" audience to be used for web domains
- Align authorization logic closer to actual Steam audience behavior
- Rename cookie APIs from Add* to Set* where appropriate
2026-05-12 16:36:37 +03:00
achiez ce55005d44 chore: remove outdated README files and update main README with new features and documentation link 2026-05-04 19:37:37 +03:00
github-actions a23d7017a7 chore(release): 1.8.4 2026-03-14 13:36:41 +00:00
achiez c879dd462c fix(build): include NebulaAuth.exe in release package
Fix CI packaging configuration to ensure NebulaAuth.exe is included
in the published release artifacts.
2026-03-14 15:36:02 +02:00
github-actions e681ca07f1 chore(release): 1.8.3 2026-03-13 13:37:20 +00:00
Achies 9227b383bb Merge pull request #20 from achiez/1.8.3
Release 1.8.3
2026-03-13 15:36:47 +02:00
Achies a3804dd48d Update README.md
Add copyright disclaimer
2026-03-12 15:41:38 +02:00
43 changed files with 1314 additions and 538 deletions
+5 -3
View File
@@ -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
View File
@@ -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" />
+5 -4
View File
@@ -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.4</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.8.4/NebulaAuth.1.8.4.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.8.4.html</changelog>
<mandatory>false</mandatory>
<checksum algorithm="SHA256">5ecad7a711bab7e98f5f3bfa92b7a00e21efc18aa92251bb30bd3e50a2f7d2f2</checksum>
</item>
-74
View File
@@ -1,74 +0,0 @@
# NebulaAuth
## Описание
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Скачать последнюю версию</a>
</h3>
<h3 align="center">NebulaAuth — это приложение для эмуляции действий из мобильного приложения Steam. Которая заменяет ваш смартфон при работе в Steam.</h3>
<h4 align="center"><a href="https://t.me/nebulaauth">Официальная группа в Telegram</a></h4>
## Основные преимущества
- **Локализация на трёх языках**: английском, русском и украинском.
- **Полная функциональность Steam Desktop Authenticator**, переосмысление [старого приложения](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Поддержка прокси** во всех процессах работы с аккаунтом.
- **Группировка мафайлов** для продвинутого контроля.
- **Автоматическое подтверждение трейдов/действий на ТП** для экономии времени.
- **Массовый импорт мафайлов** с помощью Drag'n'Drop или CTRL+V для удобства.
- **Настройка внешнего вида** для персонализации интерфейса.
- **Возможность подтвердить вход в учетную запись без ввода кода** для облегчения доступа.
- **Автообновление** программы для использования новейших функций.
- **Автоматический повторный вход в случае проблем с сессией** для непрерывной работы.
- **Интуитивно понятный интерфейс** с подсказками и удобствами
- **Постоянная поддержка** кода приложения и других функций.
## Установка
1. Если приложение не запускается, необходимо установить [.NET Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
2. [Скачать программу из релизов этого репозитория на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
* *Для сохранности ваших данных скачивайте приложение только отсюда*
4. Распакуйте ZIP-файл в любую папку.
5. Запустите файл **NebulaAuth.exe**.
## Использование
![gh-main-window-rus](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/6a84f414-0e24-40a4-8adb-f1923fbe8719)
1. Панель управления.
- управление файлами и настройки
- управление аккаунтом (вход, привязка, отвязка)
- группировка
- выбор прокси
- индикатор с подсказкой об используемом прокси (горит либо желтым, либо красным, при наведении отобразит дополнительную информацию)
- таймеры для автоматического подтверждения трейдов/продаж на торговой площадке
- интервал таймера подтверждений (в секундах)
2. Список ваших аккаунтов
3. Код подтверждения входа (нажмите, чтобы скопировать)
4. Главное окно подтверждений
5. Поиск по логину или SteamID (7xxxxxxxxxxxxx)
6. Подтвердить вход на другогом устройстве.
7. Гиперссылка на официальную страницу приложения с указанием авторства.
## Настройки
![gh-settings-rus](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/33246ed1-1e3d-4310-88c5-085e5c50bc6b)
1. Режим фона. Используйте, если вы хотите отключить фон или установить собственный (поместите файл «Background.png» в папку приложения)
2. Язык приложения
3. Отключить таймеры при переключении между аккаунтами
4. Скрывать в трей при сворачивании
5. Индикатор с цветом. Маленький кружочек на значке панели задач с произвольным цветом. Полезно при использовании нескольких окон.
6. Пользовательский цвет приложения.
7. Текущий пароль шифрования. Если установлено, вы можете сохранять зашифрованные пароли в mafile, чтобы облегчить повторный вход в систему при проблемах с сессией. (Не рекомендуется)
8. Режим устаревших файлов. Режим совместимости Mafile с другими клиентами (SDA и т.д.). Если установлено, приложение будет сохранять файлы в старом стандартном формате (по умолчанию: включено).
9. Разрешить автообновление без подтверждения
## [Лицензия](/LICENSE.md)
Коммерческое использование запрещено. При распространении измененного кода необходимо указывать оригинальное авторство.
-71
View File
@@ -1,71 +0,0 @@
# NebulaAuth
## Опис
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Завантажити останню версію</a>
</h3>
<h3 align="center">NebulaAuth - це програма для емуляції дій з мобільного додатку Steam. Яка замінює ваш смартфон під час роботи в Steam.</h3>
<h4 align="center"><a href="https://t.me/nebulaauth">Офіційна група в Telegram</a></h4>
## Основні переваги
- **Локалізація трьома мовами**: англійською, російською та українською.
- **Повна функціональність Steam Desktop Authenticator** переосмислення [старої програми](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Підтримка проксі** у всіх процесах роботи з обліковим записом.
- **Угруповання мафайлів** для просунутого контролю.
- **Автоматичне підтвердження трейдів/дій на маркеті** для економії часу.
- **Масовий імпорт мафайлів** за допомогою Drag'n'Drop або CTRL+V для зручності.
- **Налаштування зовнішнього вигляду** для персоналізації інтерфейсу.
- **Можливість підтвердити вхід до облікового запису без введення коду** для полегшення доступу.
- **Автооновлення** програми для використання новітніх функцій.
- **Автоматичний повторний вхід у разі проблем із сесією** для безперервної роботи.
- **Інтуїтивно зрозумілий інтерфейс** з підказками та зручностями
- **Постійна підтримка** коду програми та інших функцій.
## Монтаж
1. Якщо програма не запускається, необхідно встановити [.NET Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
2. [Завантажити програму з релізів цього репозиторію на Github](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest)
* *Для збереження ваших даних завантажуйте програму тільки звідси*
4. Розпакуйте ZIP-файл у будь-яку папку.
5. Запустіть файл **NebulaAuth.exe**.
## Використання
![gh-main-window-ua](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/bf61ac61-b21c-4589-9b5a-751a6d983120)
1. Панель керування.
- керування файлами та налаштування
- керування акаунтом (вхід, прив'язка, відв'язування)
- угруповання
- вибір проксі
- індикатор з підказкою про проксі (світиться або жовтим, або червоним, при наведенні відобразить додаткову інформацію)
- таймери для автоматичного підтвердження трейдів/продажів на маркеті
- інтервал таймера підтверджень (у секундах)
2. Список ваших облікових записів
3. Код підтвердження входу (натисніть, щоб скопіювати)
4. Головне вікно підтвердження
5. Пошук за логіном або SteamID (7xxxxxxxxxxxxx)
6. Підтвердити вхід на іншому пристрою.
7. Гіперпосилання на офіційну сторінку додатку із зазначенням авторства.
## Налаштування
![gh-settings-ua](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/3f57e58d-d647-42a1-a4c9-ca53ab7b38a3)
1. Режим фону. Використовуйте, якщо ви хочете вимкнути фон або встановити власний (помістіть файл "Background.png" у папку програми)
2. Мова локалізації
3. Вимкнути таймери під час перемикання між обліковими записами
4. Приховувати у трей при згортанні
5. Індикатор із кольором. Маленький кружечок на піктограмі панелі завдань з довільним кольором. Корисно при використанні кількох вікон.
6. Власний колір програми.
7. Поточний пароль шифрування. Якщо встановлено, ви можете зберігати зашифровані паролі в mafile, щоб полегшити повторний вхід до системи у разі проблеми з сесією. (Не рекомендується)
8. Режим застарілих файлів. Режим сумісності Mafile з іншими клієнтами (SDA тощо). Якщо встановлено, програма зберігатиме файли у старому стандартному форматі (за замовчуванням: увімкнено).
9. Дозволити автооновлення без підтвердження
## [Ліцензія](/LICENSE.md)
Комерційне використання заборонено. У разі поширення зміненого коду необхідно вказувати оригінальне авторство.
+25 -46
View File
@@ -1,36 +1,44 @@
# NebulaAuth
* [Русский](README-RU.md)
* [Українська](README-UA.md)
## Description
<h3 align="center" style="margin-bottom:0">
<a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/latest">Download latest version</a>
</h3>
<h3 align="center">NebulaAuth is an application for emulating actions from the Steam Mobile App. Which replaces your smartphone when operating on Steam. </h3>
<h3 align="center">
NebulaAuth is an application for emulating actions from the Steam Mobile App, replacing your smartphone when operating on Steam.
</h3>
<p align="center">
<sub>NebulaAuth is an independent project and is not affiliated with Valve or Steam.</sub>
</p>
<h4 align="center"><a href="https://t.me/nebulaauth">Official Telegram Group</a></h4>
<h4 align="center"><a href="https://achiefy-project.gitbook.io/nebulaauth">Documentation</a></h4>
## Main advantages
- **Localization in three languages**: English, Russian and Ukrainian.
<p align="center">
<img src="misc/main_window.png" width="600"/>
</p>
- **Localization in six languages**: English, Russian, Ukrainian, Spanish, Turkish and Kazakh.
- **Full functionality of Steam Desktop Authenticator** reimagining [old app](https://github.com/Jessecar96/SteamDesktopAuthenticator)
- **Proxy support** in all account work processes.
- **Mafile grouping** for improved management.
- **Automatic confirmations of trades/market actions** to save time.
- **Bulk import of .mafiles** via Drag'n'Drop or CTRL+V for convenience.
- **Bulk import of .mafiles** via Drag'n'Drop or Ctrl+V, including SDA-encrypted mafiles with automatic manifest detection.
- **Design customization** to personalize the interface.
- **Ability to confirm account login without entering a code** for easier access.
- **Auto-update** program to use the latest features.
- **Auto-update** with SHA256 checksum verification, changelog viewer and flexible update options.
- **Automatic relogin in case of problems with the session** for continuous operation.
- **Intuitive interface** with tips and conveniences
- **Continious support** of application code and other features.
- **Intuitive interface** with tips and conveniences.
- **Continuous support** of application code and other features.
## Documentation
You can find the documentation for the application [here](https://achiefy-project.gitbook.io/nebulaauth). Currently, only the Russian version is available, but the English version will be released soon.
## Installation
1. If the application does not start, you need to install [.NET Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
@@ -39,40 +47,11 @@
4. Unpack the .zip file to any folder
5. Run the file **NebulaAuth.exe**
## Usage
![gh-main-window-eng](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/15c0e870-1766-43a5-9e8c-2f34d5436beb)
1. Control panel.
- file management and settings
- account management (login, linking, unlinking)
- grouping
- proxy selection
- an indicator with a hint about the proxy used (lit either yellow or red, when hovered it will display additional information)
- timers for automatic confirmation of trade offers/sale offers on the marketplace
- confirmation timer interval (in seconds)
2. List of your accounts
3. Login confirmation code (click to copy)
4. Main confirmation window
5. Search by login or SteamID (7xxxxxxxxxxxxx)
6. Confirm login on another device
7. Hyperlink to the official application page with attribution
## Project Ownership
## Settings
![gh-settings-eng](https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/assets/106531132/cd704495-d2df-45a7-a73e-40c19410eb72)
NebulaAuth is the original project by Achiefy™.
1. Background mode. Use it if you want to disable default or set custom background of application (put file 'Background.png' to your application folder)
2. Localization language
3. Disable timers when switching between accounts
4. Hide to tray on minimize
5. Indicator with color. Small ellipse on your task-bar icon with custom color. Useful when using multiple windows
6. Custom background color of application
7. Current encryption password. If set you can save encrypted passwords to mafile to help re-login on session troubles. (Not recommended)
8. Legacy mafile mode. Mafile compability mode for another applications (SDA and etc). If checked application will save mafiles with old standart format (Default: checked)
9. Allow auto-update without confirmation
Please keep attribution and do not present modified versions as official.
## [License](/LICENSE.md)
Commercial use prohibited. When redistributing modified code, you must indicate the original authorship.
AGPL-3.0 — see [LICENSE](/LICENSE).
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Changelog</title>
<style>
body { font-family: Segoe UI, sans-serif; background:#eeeeee; padding:20px; }
.change { background:white; padding:25px; border-radius:10px; }
li { margin-bottom:6px; }
</style>
</head>
<body>
<div class="change">
<ul>
<li><b>NEW:</b> Introduced a redesigned update system with a custom update dialog and integrated changelog viewer <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/19">details</a></li>
<li><b>NEW:</b> Added support for importing SDA-encrypted mafiles with automatic manifest detection <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/17">details</a></li>
<li><b>NEW:</b> Grouped market confirmations can now be expanded to reveal individual items <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/18">details</a></li>
<li><b>SECURITY:</b> Added SHA256 checksum verification for downloaded update packages</li>
<li><b>IMPROVEMENT:</b> Export feature now trims input automatically to prevent issues caused by invisible characters or extra spaces</li>
<li><b>IMPROVEMENT:</b> Improved update experience with 'Remind later' and 'Skip version' options</li>
<li><b>IMPROVEMENT:</b> Added visual update indicator and manual 'Check for updates' action</li>
<li><b>IMPROVEMENT:</b> Expanded localization support with Spanish, Turkish and Kazakh languages</li>
<li><b>INFO:</b> Read the full release notes for NebulaAuth 1.8.3 <a href="https://teletype.in/@achies_raw/o-RDwZKZkAU">details</a></li>
</ul>
</div>
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Changelog</title>
<style>
body { font-family: Segoe UI, sans-serif; background:#eeeeee; padding:20px; }
.change { background:white; padding:25px; border-radius:10px; }
li { margin-bottom:6px; }
</style>
</head>
<body>
<div class="change">
<ul>
<li><b>FIX:</b> Rebuilt release package to include the missing NebulaAuth.exe file. Version 1.8.4 is functionally identical to 1.8.3 and only fixes the packaging issue that caused the executable to be absent in the previous release.</li>
<li><b>NEW:</b> Introduced a redesigned update system with a custom update dialog and integrated changelog viewer <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/19">details</a></li>
<li><b>NEW:</b> Added support for importing SDA-encrypted mafiles with automatic manifest detection <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/17">details</a></li>
<li><b>NEW:</b> Grouped market confirmations can now be expanded to reveal individual items <a href="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/18">details</a></li>
<li><b>SECURITY:</b> Added SHA256 checksum verification for downloaded update packages</li>
<li><b>IMPROVEMENT:</b> Export feature now trims input automatically to prevent issues caused by invisible characters or extra spaces</li>
<li><b>IMPROVEMENT:</b> Improved update experience with 'Remind later' and 'Skip version' options</li>
<li><b>IMPROVEMENT:</b> Added visual update indicator and manual 'Check for updates' action</li>
<li><b>IMPROVEMENT:</b> Expanded localization support with Spanish, Turkish and Kazakh languages</li>
<li><b>INFO:</b> Read the full release notes for NebulaAuth 1.8.3 <a href="https://teletype.in/@achies_raw/o-RDwZKZkAU">details</a></li>
</ul>
</div>
</body>
</html>
+50
View File
@@ -0,0 +1,50 @@
{
"version": "1.8.4",
"date": "2026-03-14",
"changes": [
{
"type": "FIX",
"text": "Rebuilt release package to include the missing NebulaAuth.exe file. Version 1.8.4 is functionally identical to 1.8.3 and only fixes the packaging issue that caused the executable to be absent in the previous release."
},
{
"type": "NEW",
"text": "Introduced a redesigned update system with a custom update dialog and integrated changelog viewer",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/19"
},
{
"type": "NEW",
"text": "Added support for importing SDA-encrypted mafiles with automatic manifest detection",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/17"
},
{
"type": "NEW",
"text": "Grouped market confirmations can now be expanded to reveal individual items",
"link": "https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/pull/18"
},
{
"type": "SECURITY",
"text": "Added SHA256 checksum verification for downloaded update packages"
},
{
"type": "IMPROVEMENT",
"text": "Export feature now trims input automatically to prevent issues caused by invisible characters or extra spaces"
},
{
"type": "IMPROVEMENT",
"text": "Improved update experience with 'Remind later' and 'Skip version' options"
},
{
"type": "IMPROVEMENT",
"text": "Added visual update indicator and manual 'Check for updates' action"
},
{
"type": "IMPROVEMENT",
"text": "Expanded localization support with Spanish, Turkish and Kazakh languages"
},
{
"type": "INFO",
"text": "Read the full release notes for NebulaAuth 1.8.3",
"link": "https://teletype.in/@achies_raw/o-RDwZKZkAU"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

-8
View File
@@ -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>
@@ -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();
}
}
+17 -1
View File
@@ -73,6 +73,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();
@@ -174,4 +190,4 @@ public static class DialogsController
}
#endregion
}
}
+63 -1
View File
@@ -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": "Бар немесе жаңа топ"
}
}
}
}
@@ -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 бойынша сәрсенбі түні) пайда болатын және авторастау таймерлерін қажетсіз өшіретін қателерді елемеу"
}
}
}
}
+10 -10
View File
@@ -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": {
+3
View File
@@ -420,6 +420,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>
@@ -62,7 +62,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable
SetStatus(newStatus);
ClientHandler.CookieContainer.ClearAllCookies();
ClientHandler.CookieContainer.SetSteamMobileCookiesWithMobileToken(sessionData);
ClientHandler.CookieContainer.SetSteamMobileCookies(sessionData);
}
+1 -1
View File
@@ -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);
}
+2 -7
View File
@@ -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);
}
}
@@ -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)
+2 -2
View File
@@ -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;
@@ -127,4 +127,4 @@ public enum ThemeType
Light = 2,
Luxury = 3,
Shadcn = 4
}
}
+1
View File
@@ -45,6 +45,7 @@ public static class Shell
var threads = Environment.ProcessorCount > 0 ? Environment.ProcessorCount : 1;
await Storage.Initialize(threads);
ProxyAssignmentCache.Initialize(Storage.MaFiles);
MAACStorage.Initialize();
MafileExporterStorage.Initialize();
+1 -1
View File
@@ -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.4</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
+3
View File
@@ -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>
@@ -0,0 +1,129 @@
<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"
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" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Margin="10,10,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock FontStyle="Normal" Foreground="{DynamicResource BaseContentBrush}"
HorizontalAlignment="Left"
VerticalAlignment="Center" FontSize="18" Text="{Tr MafileImportDialog.Title}" />
<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="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();
}
}
+238 -126
View File
@@ -20,12 +20,12 @@
FontSize="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Title -->
<Grid Margin="10,10,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -45,90 +45,41 @@
</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='&#x0a;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>
<!-- Tabs -->
<TabControl Grid.Row="2"
md:ColorZoneAssist.Background="{DynamicResource Base100Brush}"
Style="{StaticResource MaterialDesignUniformTabControl}">
</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" />
<!-- 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='&#x0a;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 +87,227 @@
</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&#x0a;IP:PORT:USER:PASS&#x0a;PROTOCOL://IP:PORT:USER:PASS&#x0a;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&#x0a;IP:PORT:USER:PASS&#x0a;PROTOCOL://IP:PORT:USER:PASS&#x0a;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&#x0a;login:IP:PORT:USER:PASS&#x0a;login:socks5://USER:PASS@IP:PORT&#x0a;login:ID&#x0a;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>
+4 -4
View File
@@ -59,9 +59,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}">
@@ -196,4 +196,4 @@
</TabControl>
</Grid>
</Grid>
</UserControl>
</UserControl>
+1 -1
View File
@@ -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]
+84 -21
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
@@ -25,6 +26,11 @@ namespace NebulaAuth.ViewModel;
public partial class MainVM //File //TODO: Refactor
{
private record MafileReadResult(
Mafile? Mafile,
bool SdaPasswordPrompted,
SDAEncryptionHelper.Context? SdaContext);
public Settings Settings => Settings.Instance;
@@ -71,10 +77,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 +97,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 +107,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 +119,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 +273,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();
@@ -352,4 +413,6 @@ public partial class MainVM //File //TODO: Refactor
Mafile? Mafile,
bool SdaPasswordPrompted,
SDAEncryptionHelper.Context? SdaContext);
}
private record MafileImportPlanItem(Mafile Mafile, bool RequiresRelogin, bool HasConflict);
}
+37 -1
View File
@@ -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)
@@ -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))
{
// 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);
}
}
+4 -4
View File
@@ -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
@@ -249,4 +249,4 @@ public partial class SettingsVM : ObservableObject
}
#endregion
}
}
@@ -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") ||
+28 -34
View File
@@ -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();
}
}
@@ -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!;
+1 -1
View File
@@ -31,7 +31,7 @@ public static class ClientBuilder
}
else
{
container.SetSteamMobileCookiesWithMobileToken(sessionData);
container.SetSteamMobileCookies(sessionData);
}
//Nebula tweak: