Implement interactive command console

This commit is contained in:
JustArchi
2019-02-23 04:31:58 +01:00
parent 6241e283b5
commit 71a9f06e55
7 changed files with 295 additions and 138 deletions

View File

@@ -84,6 +84,10 @@ namespace ArchiSteamFarm {
InitBotsComparer(botsComparer);
if (!GlobalConfig.Headless) {
Logging.StartInteractiveConsole();
}
if (GlobalConfig.IPC) {
await ArchiKestrel.Start().ConfigureAwait(false);
}

View File

@@ -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<bool> 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();

View File

@@ -763,6 +763,15 @@ namespace ArchiSteamFarm.Localization {
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Enter command: .
/// </summary>
public static string EnterCommand {
get {
return ResourceManager.GetString("EnterCommand", resourceCulture);
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Aborted!.
/// </summary>
@@ -981,6 +990,15 @@ namespace ArchiSteamFarm.Localization {
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Executing....
/// </summary>
public static string Executing {
get {
return ResourceManager.GetString("Executing", resourceCulture);
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Exiting....
/// </summary>
@@ -1089,6 +1107,24 @@ namespace ArchiSteamFarm.Localization {
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Interactive console is now active, type &apos;c&apos; in order to enter command mode..
/// </summary>
public static string InteractiveConsoleEnabled {
get {
return ResourceManager.GetString("InteractiveConsoleEnabled", resourceCulture);
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Interactive console is not available due to missing {0} config property..
/// </summary>
public static string InteractiveConsoleNotAvailable {
get {
return ResourceManager.GetString("InteractiveConsoleNotAvailable", resourceCulture);
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu IPC server ready!.
/// </summary>
@@ -1224,6 +1260,15 @@ namespace ArchiSteamFarm.Localization {
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Response: {0}.
/// </summary>
public static string Response {
get {
return ResourceManager.GetString("Response", resourceCulture);
}
}
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Restarting....
/// </summary>

View File

@@ -696,4 +696,21 @@ StackTrace:
<data name="PleaseWait" xml:space="preserve">
<value>Please wait...</value>
</data>
<data name="EnterCommand" xml:space="preserve">
<value>Enter command: </value>
</data>
<data name="Executing" xml:space="preserve">
<value>Executing...</value>
</data>
<data name="InteractiveConsoleEnabled" xml:space="preserve">
<value>Interactive console is now active, type 'c' in order to enter command mode.</value>
</data>
<data name="InteractiveConsoleNotAvailable" xml:space="preserve">
<value>Interactive console is not available due to missing {0} config property.</value>
<comment>{0} will be replaced by the name of the missing config property (string)</comment>
</data>
<data name="Response" xml:space="preserve">
<value>Response: {0}</value>
<comment>{0} will be replaced by the generated response (string)</comment>
</data>
</root>

View File

@@ -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<LoggingRule> ConsoleLoggingRules = new ConcurrentHashSet<LoggingRule>();
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<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;
}
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;
Utilities.InBackground(HandleConsoleInteractively);
ASF.ArchiLogger.LogGenericInfo(Strings.InteractiveConsoleEnabled);
}
if (reconfigure) {
LogManager.ReconfigExistingLoggers();
private static string ConsoleReadLine() {
Console.Beep();
return Console.ReadLine();
}
[NotNull]
private static string ConsoleReadLineMasked(char mask = '*') {
StringBuilder result = new StringBuilder();
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(@" ");
}
}
}
internal static void OnUserInputStart() {
IsWaitingForUserInput = true;
Console.WriteLine();
if (ConsoleLoggingRules.Count == 0) {
return;
return result.ToString();
}
bool reconfigure = false;
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) {
if (LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule)) {
reconfigure = true;
}
private static async Task HandleConsoleInteractively() {
while (!Program.ShutdownSequenceInitialized) {
try {
if (IsWaitingForUserInput || !Console.KeyAvailable) {
continue;
}
if (reconfigure) {
LogManager.ReconfigExistingLoggers();
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<HistoryTarget>().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();
}
}
}
}

View File

@@ -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<byte> ShutdownResetEvent = new TaskCompletionSource<byte>();
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<string> 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);

View File

@@ -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();
}
}
}