diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index 66d6e90c7..d006791c8 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -72,6 +72,27 @@ namespace ArchiSteamFarm { return (steamID == GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID)); } + internal static string GetFilePath(EFileType fileType) { + if (!Enum.IsDefined(typeof(EFileType), fileType)) { + ArchiLogger.LogNullError(nameof(fileType)); + + return null; + } + + switch (fileType) { + case EFileType.Config: + + return Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName); + case EFileType.Database: + + return Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName); + default: + ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(fileType), fileType)); + + return null; + } + } + internal static async Task Init() { WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true); @@ -726,6 +747,11 @@ namespace ArchiSteamFarm { return true; } + internal enum EFileType : byte { + Config, + Database + } + internal enum EUserInputType : byte { Unknown, DeviceID, diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 9adee1b82..92ee60252 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -116,10 +116,10 @@ namespace ArchiSteamFarm { private readonly SteamUser SteamUser; private readonly Trading Trading; - private IEnumerable<(string FilePath, EBotFileType FileType)> RelatedFiles { + private IEnumerable<(string FilePath, EFileType FileType)> RelatedFiles { get { - foreach (EBotFileType fileType in Enum.GetValues(typeof(EBotFileType))) { - string filePath = GetBotFilePath(fileType); + foreach (EFileType fileType in Enum.GetValues(typeof(EFileType))) { + string filePath = GetFilePath(fileType); if (string.IsNullOrEmpty(filePath)) { ArchiLogger.LogNullError(nameof(filePath)); @@ -473,7 +473,7 @@ namespace ArchiSteamFarm { } internal bool DeleteRedeemedKeysFiles() { - string unusedKeysFilePath = GetBotFilePath(EBotFileType.KeysToRedeemUnused); + string unusedKeysFilePath = GetFilePath(EFileType.KeysToRedeemUnused); if (string.IsNullOrEmpty(unusedKeysFilePath)) { ASF.ArchiLogger.LogNullError(nameof(unusedKeysFilePath)); @@ -491,7 +491,7 @@ namespace ArchiSteamFarm { } } - string usedKeysFilePath = GetBotFilePath(EBotFileType.KeysToRedeemUsed); + string usedKeysFilePath = GetFilePath(EFileType.KeysToRedeemUsed); if (string.IsNullOrEmpty(usedKeysFilePath)) { ASF.ArchiLogger.LogNullError(nameof(usedKeysFilePath)); @@ -681,6 +681,44 @@ namespace ArchiSteamFarm { return ((productInfoResultSet.Complete && !productInfoResultSet.Failed) || optimisticDiscovery ? appID : 0, DateTime.MinValue); } + internal static string GetFilePath(string botName, EFileType fileType) { + if (string.IsNullOrEmpty(botName) || !Enum.IsDefined(typeof(EFileType), fileType)) { + ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(fileType)); + + return null; + } + + string botPath = Path.Combine(SharedInfo.ConfigDirectory, botName); + + switch (fileType) { + case EFileType.Config: + + return botPath + SharedInfo.ConfigExtension; + case EFileType.Database: + + return botPath + SharedInfo.DatabaseExtension; + case EFileType.KeysToRedeem: + + return botPath + SharedInfo.KeysExtension; + case EFileType.KeysToRedeemUnused: + + return botPath + SharedInfo.KeysExtension + SharedInfo.KeysUnusedExtension; + case EFileType.KeysToRedeemUsed: + + return botPath + SharedInfo.KeysExtension + SharedInfo.KeysUsedExtension; + case EFileType.MobileAuthenticator: + + return botPath + SharedInfo.MobileAuthenticatorExtension; + case EFileType.SentryFile: + + return botPath + SharedInfo.SentryHashExtension; + default: + ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(fileType), fileType)); + + return null; + } + } + [ItemCanBeNull] internal async Task> GetMarketableAppIDs() => await ArchiWebHandler.GetAppList().ConfigureAwait(false); @@ -748,7 +786,7 @@ namespace ArchiSteamFarm { } internal async Task<(Dictionary UnusedKeys, Dictionary UsedKeys)> GetUsedAndUnusedKeys() { - string unusedKeysFilePath = GetBotFilePath(EBotFileType.KeysToRedeemUnused); + string unusedKeysFilePath = GetFilePath(EFileType.KeysToRedeemUnused); if (string.IsNullOrEmpty(unusedKeysFilePath)) { ASF.ArchiLogger.LogNullError(nameof(unusedKeysFilePath)); @@ -756,7 +794,7 @@ namespace ArchiSteamFarm { return (null, null); } - string usedKeysFilePath = GetBotFilePath(EBotFileType.KeysToRedeemUsed); + string usedKeysFilePath = GetFilePath(EFileType.KeysToRedeemUsed); if (string.IsNullOrEmpty(usedKeysFilePath)) { ASF.ArchiLogger.LogNullError(nameof(usedKeysFilePath)); @@ -896,7 +934,7 @@ namespace ArchiSteamFarm { return; } - string configFile = GetBotFilePath(EBotFileType.Config); + string configFile = GetFilePath(EFileType.Config); if (string.IsNullOrEmpty(configFile)) { ArchiLogger.LogNullError(nameof(configFile)); @@ -1003,7 +1041,7 @@ namespace ArchiSteamFarm { return; } - string configFilePath = GetBotFilePath(botName, EBotFileType.Config); + string configFilePath = GetFilePath(botName, EFileType.Config); if (string.IsNullOrEmpty(configFilePath)) { ASF.ArchiLogger.LogNullError(nameof(configFilePath)); @@ -1023,7 +1061,7 @@ namespace ArchiSteamFarm { ASF.ArchiLogger.LogGenericDebug(configFilePath + ": " + JsonConvert.SerializeObject(botConfig, Formatting.Indented)); } - string databaseFilePath = GetBotFilePath(botName, EBotFileType.Database); + string databaseFilePath = GetFilePath(botName, EFileType.Database); if (string.IsNullOrEmpty(databaseFilePath)) { ASF.ArchiLogger.LogNullError(nameof(databaseFilePath)); @@ -1091,8 +1129,8 @@ namespace ArchiSteamFarm { await BotDatabase.MakeReadOnly().ConfigureAwait(false); // We handle the config file last as it'll trigger new bot creation - foreach ((string filePath, EBotFileType fileType) in RelatedFiles.Where(file => File.Exists(file.FilePath)).OrderByDescending(file => file.FileType != EBotFileType.Config)) { - string newFilePath = GetBotFilePath(newBotName, fileType); + foreach ((string filePath, EFileType fileType) in RelatedFiles.Where(file => File.Exists(file.FilePath)).OrderByDescending(file => file.FileType != EFileType.Config)) { + string newFilePath = GetFilePath(newBotName, fileType); if (string.IsNullOrEmpty(newFilePath)) { ArchiLogger.LogNullError(nameof(newFilePath)); @@ -1328,7 +1366,7 @@ namespace ArchiSteamFarm { // Support and convert 2FA files if (!HasMobileAuthenticator) { - string mobileAuthenticatorFilePath = GetBotFilePath(EBotFileType.MobileAuthenticator); + string mobileAuthenticatorFilePath = GetFilePath(EFileType.MobileAuthenticator); if (string.IsNullOrEmpty(mobileAuthenticatorFilePath)) { ArchiLogger.LogNullError(nameof(mobileAuthenticatorFilePath)); @@ -1341,7 +1379,7 @@ namespace ArchiSteamFarm { } } - string keysToRedeemFilePath = GetBotFilePath(EBotFileType.KeysToRedeem); + string keysToRedeemFilePath = GetFilePath(EFileType.KeysToRedeem); if (string.IsNullOrEmpty(keysToRedeemFilePath)) { ArchiLogger.LogNullError(nameof(keysToRedeemFilePath)); @@ -1479,52 +1517,14 @@ namespace ArchiSteamFarm { return message.Replace("\\", "\\\\").Replace("[", "\\["); } - private static string GetBotFilePath(string botName, EBotFileType fileType) { - if (string.IsNullOrEmpty(botName) || !Enum.IsDefined(typeof(EBotFileType), fileType)) { - ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(fileType)); - - return null; - } - - string botPath = Path.Combine(SharedInfo.ConfigDirectory, botName); - - switch (fileType) { - case EBotFileType.Config: - - return botPath + SharedInfo.ConfigExtension; - case EBotFileType.Database: - - return botPath + SharedInfo.DatabaseExtension; - case EBotFileType.KeysToRedeem: - - return botPath + SharedInfo.KeysExtension; - case EBotFileType.KeysToRedeemUnused: - - return botPath + SharedInfo.KeysExtension + SharedInfo.KeysUnusedExtension; - case EBotFileType.KeysToRedeemUsed: - - return botPath + SharedInfo.KeysExtension + SharedInfo.KeysUsedExtension; - case EBotFileType.MobileAuthenticator: - - return botPath + SharedInfo.MobileAuthenticatorExtension; - case EBotFileType.SentryFile: - - return botPath + SharedInfo.SentryHashExtension; - default: - ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(fileType), fileType)); - - return null; - } - } - - private string GetBotFilePath(EBotFileType fileType) { - if (!Enum.IsDefined(typeof(EBotFileType), fileType)) { + private string GetFilePath(EFileType fileType) { + if (!Enum.IsDefined(typeof(EFileType), fileType)) { ASF.ArchiLogger.LogNullError(nameof(fileType)); return null; } - return GetBotFilePath(BotName, fileType); + return GetFilePath(BotName, fileType); } [ItemCanBeNull] @@ -1897,7 +1897,7 @@ namespace ArchiSteamFarm { return; } - string sentryFilePath = GetBotFilePath(EBotFileType.SentryFile); + string sentryFilePath = GetFilePath(EFileType.SentryFile); if (string.IsNullOrEmpty(sentryFilePath)) { ArchiLogger.LogNullError(nameof(sentryFilePath)); @@ -2492,7 +2492,7 @@ namespace ArchiSteamFarm { return; } - string sentryFilePath = GetBotFilePath(EBotFileType.SentryFile); + string sentryFilePath = GetFilePath(EFileType.SentryFile); if (string.IsNullOrEmpty(sentryFilePath)) { ArchiLogger.LogNullError(nameof(sentryFilePath)); @@ -2793,7 +2793,7 @@ namespace ArchiSteamFarm { string logEntry = name + DefaultBackgroundKeysRedeemerSeparator + "[" + result.PurchaseResultDetail + "]" + ((result.Items != null) && (result.Items.Count > 0) ? DefaultBackgroundKeysRedeemerSeparator + string.Join(", ", result.Items) : "") + DefaultBackgroundKeysRedeemerSeparator + key; - string filePath = GetBotFilePath(redeemed ? EBotFileType.KeysToRedeemUsed : EBotFileType.KeysToRedeemUnused); + string filePath = GetFilePath(redeemed ? EFileType.KeysToRedeemUsed : EFileType.KeysToRedeemUnused); if (string.IsNullOrEmpty(filePath)) { ArchiLogger.LogNullError(nameof(filePath)); @@ -2878,7 +2878,7 @@ namespace ArchiSteamFarm { return message.Replace("\\[", "[").Replace("\\\\", "\\"); } - private enum EBotFileType : byte { + internal enum EFileType : byte { Config, Database, KeysToRedeem, diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index 15a0c963a..98c495d1e 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -149,11 +149,7 @@ namespace ArchiSteamFarm { public ulong SteamMasterClanID { get; private set; } = DefaultSteamMasterClanID; [JsonExtensionData] - internal Dictionary AdditionalProperties { - get; - [UsedImplicitly] - private set; - } + internal Dictionary AdditionalProperties { get; set; } internal string DecryptedSteamPassword { get { diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs index c34f77b03..7ee420fba 100644 --- a/ArchiSteamFarm/GlobalConfig.cs +++ b/ArchiSteamFarm/GlobalConfig.cs @@ -193,11 +193,7 @@ namespace ArchiSteamFarm { public ProtocolTypes SteamProtocols { get; private set; } = DefaultSteamProtocols; [JsonExtensionData] - internal Dictionary AdditionalProperties { - get; - [UsedImplicitly] - private set; - } + internal Dictionary AdditionalProperties { get; set; } internal bool IsWebProxyPasswordSet { get; private set; } internal bool ShouldSerializeEverything { private get; set; } = true; diff --git a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs index b11cb2027..76c12ecc5 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs @@ -20,12 +20,14 @@ // limitations under the License. using System; -using System.IO; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using ArchiSteamFarm.IPC.Requests; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; namespace ArchiSteamFarm.IPC.Controllers.Api { [Route("Api/ASF")] @@ -62,14 +64,32 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return BadRequest(new GenericResponse(false, errorMessage)); } + request.GlobalConfig.ShouldSerializeEverything = false; + request.GlobalConfig.ShouldSerializeHelperProperties = false; + if (!request.GlobalConfig.IsWebProxyPasswordSet && ASF.GlobalConfig.IsWebProxyPasswordSet) { request.GlobalConfig.WebProxyPassword = ASF.GlobalConfig.WebProxyPassword; } - request.GlobalConfig.ShouldSerializeEverything = false; - request.GlobalConfig.ShouldSerializeHelperProperties = false; + if ((ASF.GlobalConfig.AdditionalProperties != null) && (ASF.GlobalConfig.AdditionalProperties.Count > 0)) { + if (request.GlobalConfig.AdditionalProperties == null) { + request.GlobalConfig.AdditionalProperties = new Dictionary(ASF.GlobalConfig.AdditionalProperties.Count); + } - string filePath = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName); + foreach ((string key, JToken value) in ASF.GlobalConfig.AdditionalProperties.Where(property => !request.GlobalConfig.AdditionalProperties.ContainsKey(property.Key))) { + request.GlobalConfig.AdditionalProperties.Add(key, value); + } + + request.GlobalConfig.AdditionalProperties.TrimExcess(); + } + + string filePath = ASF.GetFilePath(ASF.EFileType.Config); + + if (string.IsNullOrEmpty(filePath)) { + ASF.ArchiLogger.LogNullError(filePath); + + return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(filePath)))); + } bool result = await GlobalConfig.Write(filePath, request.GlobalConfig).ConfigureAwait(false); diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs index ed8421400..9839f6792 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -22,13 +22,13 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.IO; using System.Linq; using System.Threading.Tasks; using ArchiSteamFarm.IPC.Requests; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; namespace ArchiSteamFarm.IPC.Controllers.Api { [Route("Api/Bot")] @@ -99,18 +99,12 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { request.BotConfig.ShouldSerializeEverything = false; request.BotConfig.ShouldSerializeHelperProperties = false; - string originalSteamLogin = request.BotConfig.SteamLogin; - string originalDecryptedSteamPassword = request.BotConfig.DecryptedSteamPassword; - string originalSteamParentalCode = request.BotConfig.SteamParentalCode; - HashSet bots = botNames.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(botName => botName != SharedInfo.ASF).ToHashSet(); Dictionary result = new Dictionary(bots.Count); foreach (string botName in bots) { - bool botExists = Bot.Bots.TryGetValue(botName, out Bot bot); - - if (botExists) { + if (Bot.Bots.TryGetValue(botName, out Bot bot)) { if (!request.BotConfig.IsSteamLoginSet && bot.BotConfig.IsSteamLoginSet) { request.BotConfig.SteamLogin = bot.BotConfig.SteamLogin; } @@ -122,16 +116,29 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { if (!request.BotConfig.IsSteamParentalCodeSet && bot.BotConfig.IsSteamParentalCodeSet) { request.BotConfig.SteamParentalCode = bot.BotConfig.SteamParentalCode; } + + if ((bot.BotConfig.AdditionalProperties != null) && (bot.BotConfig.AdditionalProperties.Count > 0)) { + if (request.BotConfig.AdditionalProperties == null) { + request.BotConfig.AdditionalProperties = new Dictionary(bot.BotConfig.AdditionalProperties.Count); + } + + foreach ((string key, JToken value) in bot.BotConfig.AdditionalProperties.Where(property => !request.BotConfig.AdditionalProperties.ContainsKey(property.Key))) { + request.BotConfig.AdditionalProperties.Add(key, value); + } + + request.BotConfig.AdditionalProperties.TrimExcess(); + } + } + + string filePath = Bot.GetFilePath(botName, Bot.EFileType.Config); + + if (string.IsNullOrEmpty(filePath)) { + ASF.ArchiLogger.LogNullError(filePath); + + return BadRequest(new GenericResponse>(false, string.Format(Strings.ErrorIsInvalid, nameof(filePath)))); } - string filePath = Path.Combine(SharedInfo.ConfigDirectory, botName + SharedInfo.ConfigExtension); result[botName] = await BotConfig.Write(filePath, request.BotConfig).ConfigureAwait(false); - - if (botExists) { - request.BotConfig.SteamLogin = originalSteamLogin; - request.BotConfig.DecryptedSteamPassword = originalDecryptedSteamPassword; - request.BotConfig.SteamParentalCode = originalSteamParentalCode; - } } return Ok(new GenericResponse>(result.Values.All(value => value), result)); diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index 97c26a362..cda791977 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -170,7 +170,13 @@ namespace ArchiSteamFarm { } private static async Task InitGlobalConfigAndLanguage() { - string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName); + string globalConfigFile = ASF.GetFilePath(ASF.EFileType.Config); + + if (string.IsNullOrEmpty(globalConfigFile)) { + ASF.ArchiLogger.LogNullError(nameof(globalConfigFile)); + + return; + } GlobalConfig globalConfig; @@ -191,7 +197,7 @@ namespace ArchiSteamFarm { ASF.InitGlobalConfig(globalConfig); if (Debugging.IsUserDebugging) { - ASF.ArchiLogger.LogGenericDebug(SharedInfo.GlobalConfigFileName + ": " + JsonConvert.SerializeObject(ASF.GlobalConfig, Formatting.Indented)); + ASF.ArchiLogger.LogGenericDebug(globalConfigFile + ": " + JsonConvert.SerializeObject(ASF.GlobalConfig, Formatting.Indented)); } if (!string.IsNullOrEmpty(ASF.GlobalConfig.CurrentCulture)) { @@ -253,7 +259,13 @@ namespace ArchiSteamFarm { } private static async Task InitGlobalDatabaseAndServices() { - string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName); + string globalDatabaseFile = ASF.GetFilePath(ASF.EFileType.Database); + + if (string.IsNullOrEmpty(globalDatabaseFile)) { + ASF.ArchiLogger.LogNullError(nameof(globalDatabaseFile)); + + return; + } if (!File.Exists(globalDatabaseFile)) { ASF.ArchiLogger.LogGenericInfo(Strings.Welcome); @@ -276,7 +288,7 @@ namespace ArchiSteamFarm { // If debugging is on, we prepare debug directory prior to running if (Debugging.IsUserDebugging) { - ASF.ArchiLogger.LogGenericDebug(SharedInfo.GlobalDatabaseFileName + ": " + JsonConvert.SerializeObject(ASF.GlobalDatabase, Formatting.Indented)); + ASF.ArchiLogger.LogGenericDebug(globalDatabaseFile + ": " + JsonConvert.SerializeObject(ASF.GlobalDatabase, Formatting.Indented)); Logging.EnableTraceLogging(); if (Directory.Exists(SharedInfo.DebugDirectory)) {