Compare commits

..

29 Commits

Author SHA1 Message Date
Łukasz Domeradzki
dd663f43b9 Closes #2840 (#2843)
* Initial login flow changes

* I hate windows

* Make it compile

* Hehe, you bet

* Another approach

* Misc

* Misc

* Final touches

* Misc

* Okay xpaw

* xPaw update

* Keep set input for reconnection

* Unify login errors

* Add missing registry entry

* Final touches before I lose my sanity
2023-03-17 23:59:46 +01:00
renovate[bot]
25e7e21604 Update ASF-ui digest to 1e372af 2023-03-16 05:43:43 +00:00
renovate[bot]
c4d0c23a0e Update actions/checkout action to v3.4.0 2023-03-15 23:01:55 +00:00
renovate[bot]
2bda115d37 Update github/codeql-action action to v2.2.7 2023-03-15 18:28:53 +00:00
Archi
4d2fdc08c8 Bump 2023-03-15 17:57:06 +01:00
Archi
d2894a8bdc Followup fix 2023-03-15 17:56:24 +01:00
Archi
f685ce598d Bump 2023-03-15 16:07:24 +01:00
Archi
63c206e6c7 Disable revocation check
Likely root cause for https://steamcommunity.com/groups/archiasf/discussions/1/3821906444724042697/
2023-03-15 16:06:53 +01:00
renovate[bot]
b98e23e840 Update ASF-ui digest to e7dad93 2023-03-15 07:05:36 +00:00
renovate[bot]
87cff8f813 Update crowdin/github-action action to v1.7.1 2023-03-14 13:23:03 +00:00
ArchiBot
c337a9b8e1 Automatic translations update 2023-03-14 02:11:37 +00:00
renovate[bot]
c4e75c5b7b Update ASF-ui digest to e6fd69f 2023-03-11 09:06:35 +00:00
ArchiBot
9e59dd69ca Automatic translations update 2023-03-11 02:16:39 +00:00
renovate[bot]
b90707c6d1 Update github/codeql-action action to v2.2.6 2023-03-10 19:05:30 +00:00
renovate[bot]
641dfa4a1d Update docker/setup-buildx-action action to v2.5.0 2023-03-10 11:20:48 +00:00
renovate[bot]
891d6c87c8 Update ASF-ui digest to 3440b7a 2023-03-09 01:25:28 +00:00
renovate[bot]
ccd6720fc1 Update dependency Newtonsoft.Json to v13.0.3 2023-03-08 11:45:46 +00:00
renovate[bot]
7c9bed6441 Update peter-evans/dockerhub-description action to v3.3.0 2023-03-06 07:30:17 +00:00
ArchiBot
4e92de584c Automatic translations update 2023-03-06 02:22:52 +00:00
renovate[bot]
71a70fd274 Update peter-evans/dockerhub-description action to v3.2.0 2023-03-05 04:34:06 +00:00
ArchiBot
554b1fe443 Automatic translations update 2023-03-03 02:53:30 +00:00
ArchiBot
93ee2c38fb Automatic translations update 2023-03-01 02:24:54 +00:00
ArchiBot
8cde60e7d2 Automatic translations update 2023-02-28 02:22:02 +00:00
renovate[bot]
eeef41e67f Update wiki digest to f1dd80f 2023-02-27 18:03:41 +00:00
renovate[bot]
aeba4f2028 Update dependency Markdig.Signed to v0.31.0 2023-02-27 10:07:17 +00:00
renovate[bot]
5794233013 Update ASF-ui digest to 9a59cc9 2023-02-27 10:06:48 +00:00
ArchiBot
95df482938 Automatic translations update 2023-02-27 02:22:18 +00:00
renovate[bot]
a0766ffe41 Update wiki digest to 76c1aa4 2023-02-26 18:15:13 +00:00
Archi
bae511d2cf Bump + misc 2023-02-26 14:11:43 +01:00
25 changed files with 575 additions and 316 deletions

View File

@@ -19,6 +19,12 @@
"allowedVersions": "<= 3.1",
"matchManagers": [ "nuget" ],
"matchPackageNames": [ "Microsoft.Extensions.Configuration.Json", "Microsoft.Extensions.Logging.Configuration" ]
},
{
// TODO: Stick with SK2 version until we can remove the submodule
"enabled": false,
"matchManagers": [ "nuget" ],
"matchPackageNames": [ "Microsoft.Win32.Registry", "protobuf-net" ]
}
]
}

View File

@@ -19,7 +19,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
with:
submodules: recursive
@@ -39,7 +39,7 @@ jobs:
- name: Upload latest strings for translation on Crowdin
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
uses: crowdin/github-action@v1.7.0
uses: crowdin/github-action@v1.7.1
with:
crowdin_branch_name: main
config: '.github/crowdin.yml'

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
- name: Run Qodana scan
uses: JetBrains/qodana-action@v2022.3.4
@@ -20,6 +20,6 @@ jobs:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
- name: Report Qodana results to GitHub
uses: github/codeql-action/upload-sarif@v2.2.5
uses: github/codeql-action/upload-sarif@v2.2.7
with:
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json

View File

@@ -17,12 +17,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.4.1
uses: docker/setup-buildx-action@v2.5.0
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
uses: docker/build-push-action@v4.0.0

View File

@@ -15,12 +15,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.4.1
uses: docker/setup-buildx-action@v2.5.0
- name: Login to ghcr.io
uses: docker/login-action@v2.1.0

View File

