From f62da0e273e80fd0a046466af0473935ec93c591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Fri, 2 May 2025 12:56:54 +0200 Subject: [PATCH] Closes #3420 --- .../Commands.cs | 20 +-- .../MobileAuthenticatorHandler.cs | 120 -------------- .../MobileAuthenticatorPlugin.cs | 22 +-- .../MobileAuthenticatorWebHandler.cs | 156 ++++++++++++++++++ .../Steam/Integration/ArchiWebHandler.cs | 3 +- 5 files changed, 163 insertions(+), 158 deletions(-) delete mode 100644 ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorHandler.cs create mode 100644 ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorWebHandler.cs diff --git a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/Commands.cs b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/Commands.cs index 8bd90450b..64f5c3dd0 100644 --- a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/Commands.cs +++ b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/Commands.cs @@ -135,12 +135,6 @@ internal static class Commands { mobileAuthenticator.Init(bot); - MobileAuthenticatorHandler? mobileAuthenticatorHandler = bot.GetHandler(); - - if (mobileAuthenticatorHandler == null) { - throw new InvalidOperationException(nameof(mobileAuthenticatorHandler)); - } - ulong steamTime = await mobileAuthenticator.GetSteamTime().ConfigureAwait(false); string? code = mobileAuthenticator.GenerateTokenForTime(steamTime); @@ -149,10 +143,10 @@ internal static class Commands { return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(mobileAuthenticator.GenerateTokenForTime))); } - CTwoFactor_FinalizeAddAuthenticator_Response? response = await mobileAuthenticatorHandler.FinalizeAuthenticator(bot.SteamID, activationCode, code, steamTime).ConfigureAwait(false); + CTwoFactor_FinalizeAddAuthenticator_Response? response = await MobileAuthenticatorWebHandler.FinalizeAuthenticator(bot, activationCode, code, steamTime).ConfigureAwait(false); if (response == null) { - return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(mobileAuthenticatorHandler.FinalizeAuthenticator))); + return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(MobileAuthenticatorWebHandler.FinalizeAuthenticator))); } if (!response.success) { @@ -319,18 +313,12 @@ internal static class Commands { return bot.Commands.FormatBotResponse(Strings.BotNotConnected); } - MobileAuthenticatorHandler? mobileAuthenticatorHandler = bot.GetHandler(); - - if (mobileAuthenticatorHandler == null) { - throw new InvalidOperationException(nameof(mobileAuthenticatorHandler)); - } - string deviceID = $"android:{Guid.NewGuid()}"; - CTwoFactor_AddAuthenticator_Response? response = await mobileAuthenticatorHandler.AddAuthenticator(bot.SteamID, deviceID).ConfigureAwait(false); + CTwoFactor_AddAuthenticator_Response? response = await MobileAuthenticatorWebHandler.AddAuthenticator(bot, deviceID).ConfigureAwait(false); if (response == null) { - return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(mobileAuthenticatorHandler.AddAuthenticator))); + return bot.Commands.FormatBotResponse(Strings.FormatWarningFailedWithError(nameof(MobileAuthenticatorWebHandler.AddAuthenticator))); } EResult result = (EResult) response.status; diff --git a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorHandler.cs b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorHandler.cs deleted file mode 100644 index 160e82efa..000000000 --- a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorHandler.cs +++ /dev/null @@ -1,120 +0,0 @@ -// ---------------------------------------------------------------------------------------------- -// _ _ _ ____ _ _____ -// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ -// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ -// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | -// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// ---------------------------------------------------------------------------------------------- -// | -// Copyright 2015-2025 Ł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.Threading.Tasks; -using ArchiSteamFarm.Core; -using ArchiSteamFarm.NLog; -using SteamKit2; -using SteamKit2.Internal; - -namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator; - -internal sealed class MobileAuthenticatorHandler : ClientMsgHandler { - private readonly ArchiLogger ArchiLogger; - private readonly TwoFactor UnifiedTwoFactorService; - - internal MobileAuthenticatorHandler(ArchiLogger archiLogger, SteamUnifiedMessages steamUnifiedMessages) { - ArgumentNullException.ThrowIfNull(archiLogger); - ArgumentNullException.ThrowIfNull(steamUnifiedMessages); - - ArchiLogger = archiLogger; - UnifiedTwoFactorService = steamUnifiedMessages.CreateService(); - } - - public override void HandleMsg(IPacketMsg packetMsg) => ArgumentNullException.ThrowIfNull(packetMsg); - - internal async Task AddAuthenticator(ulong steamID, string deviceID) { - if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { - throw new ArgumentOutOfRangeException(nameof(steamID)); - } - - ArgumentException.ThrowIfNullOrEmpty(deviceID); - - if (Client == null) { - throw new InvalidOperationException(nameof(Client)); - } - - if (!Client.IsConnected) { - return null; - } - - CTwoFactor_AddAuthenticator_Request request = new() { - authenticator_type = 1, - authenticator_time = Utilities.GetUnixTime(), - device_identifier = deviceID, - steamid = steamID - }; - - SteamUnifiedMessages.ServiceMethodResponse response; - - try { - response = await UnifiedTwoFactorService.AddAuthenticator(request).ToLongRunningTask().ConfigureAwait(false); - } catch (Exception e) { - ArchiLogger.LogGenericWarningException(e); - - return null; - } - - // We want to return the response even with failed EResult - return response.Body; - } - - internal async Task FinalizeAuthenticator(ulong steamID, string activationCode, string authenticatorCode, ulong authenticatorTime) { - if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { - throw new ArgumentOutOfRangeException(nameof(steamID)); - } - - ArgumentException.ThrowIfNullOrEmpty(activationCode); - ArgumentException.ThrowIfNullOrEmpty(authenticatorCode); - ArgumentOutOfRangeException.ThrowIfNegativeOrZero(authenticatorTime); - - if (Client == null) { - throw new InvalidOperationException(nameof(Client)); - } - - if (!Client.IsConnected) { - return null; - } - - CTwoFactor_FinalizeAddAuthenticator_Request request = new() { - activation_code = activationCode, - authenticator_code = authenticatorCode, - authenticator_time = authenticatorTime, - steamid = steamID - }; - - SteamUnifiedMessages.ServiceMethodResponse response; - - try { - response = await UnifiedTwoFactorService.FinalizeAddAuthenticator(request).ToLongRunningTask().ConfigureAwait(false); - } catch (Exception e) { - ArchiLogger.LogGenericWarningException(e); - - return null; - } - - // We want to return the response even with failed EResult - return response.Body; - } -} diff --git a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs index 79d88bb4a..5a769d7b3 100644 --- a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs @@ -22,7 +22,6 @@ // limitations under the License. using System; -using System.Collections.Generic; using System.ComponentModel; using System.Composition; using System.Diagnostics.CodeAnalysis; @@ -39,7 +38,7 @@ namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator; [Export(typeof(IPlugin))] [SuppressMessage("ReSharper", "MemberCanBeFileLocal")] -internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, IBotSteamClient { +internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2 { [JsonInclude] public override string Name => nameof(MobileAuthenticatorPlugin); @@ -66,25 +65,6 @@ internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, return await Commands.OnBotCommand(bot, access, message, args, steamID).ConfigureAwait(false); } - public Task OnBotSteamCallbacksInit(Bot bot, CallbackManager callbackManager) { - ArgumentNullException.ThrowIfNull(bot); - ArgumentNullException.ThrowIfNull(callbackManager); - - return Task.CompletedTask; - } - - public Task?> OnBotSteamHandlersInit(Bot bot) { - ArgumentNullException.ThrowIfNull(bot); - - SteamUnifiedMessages? steamUnifiedMessages = bot.GetHandler(); - - if (steamUnifiedMessages == null) { - throw new InvalidOperationException(nameof(steamUnifiedMessages)); - } - - return Task.FromResult?>(new HashSet(1) { new MobileAuthenticatorHandler(bot.ArchiLogger, steamUnifiedMessages) }); - } - public override Task OnLoaded() { Utilities.WarnAboutIncompleteTranslation(Strings.ResourceManager); diff --git a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorWebHandler.cs b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorWebHandler.cs new file mode 100644 index 000000000..564cf430f --- /dev/null +++ b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorWebHandler.cs @@ -0,0 +1,156 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2015-2025 Ł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.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using ArchiSteamFarm.Core; +using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Steam; +using ArchiSteamFarm.Steam.Integration; +using ArchiSteamFarm.Web; +using SteamKit2; +using SteamKit2.Internal; + +namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator; + +internal static class MobileAuthenticatorWebHandler { + private const string TwoFactorService = "ITwoFactorService"; + + internal static async Task AddAuthenticator(Bot bot, string deviceID) { + ArgumentNullException.ThrowIfNull(bot); + ArgumentException.ThrowIfNullOrEmpty(deviceID); + + if (!bot.IsConnectedAndLoggedOn) { + return null; + } + + string? accessToken = bot.AccessToken; + + if (string.IsNullOrEmpty(accessToken)) { + return null; + } + + Dictionary arguments = new(5, StringComparer.Ordinal) { + { "access_token", accessToken }, + { "authenticator_time", Utilities.GetUnixTime() }, + { "authenticator_type", 1 }, + { "device_identifier", deviceID }, + { "steamid", bot.SteamID } + }; + + CTwoFactor_AddAuthenticator_Response? response = null; + + for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { + if ((i > 0) && (ArchiWebHandler.WebLimiterDelay > 0)) { + await Task.Delay(ArchiWebHandler.WebLimiterDelay).ConfigureAwait(false); + } + + using WebAPI.AsyncInterface twoFactorService = bot.SteamConfiguration.GetAsyncWebAPIInterface(TwoFactorService); + + twoFactorService.Timeout = bot.ArchiWebHandler.WebBrowser.Timeout; + + try { + // TODO: Move to CallProtobufAsync when we update to SK2 3.2.0+ + response = await ArchiWebHandler.WebLimitRequest( + WebAPI.DefaultBaseAddress, + + // ReSharper disable once AccessToDisposedClosure + async () => await twoFactorService.CallProtobufAsync(HttpMethod.Post, "AddAuthenticator", args: arguments).ConfigureAwait(false) + ).ConfigureAwait(false); + } catch (TaskCanceledException e) { + bot.ArchiLogger.LogGenericDebuggingException(e); + } catch (Exception e) { + bot.ArchiLogger.LogGenericWarningException(e); + } + } + + if (response == null) { + bot.ArchiLogger.LogGenericWarning(Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries)); + + return null; + } + + return response; + } + + internal static async Task FinalizeAuthenticator(Bot bot, string activationCode, string authenticatorCode, ulong authenticatorTime) { + ArgumentNullException.ThrowIfNull(bot); + ArgumentException.ThrowIfNullOrEmpty(activationCode); + ArgumentException.ThrowIfNullOrEmpty(authenticatorCode); + ArgumentOutOfRangeException.ThrowIfZero(authenticatorTime); + + if (!bot.IsConnectedAndLoggedOn) { + return null; + } + + string? accessToken = bot.AccessToken; + + if (string.IsNullOrEmpty(accessToken)) { + return null; + } + + Dictionary arguments = new(5, StringComparer.Ordinal) { + { "access_token", accessToken }, + { "activation_code", activationCode }, + { "authenticator_code", authenticatorCode }, + { "authenticator_time", authenticatorTime }, + { "steamid", bot.SteamID } + }; + + CTwoFactor_FinalizeAddAuthenticator_Response? response = null; + + for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { + if ((i > 0) && (ArchiWebHandler.WebLimiterDelay > 0)) { + await Task.Delay(ArchiWebHandler.WebLimiterDelay).ConfigureAwait(false); + } + + using WebAPI.AsyncInterface twoFactorService = bot.SteamConfiguration.GetAsyncWebAPIInterface(TwoFactorService); + + twoFactorService.Timeout = bot.ArchiWebHandler.WebBrowser.Timeout; + + try { + // TODO: Move to CallProtobufAsync when we update to SK2 3.2.0+ + response = await ArchiWebHandler.WebLimitRequest( + WebAPI.DefaultBaseAddress, + + // ReSharper disable once AccessToDisposedClosure + async () => await twoFactorService.CallProtobufAsync(HttpMethod.Post, "FinalizeAddAuthenticator", args: arguments).ConfigureAwait(false) + ).ConfigureAwait(false); + } catch (TaskCanceledException e) { + bot.ArchiLogger.LogGenericDebuggingException(e); + } catch (Exception e) { + bot.ArchiLogger.LogGenericWarningException(e); + } + } + + if (response == null) { + bot.ArchiLogger.LogGenericWarning(Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries)); + + return null; + } + + return response; + } +} diff --git a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs index 9e1a153e5..69a6d86e2 100644 --- a/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs +++ b/ArchiSteamFarm/Steam/Integration/ArchiWebHandler.cs @@ -72,7 +72,8 @@ public sealed class ArchiWebHandler : IDisposable { [PublicAPI] public static Uri SteamStoreURL => new("https://store.steampowered.com"); - private static ushort WebLimiterDelay => ASF.GlobalConfig?.WebLimiterDelay ?? GlobalConfig.DefaultWebLimiterDelay; + [PublicAPI] + public static ushort WebLimiterDelay => ASF.GlobalConfig?.WebLimiterDelay ?? GlobalConfig.DefaultWebLimiterDelay; [PublicAPI] public WebBrowser WebBrowser { get; }