From 71a9f06e55688d722e1857289628daa5c832556a Mon Sep 17 00:00:00 2001 From: JustArchi Date: Sat, 23 Feb 2019 04:31:58 +0100 Subject: [PATCH] Implement interactive command console --- ArchiSteamFarm/ASF.cs | 4 + ArchiSteamFarm/Bot.cs | 16 +- .../Localization/Strings.Designer.cs | 45 ++++ ArchiSteamFarm/Localization/Strings.resx | 17 ++ ArchiSteamFarm/NLog/Logging.cs | 240 ++++++++++++++++-- ArchiSteamFarm/Program.cs | 82 +----- ArchiSteamFarm/Utilities.cs | 29 --- 7 files changed, 295 insertions(+), 138 deletions(-) diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index d25fe81d6..426613773 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -84,6 +84,10 @@ namespace ArchiSteamFarm { InitBotsComparer(botsComparer); + if (!GlobalConfig.Headless) { + Logging.StartInteractiveConsole(); + } + if (GlobalConfig.IPC) { await ArchiKestrel.Start().ConfigureAwait(false); } diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 3bf6975d8..f00e7f4da 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -1621,7 +1621,7 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericWarning(Strings.BotAuthenticatorInvalidDeviceID); if (string.IsNullOrEmpty(DeviceID)) { - string deviceID = Program.GetUserInput(ASF.EUserInputType.DeviceID, BotName); + string deviceID = await Logging.GetUserInput(ASF.EUserInputType.DeviceID, BotName).ConfigureAwait(false); if (string.IsNullOrEmpty(deviceID)) { return; @@ -1674,9 +1674,9 @@ namespace ArchiSteamFarm { SteamFamilySharingIDs.ReplaceIfNeededWith(steamIDs); } - private bool InitLoginAndPassword(bool requiresPassword) { + private async Task InitLoginAndPassword(bool requiresPassword) { if (string.IsNullOrEmpty(BotConfig.SteamLogin)) { - string steamLogin = Program.GetUserInput(ASF.EUserInputType.Login, BotName); + string steamLogin = await Logging.GetUserInput(ASF.EUserInputType.Login, BotName).ConfigureAwait(false); if (string.IsNullOrEmpty(steamLogin)) { return false; @@ -1686,7 +1686,7 @@ namespace ArchiSteamFarm { } if (requiresPassword && string.IsNullOrEmpty(BotConfig.DecryptedSteamPassword)) { - string steamPassword = Program.GetUserInput(ASF.EUserInputType.Password, BotName); + string steamPassword = await Logging.GetUserInput(ASF.EUserInputType.Password, BotName).ConfigureAwait(false); if (string.IsNullOrEmpty(steamPassword)) { return false; @@ -1881,7 +1881,7 @@ namespace ArchiSteamFarm { await BotDatabase.SetLoginKey().ConfigureAwait(false); } - if (!InitLoginAndPassword(string.IsNullOrEmpty(loginKey))) { + if (!await InitLoginAndPassword(string.IsNullOrEmpty(loginKey)).ConfigureAwait(false)) { Stop(); return; @@ -2256,7 +2256,7 @@ namespace ArchiSteamFarm { break; case EResult.AccountLogonDenied: - string authCode = Program.GetUserInput(ASF.EUserInputType.SteamGuard, BotName); + string authCode = await Logging.GetUserInput(ASF.EUserInputType.SteamGuard, BotName).ConfigureAwait(false); if (string.IsNullOrEmpty(authCode)) { Stop(); @@ -2270,7 +2270,7 @@ namespace ArchiSteamFarm { case EResult.AccountLoginDeniedNeedTwoFactor: if (!HasMobileAuthenticator) { - string twoFactorCode = Program.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName); + string twoFactorCode = await Logging.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName).ConfigureAwait(false); if (string.IsNullOrEmpty(twoFactorCode)) { Stop(); @@ -2323,7 +2323,7 @@ namespace ArchiSteamFarm { } if (!string.IsNullOrEmpty(BotConfig.SteamParentalCode) && (BotConfig.SteamParentalCode.Length != 4)) { - string steamParentalCode = Program.GetUserInput(ASF.EUserInputType.SteamParentalCode, BotName); + string steamParentalCode = await Logging.GetUserInput(ASF.EUserInputType.SteamParentalCode, BotName).ConfigureAwait(false); if (string.IsNullOrEmpty(steamParentalCode) || (steamParentalCode.Length != 4)) { Stop(); diff --git a/ArchiSteamFarm/Localization/Strings.Designer.cs b/ArchiSteamFarm/Localization/Strings.Designer.cs index 66f470a92..137dcad46 100644 --- a/ArchiSteamFarm/Localization/Strings.Designer.cs +++ b/ArchiSteamFarm/Localization/Strings.Designer.cs @@ -763,6 +763,15 @@ namespace ArchiSteamFarm.Localization { } } + /// + /// Wyszukuje zlokalizowany ciąg podobny do ciągu Enter command: . + /// + public static string EnterCommand { + get { + return ResourceManager.GetString("EnterCommand", resourceCulture); + } + } + /// /// Wyszukuje zlokalizowany ciąg podobny do ciągu Aborted!. /// @@ -981,6 +990,15 @@ namespace ArchiSteamFarm.Localization { } } + /// + /// Wyszukuje zlokalizowany ciąg podobny do ciągu Executing.... + /// + public static string Executing { + get { + return ResourceManager.GetString("Executing", resourceCulture); + } + } + /// /// Wyszukuje zlokalizowany ciąg podobny do ciągu Exiting.... /// @@ -1089,6 +1107,24 @@ namespace ArchiSteamFarm.Localization { } } + /// + /// Wyszukuje zlokalizowany ciąg podobny do ciągu Interactive console is now active, type 'c' in order to enter command mode.. + /// + public static string InteractiveConsoleEnabled { + get { + return ResourceManager.GetString("InteractiveConsoleEnabled", resourceCulture); + } + } + + /// + /// Wyszukuje zlokalizowany ciąg podobny do ciągu Interactive console is not available due to missing {0} config property.. + /// + public static string InteractiveConsoleNotAvailable { + get { + return ResourceManager.GetString("InteractiveConsoleNotAvailable", resourceCulture); + } + } + /// /// Wyszukuje zlokalizowany ciąg podobny do ciągu IPC server ready!. /// @@ -1224,6 +1260,15 @@ namespace ArchiSteamFarm.Localization { } } + /// + /// Wyszukuje zlokalizowany ciąg podobny do ciągu Response: {0}. + /// + public static string Response { + get { + return ResourceManager.GetString("Response", resourceCulture); + } + } + /// /// Wyszukuje zlokalizowany ciąg podobny do ciągu Restarting.... /// diff --git a/ArchiSteamFarm/Localization/Strings.resx b/ArchiSteamFarm/Localization/Strings.resx index a7280f91f..a9efdc63e 100644 --- a/ArchiSteamFarm/Localization/Strings.resx +++ b/ArchiSteamFarm/Localization/Strings.resx @@ -696,4 +696,21 @@ StackTrace: Please wait... + + Enter command: + + + Executing... + + + Interactive console is now active, type 'c' in order to enter command mode. + + + Interactive console is not available due to missing {0} config property. + {0} will be replaced by the name of the missing config property (string) + + + Response: {0} + {0} will be replaced by the generated response (string) + \ No newline at end of file diff --git a/ArchiSteamFarm/NLog/Logging.cs b/ArchiSteamFarm/NLog/Logging.cs index 5188cf6c8..776e2af21 100644 --- a/ArchiSteamFarm/NLog/Logging.cs +++ b/ArchiSteamFarm/NLog/Logging.cs @@ -19,19 +19,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using ArchiSteamFarm.Collections; using ArchiSteamFarm.IPC; +using ArchiSteamFarm.Localization; +using JetBrains.Annotations; using NLog; using NLog.Config; using NLog.Targets; namespace ArchiSteamFarm.NLog { internal static class Logging { + private const byte ConsoleResponsivenessDelay = 250; // In miliseconds private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${processname}-${processid}|${level:uppercase=true}|" + LayoutMessage; private const string LayoutMessage = @"${logger}|${message}${onexception:inner= ${exception:format=toString,Data}}"; private static readonly ConcurrentHashSet ConsoleLoggingRules = new ConcurrentHashSet(); + private static readonly SemaphoreSlim ConsoleSemaphore = new SemaphoreSlim(1, 1); private static bool IsUsingCustomConfiguration; private static bool IsWaitingForUserInput; @@ -53,6 +61,84 @@ namespace ArchiSteamFarm.NLog { } } + internal static async Task GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF) { + if (userInputType == ASF.EUserInputType.Unknown) { + return null; + } + + if (ASF.GlobalConfig.Headless) { + ASF.ArchiLogger.LogGenericWarning(Strings.ErrorUserInputRunningInHeadlessMode); + + return null; + } + + await ConsoleSemaphore.WaitAsync().ConfigureAwait(false); + + string result; + + try { + OnUserInputStart(); + + try { + Console.Beep(); + + switch (userInputType) { + case ASF.EUserInputType.DeviceID: + Console.Write(Bot.FormatBotResponse(Strings.UserInputDeviceID, botName)); + result = ConsoleReadLine(); + + break; + case ASF.EUserInputType.Login: + Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamLogin, botName)); + result = ConsoleReadLine(); + + break; + case ASF.EUserInputType.Password: + Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamPassword, botName)); + result = ConsoleReadLineMasked(); + + break; + case ASF.EUserInputType.SteamGuard: + Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamGuard, botName)); + result = ConsoleReadLine(); + + break; + case ASF.EUserInputType.SteamParentalCode: + Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamParentalCode, botName)); + result = ConsoleReadLineMasked(); + + break; + case ASF.EUserInputType.TwoFactorAuthentication: + Console.Write(Bot.FormatBotResponse(Strings.UserInputSteam2FA, botName)); + result = ConsoleReadLine(); + + break; + default: + ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(userInputType), userInputType)); + Console.Write(Bot.FormatBotResponse(string.Format(Strings.UserInputUnknown, userInputType), botName)); + result = ConsoleReadLine(); + + break; + } + + if (!Console.IsOutputRedirected) { + Console.Clear(); // For security purposes + } + } catch (Exception e) { + OnUserInputEnd(); + ASF.ArchiLogger.LogGenericException(e); + + return null; + } + + OnUserInputEnd(); + } finally { + ConsoleSemaphore.Release(); + } + + return !string.IsNullOrEmpty(result) ? result.Trim() : null; + } + internal static void InitCoreLoggers() { if (LogManager.Configuration != null) { IsUsingCustomConfiguration = true; @@ -105,42 +191,109 @@ namespace ArchiSteamFarm.NLog { ArchiKestrel.OnNewHistoryTarget(historyTarget); } - internal static void OnUserInputEnd() { - IsWaitingForUserInput = false; + internal static void StartInteractiveConsole() { + if (ASF.GlobalConfig.SteamOwnerID == 0) { + ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.InteractiveConsoleNotAvailable, nameof(ASF.GlobalConfig.SteamOwnerID))); - if (ConsoleLoggingRules.Count == 0) { return; } - bool reconfigure = false; - - foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules.Where(consoleLoggingRule => !LogManager.Configuration.LoggingRules.Contains(consoleLoggingRule))) { - LogManager.Configuration.LoggingRules.Add(consoleLoggingRule); - reconfigure = true; - } - - if (reconfigure) { - LogManager.ReconfigExistingLoggers(); - } + Utilities.InBackground(HandleConsoleInteractively); + ASF.ArchiLogger.LogGenericInfo(Strings.InteractiveConsoleEnabled); } - internal static void OnUserInputStart() { - IsWaitingForUserInput = true; + private static string ConsoleReadLine() { + Console.Beep(); - if (ConsoleLoggingRules.Count == 0) { - return; - } + return Console.ReadLine(); + } - bool reconfigure = false; + [NotNull] + private static string ConsoleReadLineMasked(char mask = '*') { + StringBuilder result = new StringBuilder(); - foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) { - if (LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule)) { - reconfigure = true; + Console.Beep(); + + ConsoleKeyInfo keyInfo; + + while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter) { + if (!char.IsControl(keyInfo.KeyChar)) { + result.Append(keyInfo.KeyChar); + Console.Write(mask); + } else if ((keyInfo.Key == ConsoleKey.Backspace) && (result.Length > 0)) { + result.Length--; + + if (Console.CursorLeft == 0) { + Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1); + Console.Write(' '); + Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1); + } else { + // There are two \b characters here + Console.Write(@" "); + } } } - if (reconfigure) { - LogManager.ReconfigExistingLoggers(); + Console.WriteLine(); + + return result.ToString(); + } + + private static async Task HandleConsoleInteractively() { + while (!Program.ShutdownSequenceInitialized) { + try { + if (IsWaitingForUserInput || !Console.KeyAvailable) { + continue; + } + + await ConsoleSemaphore.WaitAsync().ConfigureAwait(false); + + try { + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + + if (keyInfo.Key != ConsoleKey.C) { + continue; + } + + OnUserInputStart(); + + try { + Console.Write(@">> " + Strings.EnterCommand); + string command = ConsoleReadLine(); + + if (string.IsNullOrEmpty(command)) { + continue; + } + + Bot targetBot = Bot.Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value).FirstOrDefault(); + + if (targetBot == null) { + Console.WriteLine(@"<< " + Strings.ErrorNoBotsDefined); + + continue; + } + + Console.WriteLine(@"<> " + Strings.Executing); + + string response = await targetBot.Commands.Response(ASF.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false); + + if (string.IsNullOrEmpty(response)) { + ASF.ArchiLogger.LogNullError(nameof(response)); + Console.WriteLine(Strings.ErrorIsEmpty, nameof(response)); + + continue; + } + + Console.WriteLine(@"<< " + response); + } finally { + OnUserInputEnd(); + } + } finally { + ConsoleSemaphore.Release(); + } + } finally { + await Task.Delay(ConsoleResponsivenessDelay).ConfigureAwait(false); + } } } @@ -168,5 +321,44 @@ namespace ArchiSteamFarm.NLog { HistoryTarget historyTarget = LogManager.Configuration.AllTargets.OfType().FirstOrDefault(); ArchiKestrel.OnNewHistoryTarget(historyTarget); } + + private static void OnUserInputEnd() { + IsWaitingForUserInput = false; + + if (ConsoleLoggingRules.Count == 0) { + return; + } + + bool reconfigure = false; + + foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules.Where(consoleLoggingRule => !LogManager.Configuration.LoggingRules.Contains(consoleLoggingRule))) { + LogManager.Configuration.LoggingRules.Add(consoleLoggingRule); + reconfigure = true; + } + + if (reconfigure) { + LogManager.ReconfigExistingLoggers(); + } + } + + private static void OnUserInputStart() { + IsWaitingForUserInput = true; + + if (ConsoleLoggingRules.Count == 0) { + return; + } + + bool reconfigure = false; + + foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) { + if (LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule)) { + reconfigure = true; + } + } + + if (reconfigure) { + LogManager.ReconfigExistingLoggers(); + } + } } } diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index 6c832be67..3f944cf43 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -42,15 +42,12 @@ namespace ArchiSteamFarm { internal static bool ProcessRequired { get; private set; } internal static bool RestartAllowed { get; private set; } = true; - - private static readonly object ConsoleLock = new object(); + internal static bool ShutdownSequenceInitialized { get; private set; } // We need to keep this one assigned and not calculated on-demand private static readonly string ProcessFileName = Process.GetCurrentProcess().MainModule.FileName; private static readonly TaskCompletionSource ShutdownResetEvent = new TaskCompletionSource(); - - private static bool ShutdownSequenceInitialized; private static bool SystemRequired; internal static async Task Exit(byte exitCode = 0) { @@ -62,78 +59,6 @@ namespace ArchiSteamFarm { Environment.Exit(exitCode); } - internal static string GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF) { - if (userInputType == ASF.EUserInputType.Unknown) { - return null; - } - - if (ASF.GlobalConfig.Headless) { - ASF.ArchiLogger.LogGenericWarning(Strings.ErrorUserInputRunningInHeadlessMode); - - return null; - } - - string result; - - lock (ConsoleLock) { - Logging.OnUserInputStart(); - - try { - switch (userInputType) { - case ASF.EUserInputType.DeviceID: - Console.Write(Bot.FormatBotResponse(Strings.UserInputDeviceID, botName)); - result = Console.ReadLine(); - - break; - case ASF.EUserInputType.Login: - Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamLogin, botName)); - result = Console.ReadLine(); - - break; - case ASF.EUserInputType.Password: - Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamPassword, botName)); - result = Utilities.ReadLineMasked(); - - break; - case ASF.EUserInputType.SteamGuard: - Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamGuard, botName)); - result = Console.ReadLine(); - - break; - case ASF.EUserInputType.SteamParentalCode: - Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamParentalCode, botName)); - result = Utilities.ReadLineMasked(); - - break; - case ASF.EUserInputType.TwoFactorAuthentication: - Console.Write(Bot.FormatBotResponse(Strings.UserInputSteam2FA, botName)); - result = Console.ReadLine(); - - break; - default: - ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(userInputType), userInputType)); - Console.Write(Bot.FormatBotResponse(string.Format(Strings.UserInputUnknown, userInputType), botName)); - result = Console.ReadLine(); - - break; - } - - if (!Console.IsOutputRedirected) { - Console.Clear(); // For security purposes - } - } catch (Exception e) { - Logging.OnUserInputEnd(); - ASF.ArchiLogger.LogGenericException(e); - - return null; - } - - Logging.OnUserInputEnd(); - } - - return !string.IsNullOrEmpty(result) ? result.Trim() : null; - } - internal static async Task Restart() { if (!await InitShutdownSequence().ConfigureAwait(false)) { return; @@ -200,7 +125,10 @@ namespace ArchiSteamFarm { } private static async Task InitASF(IReadOnlyCollection args) { - ASF.ArchiLogger.LogGenericInfo(SharedInfo.PublicIdentifier + " V" + SharedInfo.Version + " (" + SharedInfo.BuildInfo.Variant + "/" + SharedInfo.ModuleVersion + " | " + OS.Variant + ")"); + string programIdentifier = SharedInfo.PublicIdentifier + " V" + SharedInfo.Version + " (" + SharedInfo.BuildInfo.Variant + "/" + SharedInfo.ModuleVersion + " | " + OS.Variant + ")"; + + Console.Title = programIdentifier; + ASF.ArchiLogger.LogGenericInfo(programIdentifier); await InitGlobalConfigAndLanguage().ConfigureAwait(false); diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index 6142f144d..692795993 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -265,34 +265,5 @@ namespace ArchiSteamFarm { return Random.Next(); } } - - [NotNull] - internal static string ReadLineMasked(char mask = '*') { - StringBuilder result = new StringBuilder(); - - ConsoleKeyInfo keyInfo; - - while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter) { - if (!char.IsControl(keyInfo.KeyChar)) { - result.Append(keyInfo.KeyChar); - Console.Write(mask); - } else if ((keyInfo.Key == ConsoleKey.Backspace) && (result.Length > 0)) { - result.Remove(result.Length - 1, 1); - - if (Console.CursorLeft == 0) { - Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1); - Console.Write(' '); - Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1); - } else { - // There are two \b characters here - Console.Write(@" "); - } - } - } - - Console.WriteLine(); - - return result.ToString(); - } } }