@@ -16,12 +16,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.4.1
uses: docker/setup-buildx-action@v2.5.0
- name: Login to ghcr.io
uses: docker/login-action@v2.1.0
@@ -70,7 +70,7 @@ jobs:
push: true
- name: Update DockerHub repository description
uses: peter-evans/dockerhub-description@v3.1.2
uses: peter-evans/dockerhub-description@v3.3.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}

View File

@@ -16,12 +16,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.4.1
uses: docker/setup-buildx-action@v2.5.0
- name: Login to ghcr.io
uses: docker/login-action@v2.1.0

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
with:
submodules: recursive
@@ -78,7 +78,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.0.3
@@ -417,7 +417,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
- name: Download ASF-generic artifact from ubuntu-latest
uses: actions/download-artifact@v3.0.2
@@ -503,7 +503,6 @@ jobs:
path: out/SHA512SUMS.sign
- name: Create ArchiSteamFarm GitHub release
id: github_release
uses: ncipollo/release-action@v1.12.0
with:
artifacts: "out/*"

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.4.0
with:
submodules: recursive
token: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
@@ -26,7 +26,7 @@ jobs:
git reset --hard origin/master
- name: Download latest translations from Crowdin
uses: crowdin/github-action@v1.7.0
uses: crowdin/github-action@v1.7.1
with:
upload_sources: false
download_translations: true

2
ASF-ui

Submodule ASF-ui updated: b30b3f5bce...1e372af6d5

View File

@@ -66,4 +66,20 @@
<value>Corresponde a um total de {0} conjuntos nesta rodada.</value>
<comment>{0} will be replaced by number of sets traded</comment>
</data>
<data name="ListingAnnouncing" xml:space="preserve">
<value>Anunciando {0} ({1}) com um inventário composto por {2} itens no total na listagem...</value>
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname, {2} will be replaced with number of items in the inventory</comment>
</data>
<data name="MatchingFound" xml:space="preserve">
<value>Foi confirmando um total de {0} itens com o bot {1} ({2}), a enviar oferta de troca...</value>
<comment>{0} will be replaced by number of items matched, {1} will be replaced by steam ID (number), {2} will be replaced by user's nickname</comment>
</data>
<data name="TradeOfferFailed" xml:space="preserve">
<value>Falha ao enviar uma oferta de troca para o bot {0} ({1}), a avançar...</value>
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
</data>
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
<value>Algumas confirmações falharam, aproximadamente {0} de {1} trocas foram enviadas com sucesso.</value>
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
</data>
</root>

View File

@@ -62,15 +62,41 @@
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
<value>{0} foi desativado devido à falta de um token de compilação</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
</data>
<data name="PluginDisabledInConfig" xml:space="preserve">
<value>{0} está atualmente desativado de acordo com a sua configuração. Se quiser ajudar o SteamDB na submissão de dados, por favor confira o nosso wiki.</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
</data>
<data name="PluginInitializedAndEnabled" xml:space="preserve">
<value>{0} foi inicializado com sucesso, obrigado antecipadamente pela sua ajuda. A primeira submissão ocorrerá aproximadamente daqui a {1} a partir de agora.</value>
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin"), {1} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
</data>
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
<value>{0} não pôde ser carregado, uma instância nova será inicializada...</value>
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
</data>
<data name="BotNoAppsToRefresh" xml:space="preserve">
<value>Não há aplicações que requiram uma atualização nesta instância do bot.</value>
</data>
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Recolhendo um total de {0} tokens de acesso a aplicações...</value>
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
</data>
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
<value>Recolhendo {0} tokens de acesso a aplicações...</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
</data>
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
<value>Terminado o recolhimento de {0} tokens de acesso a aplicações.</value>
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
</data>
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
<value>Terminado o recolhimento de um total de {0} tokens de acesso a aplicações.</value>
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
</data>

View File

@@ -74,18 +74,36 @@
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
<value>{0} din {1} chei de depozit au fost recuperate cu succes.</value>
<comment>{0} will be replaced by the number (count this batch) of depot keys that were successfully retrieved, {1} will be replaced by the number (count this batch) of depot keys that were supposed to be retrieved</comment>
</data>
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
<value>Aplicații noi: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedApps" xml:space="preserve">
<value>Aplicații verificate: {0}</value>
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewPackages" xml:space="preserve">
<value>Pachete noi: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
<value>Pachete verificate: {0}</value>
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
</data>
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
<value>Depozite noi: {0}</value>
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
</data>

View File

@@ -13,16 +13,21 @@
<PackageReference Include="Humanizer" />
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
<PackageReference Include="Markdig.Signed" />
<PackageReference Include="Microsoft.Win32.Registry" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Nito.AsyncEx.Coordination" />
<PackageReference Include="NLog.Web.AspNetCore" />
<PackageReference Include="SteamKit2" />
<PackageReference Include="protobuf-net" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" />
<PackageReference Include="System.Composition" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="zxcvbn-core" />
<Reference Include="SteamKit2">
<HintPath>Temp\SteamKit2.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net481'">

View File

@@ -112,7 +112,7 @@ public static class Utilities {
public static void InBackground<T>(Func<T> function, bool longRunning = false) {
ArgumentNullException.ThrowIfNull(function);
InBackground(void () => function(), longRunning);
InBackground(void() => function(), longRunning);
}
[PublicAPI]

View File

@@ -512,7 +512,7 @@ StackTrace:
<value>O seu CurrentCulture fornecido é inválido, o ASF continuará executando com o valor padrão!</value>
</data>
<data name="TranslationIncomplete" xml:space="preserve">
<value>O ASF tentará usar o teu idioma preferido {0}, mas a traudção para essa língua está apenas {1} completa. Talvez queiras ajudar-nos a melhorar a tradução do ASF para a tua língua?</value>
<value>O ASF tentará usar o seu idioma preferido {0}, mas a tradução para essa língua está apenas {1} completa. Talvez nos queira ajudar a melhorar a tradução do ASF para a sua língua?</value>
<comment>{0} will be replaced by culture code, such as "en-US", {1} will be replaced by completeness percentage, such as "78.5%"</comment>
</data>
<data name="IdlingGameNotPossible" xml:space="preserve">
@@ -732,7 +732,16 @@ Tempo de execução: {1}</value>
<data name="PatchingFiles" xml:space="preserve">
<value>A atualizar os ficheiros do ASF...</value>
</data>
<data name="UserInputCryptkey" xml:space="preserve">
<value>Por favor introduza a sua criptochave: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="ErrorIPNotBanned" xml:space="preserve">
<value>O endereço IP {0} não está banido!</value>
<comment>{0} will be replaced by an IP address which was requested to be unbanned from using IPC</comment>
</data>
<data name="WarningNoLicense" xml:space="preserve">
<value>Tentou usar uma funcionalidade paga {0}, mas não tem uma LicenseID válida definida na configuração global do ASF. Por favor, reveja a sua configuração, pois as funcionalidades não funcionarão sem detalhes adicionais.</value>
<comment>{0} will be replaced by feature name (e.g. MatchActively)</comment>
</data>
</root>

View File

@@ -718,7 +718,13 @@ Proces: {1}</value>
<data name="PatchingFiles" xml:space="preserve">
<value>Se repara fișierele ASF...</value>
</data>
<data name="UserInputCryptkey" xml:space="preserve">
<value>Introdu cheia de criptare: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="ErrorIPNotBanned" xml:space="preserve">
<value>Adresa IP {0} nu este interzisă!</value>
<comment>{0} will be replaced by an IP address which was requested to be unbanned from using IPC</comment>
</data>
</root>

View File

@@ -157,6 +157,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
private readonly SemaphoreSlim MessagingSemaphore = new(1, 1);
private readonly ConcurrentDictionary<UserNotificationsCallback.EUserNotification, uint> PastNotifications = new();
private readonly SemaphoreSlim SendCompleteTypesSemaphore = new(1, 1);
private readonly SteamAuthentication SteamAuthentication;
private readonly SteamClient SteamClient;
private readonly ConcurrentHashSet<ulong> SteamFamilySharingIDs = new();
private readonly SteamUser SteamUser;
@@ -178,11 +179,6 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
}
}
/// <remarks>
/// Login keys are not guaranteed to be valid, we should use them only if we don't have full details available from the user
/// </remarks>
private bool ShouldUseLoginKeys => BotConfig.UseLoginKeys && (!BotConfig.IsSteamPasswordSet || !HasMobileAuthenticator);
[JsonProperty($"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(SteamID)}")]
private string SSteamID => SteamID.ToString(CultureInfo.InvariantCulture);
@@ -302,6 +298,8 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
CallbackManager.Subscribe<SteamApps.GuestPassListCallback>(OnGuestPassList);
CallbackManager.Subscribe<SteamApps.LicenseListCallback>(OnLicenseList);
SteamAuthentication = SteamClient.GetHandler<SteamAuthentication>() ?? throw new InvalidOperationException(nameof(SteamAuthentication));
SteamFriends = SteamClient.GetHandler<SteamFriends>() ?? throw new InvalidOperationException(nameof(SteamFriends));
CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
CallbackManager.Subscribe<SteamFriends.PersonaStateCallback>(OnPersonaState);
@@ -311,7 +309,6 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
SteamUser = SteamClient.GetHandler<SteamUser>() ?? throw new InvalidOperationException(nameof(SteamUser));
CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
CallbackManager.Subscribe<SteamUser.VanityURLChangedCallback>(OnVanityURLChangedCallback);
CallbackManager.Subscribe<SteamUser.WalletInfoCallback>(OnWalletUpdate);
@@ -1637,6 +1634,57 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
return true;
}
internal async Task<string?> RequestInput(ASF.EUserInputType inputType, bool previousCodeWasIncorrect) {
if ((inputType == ASF.EUserInputType.None) || !Enum.IsDefined(inputType)) {
throw new InvalidEnumArgumentException(nameof(inputType), (int) inputType, typeof(ASF.EUserInputType));
}
switch (inputType) {
case ASF.EUserInputType.SteamGuard when !string.IsNullOrEmpty(AuthCode):
string? savedAuthCode = AuthCode;
AuthCode = null;
return savedAuthCode;
case ASF.EUserInputType.TwoFactorAuthentication when !string.IsNullOrEmpty(TwoFactorCode):
string? savedTwoFactorCode = TwoFactorCode;
TwoFactorCode = null;
return savedTwoFactorCode;
case ASF.EUserInputType.TwoFactorAuthentication when BotDatabase.MobileAuthenticator != null:
if (previousCodeWasIncorrect) {
// There is a possibility that our cached time is no longer appropriate, so we should reset the cache in this case in order to fetch it upon the next login attempt
// Yes, this might as well be just invalid 2FA credentials, but we can't be sure about that, and we have LoginFailures designed to verify that for us
await MobileAuthenticator.ResetSteamTimeDifference().ConfigureAwait(false);
}
string? generatedTwoFactorCode = await BotDatabase.MobileAuthenticator.GenerateToken().ConfigureAwait(false);
if (!string.IsNullOrEmpty(generatedTwoFactorCode)) {
return generatedTwoFactorCode;
}
break;
}
RequiredInput = inputType;
string? input = await Logging.GetUserInput(inputType, BotName).ConfigureAwait(false);
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (string.IsNullOrEmpty(input) || !SetUserInput(inputType, input!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(input)));
Stop();
return null;
}
// We keep user input set in case we need to use it again due to disconnection, OnLoggedOn() will reset it for us
return input;
}
internal void RequestPersonaStateUpdate() {
if (!IsConnectedAndLoggedOn) {
return;
@@ -1938,6 +1986,102 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
}
}
private async Task HandleLoginResult(EResult result, EResult extendedResult) {
if (!Enum.IsDefined(result)) {
throw new InvalidEnumArgumentException(nameof(result), (int) result, typeof(EResult));
}
if (!Enum.IsDefined(extendedResult)) {
throw new InvalidEnumArgumentException(nameof(extendedResult), (int) extendedResult, typeof(EResult));
}
// Keep LastLogOnResult for OnDisconnected()
LastLogOnResult = result;
HeartBeatFailures = 0;
StopConnectionFailureTimer();
switch (result) {
case EResult.AccountDisabled:
// Those failures are permanent, we should Stop() the bot if any of those happen
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.BotUnableToLogin, result, extendedResult));
Stop();
break;
case EResult.AccountLogonDenied:
case EResult.InvalidLoginAuthCode:
RequiredInput = ASF.EUserInputType.SteamGuard;
string? authCode = await Logging.GetUserInput(ASF.EUserInputType.SteamGuard, BotName).ConfigureAwait(false);
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (string.IsNullOrEmpty(authCode) || !SetUserInput(ASF.EUserInputType.SteamGuard, authCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(authCode)));
Stop();
}
break;
case EResult.AccountLoginDeniedNeedTwoFactor when !HasMobileAuthenticator:
case EResult.TwoFactorCodeMismatch when !HasMobileAuthenticator:
RequiredInput = ASF.EUserInputType.TwoFactorAuthentication;
string? twoFactorCode = await Logging.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName).ConfigureAwait(false);
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (string.IsNullOrEmpty(twoFactorCode) || !SetUserInput(ASF.EUserInputType.TwoFactorAuthentication, twoFactorCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(twoFactorCode)));
Stop();
}
break;
case EResult.AccountLoginDeniedNeedTwoFactor:
case EResult.InvalidPassword:
case EResult.NoConnection:
case EResult.PasswordRequiredToKickSession: // Not sure about this one, it seems to be just generic "try again"? #694
case EResult.RateLimitExceeded:
case EResult.ServiceUnavailable:
case EResult.Timeout:
case EResult.TryAnotherCM:
case EResult.TwoFactorCodeMismatch:
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.BotUnableToLogin, result, extendedResult));
switch (result) {
case EResult.AccountLoginDeniedNeedTwoFactor:
case EResult.TwoFactorCodeMismatch:
// There is a possibility that our cached time is no longer appropriate, so we should reset the cache in this case in order to fetch it upon the next login attempt
// Yes, this might as well be just invalid 2FA credentials, but we can't be sure about that, and we have LoginFailures designed to verify that for us
await MobileAuthenticator.ResetSteamTimeDifference().ConfigureAwait(false);
if (++LoginFailures >= MaxLoginFailures) {
LoginFailures = 0;
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.BotInvalidAuthenticatorDuringLogin, MaxLoginFailures));
Stop();
}
break;
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.RefreshToken) && (++LoginFailures >= MaxLoginFailures):
LoginFailures = 0;
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.BotInvalidPasswordDuringLogin, MaxLoginFailures));
Stop();
break;
}
break;
case EResult.OK:
break;
default:
// Unexpected result, shutdown immediately
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(result), result));
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.BotUnableToLogin, result, extendedResult));
Stop();
break;
}
}
private async void HeartBeat(object? state = null) {
if (!KeepRunning || !IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) {
return;
@@ -2265,22 +2409,17 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
}
}
string? loginKey = null;
string? refreshToken = BotDatabase.RefreshToken;
if (ShouldUseLoginKeys && string.IsNullOrEmpty(AuthCode) && string.IsNullOrEmpty(TwoFactorCode)) {
loginKey = BotDatabase.LoginKey;
// Decrypt login key if needed
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (!string.IsNullOrEmpty(loginKey) && (loginKey!.Length > 19) && BotConfig.PasswordFormat.HasTransformation()) {
loginKey = await ArchiCryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey).ConfigureAwait(false);
if (!string.IsNullOrEmpty(refreshToken)) {
// Decrypt refreshToken if needed
if (BotConfig.PasswordFormat.HasTransformation()) {
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
refreshToken = await ArchiCryptoHelper.Decrypt(BotConfig.PasswordFormat, refreshToken!).ConfigureAwait(false);
}
} else {
// If we're not using login keys, ensure we don't have any saved
BotDatabase.LoginKey = null;
}
if (!await InitLoginAndPassword(string.IsNullOrEmpty(loginKey)).ConfigureAwait(false)) {
if (!await InitLoginAndPassword(string.IsNullOrEmpty(refreshToken)).ConfigureAwait(false)) {
Stop();
return;
@@ -2318,22 +2457,57 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
ArchiLogger.LogGenericInfo(Strings.BotLoggingIn);
if (string.IsNullOrEmpty(TwoFactorCode) && (BotDatabase.MobileAuthenticator != null)) {
// We should always include 2FA token, even if it's not required
TwoFactorCode = await BotDatabase.MobileAuthenticator.GenerateToken().ConfigureAwait(false);
}
InitConnectionFailureTimer();
if (string.IsNullOrEmpty(refreshToken)) {
SteamAuthentication.AuthPollResult pollResponse;
try {
using CancellationTokenSource authCancellationTokenSource = new();
SteamAuthentication.CredentialsAuthSession authSession = await SteamAuthentication.BeginAuthSessionViaCredentials(
new SteamAuthentication.AuthSessionDetails {
Authenticator = new BotCredentialsProvider(this, authCancellationTokenSource),
DeviceFriendlyName = SharedInfo.PublicIdentifier,
GuardData = BotConfig.UseLoginKeys ? BotDatabase.SteamGuardData : null,
IsPersistentSession = true,
Password = password,
Username = username
}
).ConfigureAwait(false);
pollResponse = await authSession.StartPolling(authCancellationTokenSource.Token).ConfigureAwait(false);
} catch (AuthenticationException e) {
ArchiLogger.LogGenericWarningException(e);
await HandleLoginResult(e.Result, e.Result).ConfigureAwait(false);
ReconnectOnUserInitiated = true;
SteamClient.Disconnect();
return;
} catch (OperationCanceledException) {
// This is okay, we already took care of that and can ignore it here
return;
}
refreshToken = pollResponse.RefreshToken;
if (BotConfig.UseLoginKeys) {
BotDatabase.RefreshToken = BotConfig.PasswordFormat.HasTransformation() ? ArchiCryptoHelper.Encrypt(BotConfig.PasswordFormat, refreshToken) : refreshToken;
if (!string.IsNullOrEmpty(pollResponse.NewGuardData)) {
BotDatabase.SteamGuardData = pollResponse.NewGuardData;
}
}
}
SteamUser.LogOnDetails logOnDetails = new() {
AuthCode = AuthCode,
AccessToken = refreshToken,
CellID = ASF.GlobalDatabase?.CellID,
LoginID = LoginID,
LoginKey = loginKey,
Password = password,
SentryFileHash = sentryFileHash,
ShouldRememberPassword = ShouldUseLoginKeys,
TwoFactorCode = TwoFactorCode,
ShouldRememberPassword = BotConfig.UseLoginKeys,
Username = username
};
@@ -2380,9 +2554,9 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
case EResult.AccountDisabled:
// Do not attempt to reconnect, those failures are permanent
return;
case EResult.InvalidPassword when !string.IsNullOrEmpty(BotDatabase.LoginKey):
case EResult.InvalidPassword when !string.IsNullOrEmpty(BotDatabase.RefreshToken):
// We can retry immediately
BotDatabase.LoginKey = null;
BotDatabase.RefreshToken = null;
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
break;
@@ -2681,6 +2855,7 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
private void OnLoggedOff(SteamUser.LoggedOffCallback callback) {
ArgumentNullException.ThrowIfNull(callback);
// Keep LastLogOnResult for OnDisconnected()
LastLogOnResult = callback.Result;
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotLoggedOff, callback.Result));
@@ -2716,231 +2891,133 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
// Always reset one-time-only access tokens when we get OnLoggedOn() response
AuthCode = TwoFactorCode = null;
// Keep LastLogOnResult for OnDisconnected()
LastLogOnResult = callback.Result;
await HandleLoginResult(callback.Result, callback.ExtendedResult).ConfigureAwait(false);
HeartBeatFailures = 0;
StopConnectionFailureTimer();
switch (callback.Result) {
case EResult.AccountDisabled:
// Those failures are permanent, we should Stop() the bot if any of those happen
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
Stop();
break;
case EResult.AccountLogonDenied:
case EResult.InvalidLoginAuthCode:
RequiredInput = ASF.EUserInputType.SteamGuard;
string? authCode = await Logging.GetUserInput(ASF.EUserInputType.SteamGuard, BotName).ConfigureAwait(false);
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (string.IsNullOrEmpty(authCode) || !SetUserInput(ASF.EUserInputType.SteamGuard, authCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(authCode)));
Stop();
}
break;
case EResult.AccountLoginDeniedNeedTwoFactor when !HasMobileAuthenticator:
case EResult.TwoFactorCodeMismatch when !HasMobileAuthenticator:
RequiredInput = ASF.EUserInputType.TwoFactorAuthentication;
string? twoFactorCode = await Logging.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName).ConfigureAwait(false);
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (string.IsNullOrEmpty(twoFactorCode) || !SetUserInput(ASF.EUserInputType.TwoFactorAuthentication, twoFactorCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(twoFactorCode)));
Stop();
}
break;
case EResult.OK:
AccountFlags = callback.AccountFlags;
SteamID = callback.ClientSteamID ?? throw new InvalidOperationException(nameof(callback.ClientSteamID));
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotLoggedOn, $"{SteamID}{(!string.IsNullOrEmpty(callback.VanityURL) ? $"/{callback.VanityURL}" : "")}"));
// Old status for these doesn't matter, we'll update them if needed
LoginFailures = 0;
LibraryLocked = PlayingBlocked = false;
if (PlayingWasBlocked && (PlayingWasBlockedTimer == null)) {
InitPlayingWasBlockedTimer();
}
if (IsAccountLimited) {
ArchiLogger.LogGenericWarning(Strings.BotAccountLimited);
}
if (IsAccountLocked) {
ArchiLogger.LogGenericWarning(Strings.BotAccountLocked);
}
if ((callback.CellID != 0) && (ASF.GlobalDatabase != null) && (callback.CellID != ASF.GlobalDatabase.CellID)) {
ASF.GlobalDatabase.CellID = callback.CellID;
}
// Handle steamID-based maFile
if (!HasMobileAuthenticator) {
string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, $"{SteamID}{SharedInfo.MobileAuthenticatorExtension}");
if (File.Exists(maFilePath)) {
await ImportAuthenticatorFromFile(maFilePath).ConfigureAwait(false);
}
}
if (callback.ParentalSettings != null) {
(SteamParentalActive, string? steamParentalCode) = ValidateSteamParental(callback.ParentalSettings, BotConfig.SteamParentalCode, Program.SteamParentalGeneration);
if (SteamParentalActive) {
// Steam parental enabled
if (!string.IsNullOrEmpty(steamParentalCode)) {
// We were able to automatically generate it, potentially with help of the config
if (BotConfig.SteamParentalCode != steamParentalCode) {
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (!SetUserInput(ASF.EUserInputType.SteamParentalCode, steamParentalCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(steamParentalCode)));
Stop();
break;
}
}
} else {
// We failed to generate the pin ourselves, ask the user
RequiredInput = ASF.EUserInputType.SteamParentalCode;
steamParentalCode = await Logging.GetUserInput(ASF.EUserInputType.SteamParentalCode, BotName).ConfigureAwait(false);
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (string.IsNullOrEmpty(steamParentalCode) || !SetUserInput(ASF.EUserInputType.SteamParentalCode, steamParentalCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(steamParentalCode)));
Stop();
break;
}
}
}
} else {
// Steam parental disabled
SteamParentalActive = false;
}
ArchiWebHandler.OnVanityURLChanged(callback.VanityURL);
if (!await ArchiWebHandler.Init(SteamID, SteamClient.Universe, callback.WebAPIUserNonce ?? throw new InvalidOperationException(nameof(callback.WebAPIUserNonce)), SteamParentalActive ? BotConfig.SteamParentalCode : null).ConfigureAwait(false)) {
if (!await RefreshSession().ConfigureAwait(false)) {
break;
}
}
// Pre-fetch API key for future usage if possible
Utilities.InBackground(ArchiWebHandler.HasValidApiKey);
if ((GamesRedeemerInBackgroundTimer == null) && BotDatabase.HasGamesToRedeemInBackground) {
Utilities.InBackground(() => RedeemGamesInBackground());
}
ArchiHandler.SetCurrentMode(BotConfig.UserInterfaceMode);
ArchiHandler.RequestItemAnnouncements();
// Sometimes Steam won't send us our own PersonaStateCallback, so request it explicitly
RequestPersonaStateUpdate();
Utilities.InBackground(InitializeFamilySharing);
ResetPersonaState();
if (BotConfig.SteamMasterClanID != 0) {
Utilities.InBackground(
async () => {
if (!await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false)) {
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ArchiWebHandler.JoinGroup)));
}
await JoinMasterChatGroupID().ConfigureAwait(false);
}
);
}
if (BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.SteamGroup)) {
Utilities.InBackground(() => ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID));
}
if (CardsFarmer.Paused) {
// Emit initial game playing status in this case
Utilities.InBackground(ResetGamesPlayed);
}
SteamPICSChanges.OnBotLoggedOn();
await PluginsCore.OnBotLoggedOn(this).ConfigureAwait(false);
break;
case EResult.AccountLoginDeniedNeedTwoFactor:
case EResult.InvalidPassword:
case EResult.NoConnection:
case EResult.PasswordRequiredToKickSession: // Not sure about this one, it seems to be just generic "try again"? #694
case EResult.RateLimitExceeded:
case EResult.ServiceUnavailable:
case EResult.Timeout:
case EResult.TryAnotherCM:
case EResult.TwoFactorCodeMismatch:
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
switch (callback.Result) {
case EResult.AccountLoginDeniedNeedTwoFactor:
case EResult.TwoFactorCodeMismatch:
// There is a possibility that our cached time is no longer appropriate, so we should reset the cache in this case in order to fetch it upon the next login attempt
// Yes, this might as well be just invalid 2FA credentials, but we can't be sure about that, and we have LoginFailures designed to verify that for us
await MobileAuthenticator.ResetSteamTimeDifference().ConfigureAwait(false);
if (++LoginFailures >= MaxLoginFailures) {
LoginFailures = 0;
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.BotInvalidAuthenticatorDuringLogin, MaxLoginFailures));
Stop();
}
break;
case EResult.InvalidPassword when string.IsNullOrEmpty(BotDatabase.LoginKey) && (++LoginFailures >= MaxLoginFailures):
LoginFailures = 0;
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.BotInvalidPasswordDuringLogin, MaxLoginFailures));
Stop();
break;
}
break;
default:
// Unexpected result, shutdown immediately
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(callback.Result), callback.Result));
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.BotUnableToLogin, callback.Result, callback.ExtendedResult));
Stop();
break;
}
}
private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
ArgumentNullException.ThrowIfNull(callback);
ArgumentNullException.ThrowIfNull(callback.LoginKey);
if (!ShouldUseLoginKeys) {
if (callback.Result != EResult.OK) {
return;
}
string? loginKey = callback.LoginKey;
AccountFlags = callback.AccountFlags;
SteamID = callback.ClientSteamID ?? throw new InvalidOperationException(nameof(callback.ClientSteamID));
if (BotConfig.PasswordFormat.HasTransformation()) {
loginKey = ArchiCryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotLoggedOn, $"{SteamID}{(!string.IsNullOrEmpty(callback.VanityURL) ? $"/{callback.VanityURL}" : "")}"));
// Old status for these doesn't matter, we'll update them if needed
LoginFailures = 0;
LibraryLocked = PlayingBlocked = false;
if (PlayingWasBlocked && (PlayingWasBlockedTimer == null)) {
InitPlayingWasBlockedTimer();
}
BotDatabase.LoginKey = loginKey;
SteamUser.AcceptNewLoginKey(callback);
if (IsAccountLimited) {
ArchiLogger.LogGenericWarning(Strings.BotAccountLimited);
}
if (IsAccountLocked) {
ArchiLogger.LogGenericWarning(Strings.BotAccountLocked);
}
if ((callback.CellID != 0) && (ASF.GlobalDatabase != null) && (callback.CellID != ASF.GlobalDatabase.CellID)) {
ASF.GlobalDatabase.CellID = callback.CellID;
}
// Handle steamID-based maFile
if (!HasMobileAuthenticator) {
string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, $"{SteamID}{SharedInfo.MobileAuthenticatorExtension}");
if (File.Exists(maFilePath)) {
await ImportAuthenticatorFromFile(maFilePath).ConfigureAwait(false);
}
}
if (callback.ParentalSettings != null) {
(SteamParentalActive, string? steamParentalCode) = ValidateSteamParental(callback.ParentalSettings, BotConfig.SteamParentalCode, Program.SteamParentalGeneration);
if (SteamParentalActive) {
// Steam parental enabled
if (!string.IsNullOrEmpty(steamParentalCode)) {
// We were able to automatically generate it, potentially with help of the config
if (BotConfig.SteamParentalCode != steamParentalCode) {
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (!SetUserInput(ASF.EUserInputType.SteamParentalCode, steamParentalCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(steamParentalCode)));
Stop();
return;
}
}
} else {
// We failed to generate the pin ourselves, ask the user
RequiredInput = ASF.EUserInputType.SteamParentalCode;
steamParentalCode = await Logging.GetUserInput(ASF.EUserInputType.SteamParentalCode, BotName).ConfigureAwait(false);
// ReSharper disable once RedundantSuppressNullableWarningExpression - required for .NET Framework
if (string.IsNullOrEmpty(steamParentalCode) || !SetUserInput(ASF.EUserInputType.SteamParentalCode, steamParentalCode!)) {
ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(steamParentalCode)));
Stop();
return;
}
}
}
} else {
// Steam parental disabled
SteamParentalActive = false;
}
ArchiWebHandler.OnVanityURLChanged(callback.VanityURL);
if (!await ArchiWebHandler.Init(SteamID, SteamClient.Universe, callback.WebAPIUserNonce ?? throw new InvalidOperationException(nameof(callback.WebAPIUserNonce)), SteamParentalActive ? BotConfig.SteamParentalCode : null).ConfigureAwait(false)) {
if (!await RefreshSession().ConfigureAwait(false)) {
return;
}
}
// Pre-fetch API key for future usage if possible
Utilities.InBackground(ArchiWebHandler.HasValidApiKey);
if ((GamesRedeemerInBackgroundTimer == null) && BotDatabase.HasGamesToRedeemInBackground) {
Utilities.InBackground(() => RedeemGamesInBackground());
}
ArchiHandler.SetCurrentMode(BotConfig.UserInterfaceMode);
ArchiHandler.RequestItemAnnouncements();
// Sometimes Steam won't send us our own PersonaStateCallback, so request it explicitly
RequestPersonaStateUpdate();
Utilities.InBackground(InitializeFamilySharing);
ResetPersonaState();
if (BotConfig.SteamMasterClanID != 0) {
Utilities.InBackground(
async () => {
if (!await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false)) {
ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ArchiWebHandler.JoinGroup)));
}
await JoinMasterChatGroupID().ConfigureAwait(false);
}
);
}
if (BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.SteamGroup)) {
Utilities.InBackground(() => ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID));
}
if (CardsFarmer.Paused) {
// Emit initial game playing status in this case
Utilities.InBackground(ResetGamesPlayed);
}
SteamPICSChanges.OnBotLoggedOn();
await PluginsCore.OnBotLoggedOn(this).ConfigureAwait(false);
}
private async void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) {

View File

@@ -0,0 +1,80 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
// |
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
// Contact: JustArchi@JustArchi.net
// |
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// |
// http://www.apache.org/licenses/LICENSE-2.0
// |
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using ArchiSteamFarm.Localization;
using SteamKit2;
namespace ArchiSteamFarm.Steam.Integration;
internal sealed class BotCredentialsProvider : IAuthenticator {
private const byte MaxLoginFailures = 5;
private readonly Bot Bot;
private readonly CancellationTokenSource CancellationTokenSource;
private byte LoginFailures;
internal BotCredentialsProvider(Bot bot, CancellationTokenSource cancellationTokenSource) {
ArgumentNullException.ThrowIfNull(bot);
ArgumentNullException.ThrowIfNull(cancellationTokenSource);
Bot = bot;
CancellationTokenSource = cancellationTokenSource;
}
public Task<bool> AcceptDeviceConfirmation() => Task.FromResult(false);
public async Task<string> ProvideDeviceCode(bool previousCodeWasIncorrect) => await ProvideInput(ASF.EUserInputType.TwoFactorAuthentication, previousCodeWasIncorrect).ConfigureAwait(false);
public async Task<string> ProvideEmailCode(string email, bool previousCodeWasIncorrect) => await ProvideInput(ASF.EUserInputType.SteamGuard, previousCodeWasIncorrect).ConfigureAwait(false);
private async Task<string> ProvideInput(ASF.EUserInputType inputType, bool previousCodeWasIncorrect) {
if (!Enum.IsDefined(inputType)) {
throw new InvalidEnumArgumentException(nameof(inputType), (int) inputType, typeof(ASF.EUserInputType));
}
if (previousCodeWasIncorrect && (++LoginFailures >= MaxLoginFailures)) {
EResult reason = inputType == ASF.EUserInputType.TwoFactorAuthentication ? EResult.TwoFactorCodeMismatch : EResult.InvalidLoginAuthCode;
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.BotUnableToLogin, reason, reason));
if (++LoginFailures >= MaxLoginFailures) {
CancellationTokenSource.Cancel();
return "";
}
}
string? result = await Bot.RequestInput(inputType, previousCodeWasIncorrect).ConfigureAwait(false);
if (string.IsNullOrEmpty(result)) {
CancellationTokenSource.Cancel();
}
return result ?? "";
}
}

View File

@@ -62,19 +62,6 @@ public sealed class BotDatabase : GenericDatabase {
[JsonProperty(Required = Required.DisallowNull)]
private readonly OrderedDictionary GamesToRedeemInBackground = new();
internal string? LoginKey {
get => BackingLoginKey;
set {
if (BackingLoginKey == value) {
return;
}
BackingLoginKey = value;
Utilities.InBackground(Save);
}
}
internal MobileAuthenticator? MobileAuthenticator {
get => BackingMobileAuthenticator;
@@ -88,12 +75,41 @@ public sealed class BotDatabase : GenericDatabase {
}
}
[JsonProperty($"_{nameof(LoginKey)}")]
private string? BackingLoginKey;
internal string? RefreshToken {
get => BackingRefreshToken;
set {
if (BackingRefreshToken == value) {
return;
}
BackingRefreshToken = value;
Utilities.InBackground(Save);
}
}
internal string? SteamGuardData {
get => BackingSteamGuardData;
set {
if (BackingSteamGuardData == value) {
return;
}
BackingSteamGuardData = value;
Utilities.InBackground(Save);
}
}
[JsonProperty($"_{nameof(MobileAuthenticator)}")]
private MobileAuthenticator? BackingMobileAuthenticator;
[JsonProperty]
private string? BackingRefreshToken;
[JsonProperty]
private string? BackingSteamGuardData;
private BotDatabase(string filePath) : this() {
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
@@ -111,10 +127,13 @@ public sealed class BotDatabase : GenericDatabase {
}
[UsedImplicitly]
public bool ShouldSerializeBackingLoginKey() => !string.IsNullOrEmpty(BackingLoginKey);
public bool ShouldSerializeBackingMobileAuthenticator() => BackingMobileAuthenticator != null;
[UsedImplicitly]
public bool ShouldSerializeBackingMobileAuthenticator() => BackingMobileAuthenticator != null;
public bool ShouldSerializeBackingRefreshToken() => !string.IsNullOrEmpty(BackingRefreshToken);
[UsedImplicitly]
public bool ShouldSerializeBackingSteamGuardData() => !string.IsNullOrEmpty(BackingSteamGuardData);
[UsedImplicitly]
public bool ShouldSerializeFarmingBlacklistAppIDs() => FarmingBlacklistAppIDs.Count > 0;

Binary file not shown.

View File

@@ -71,7 +71,6 @@ public sealed class WebBrowser : IDisposable {
AutomaticDecompression = DecompressionMethods.All,
#endif
CheckCertificateRevocationList = true,
CookieContainer = CookieContainer
};
@@ -759,9 +758,6 @@ public sealed class WebBrowser : IDisposable {
// Set max idle time from default of 100 seconds (100 * 1000) to desired value
ServicePointManager.MaxServicePointIdleTime = MaxIdleTime * 1000;
// Make best effort in ensuring that we're not honoring revoked certificates
ServicePointManager.CheckCertificateRevocationList = true;
// Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets
ServicePointManager.Expect100Continue = false;

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>5.4.3.2</Version>
<Version>5.4.4.2</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -5,13 +5,15 @@
<PackageVersion Include="CryptSharpStandard" Version="1.0.0" />
<PackageVersion Include="Humanizer" Version="2.14.1" />
<PackageVersion Include="JetBrains.Annotations" Version="2022.3.1" />
<PackageVersion Include="Markdig.Signed" Version="0.30.4" />
<PackageVersion Include="Markdig.Signed" Version="0.31.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageVersion Include="MSTest.TestFramework" Version="3.0.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Nito.AsyncEx.Coordination" Version="5.1.2" />
<PackageVersion Include="NLog.Web.AspNetCore" Version="5.2.2" />
<PackageVersion Include="protobuf-net" Version="3.1.17" />
<PackageVersion Include="SteamKit2" Version="2.4.1" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />

2
wiki

Submodule wiki updated: b90603aa93...41f0e1370c