diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index b90a273b5..4fd1cfc23 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -40,6 +40,7 @@ namespace ArchiSteamFarm { internal sealed class ArchiWebHandler : IDisposable { private const string IEconService = "IEconService"; private const string IPlayerService = "IPlayerService"; + private const string ISteamApps = "ISteamApps"; private const string ISteamUserAuth = "ISteamUserAuth"; private const string ITwoFactorService = "ITwoFactorService"; private const string SteamCommunityHost = "steamcommunity.com"; @@ -188,9 +189,9 @@ namespace ArchiSteamFarm { #pragma warning disable ConfigureAwaitChecker // CAC001 // ReSharper disable once AccessToDisposedClosure async () => await iEconService.DeclineTradeOffer( - tradeofferid: tradeID.ToString(), method: WebRequestMethods.Http.Post, - secure: true + secure: true, + tradeofferid: tradeID.ToString() ) #pragma warning restore ConfigureAwaitChecker // CAC001 ).ConfigureAwait(false); @@ -343,6 +344,55 @@ namespace ArchiSteamFarm { return result; } + internal async Task> GetAppList() { + KeyValue response = null; + for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) { + using (dynamic iSteamApps = WebAPI.GetAsyncInterface(ISteamApps)) { + iSteamApps.Timeout = WebBrowser.Timeout; + + try { + response = await WebLimitRequest( + WebAPI.DefaultBaseAddress.Host, +#pragma warning disable ConfigureAwaitChecker // CAC001 + // ReSharper disable once AccessToDisposedClosure + async () => await iSteamApps.GetAppList2(secure: true) +#pragma warning restore ConfigureAwaitChecker // CAC001 + ).ConfigureAwait(false); + } catch (TaskCanceledException e) { + Bot.ArchiLogger.LogGenericDebuggingException(e); + } catch (Exception e) { + Bot.ArchiLogger.LogGenericWarningException(e); + } + } + } + + if (response == null) { + Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)); + return null; + } + + List apps = response["applist"]["apps"].Children; + if ((apps == null) || (apps.Count == 0)) { + Bot.ArchiLogger.LogNullError(nameof(apps)); + return null; + } + + HashSet result = new HashSet(apps.Count); + + foreach (KeyValue app in apps) { + uint appID = app["appid"].AsUnsignedInteger(); + + if (appID == 0) { + Bot.ArchiLogger.LogNullError(nameof(appID)); + return null; + } + + result.Add(appID); + } + + return result; + } + internal async Task GetBadgePage(byte page) { if (page == 0) { Bot.ArchiLogger.LogNullError(nameof(page)); @@ -624,9 +674,9 @@ namespace ArchiSteamFarm { #pragma warning disable ConfigureAwaitChecker // CAC001 // ReSharper disable once AccessToDisposedClosure async () => await iPlayerService.GetOwnedGames( - steamid: steamID, include_appinfo: 1, - secure: true + secure: true, + steamid: steamID ) #pragma warning restore ConfigureAwaitChecker // CAC001 ).ConfigureAwait(false); @@ -974,11 +1024,11 @@ namespace ArchiSteamFarm { #pragma warning disable ConfigureAwaitChecker // CAC001 // ReSharper disable once AccessToDisposedClosure async () => await iSteamUserAuth.AuthenticateUser( - steamid: steamID, - sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)), encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)), method: WebRequestMethods.Http.Post, - secure: true + secure: true, + sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)), + steamid: steamID ) #pragma warning restore ConfigureAwaitChecker // CAC001 ).ConfigureAwait(false); diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 3fd92ab8d..98ee4d470 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -552,6 +552,8 @@ namespace ArchiSteamFarm { return result; } + internal async Task> GetMarketableAppIDs() => await ArchiWebHandler.GetAppList().ConfigureAwait(false); + internal async Task AppIDs)>> GetPackagesData(IReadOnlyCollection packageIDs) { if ((packageIDs == null) || (packageIDs.Count == 0)) { ArchiLogger.LogNullError(nameof(packageIDs)); diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index 29f94abf8..7fc58efe1 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -23,7 +23,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.Json; @@ -79,6 +78,9 @@ namespace ArchiSteamFarm { [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace, Required = Required.DisallowNull)] internal readonly HashSet MatchableTypes = new HashSet { Steam.Asset.EType.TradingCard }; + [JsonProperty(Required = Required.DisallowNull)] + internal readonly EPersonaState OnlineStatus = EPersonaState.Online; + [JsonProperty(Required = Required.DisallowNull)] internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText; @@ -112,9 +114,6 @@ namespace ArchiSteamFarm { [JsonProperty(Required = Required.DisallowNull)] internal EBotBehaviour BotBehaviour { get; private set; } = EBotBehaviour.None; - [JsonProperty(Required = Required.DisallowNull)] - internal EPersonaState OnlineStatus { get; private set; } = EPersonaState.Online; - [JsonProperty] internal string SteamLogin { get; set; } @@ -181,7 +180,61 @@ namespace ArchiSteamFarm { return null; } - botConfig.ShouldSerializeSensitiveDetails = false; + if (botConfig.BotBehaviour > EBotBehaviour.All) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.BotBehaviour), botConfig.BotBehaviour)); + return null; + } + + if (!Enum.IsDefined(typeof(EFarmingOrder), botConfig.FarmingOrder)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.FarmingOrder), botConfig.FarmingOrder)); + return null; + } + + if (botConfig.GamesPlayedWhileIdle.Count > ArchiHandler.MaxGamesPlayedConcurrently) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.GamesPlayedWhileIdle), botConfig.GamesPlayedWhileIdle.Count + " > " + ArchiHandler.MaxGamesPlayedConcurrently)); + return null; + } + + foreach (Steam.Asset.EType lootableType in botConfig.LootableTypes) { + if (!Enum.IsDefined(typeof(Steam.Asset.EType), lootableType)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.LootableTypes), lootableType)); + return null; + } + } + + foreach (Steam.Asset.EType matchableType in botConfig.MatchableTypes) { + if (!Enum.IsDefined(typeof(Steam.Asset.EType), matchableType)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.MatchableTypes), matchableType)); + return null; + } + } + + if ((botConfig.OnlineStatus < EPersonaState.Offline) || (botConfig.OnlineStatus >= EPersonaState.Max)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.OnlineStatus), botConfig.OnlineStatus)); + return null; + } + + if (!Enum.IsDefined(typeof(CryptoHelper.ECryptoMethod), botConfig.PasswordFormat)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.PasswordFormat), botConfig.PasswordFormat)); + return null; + } + + if (botConfig.RedeemingPreferences > ERedeemingPreferences.All) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.RedeemingPreferences), botConfig.RedeemingPreferences)); + return null; + } + + foreach (EPermission permission in botConfig.SteamUserPermissions.Values) { + if (!Enum.IsDefined(typeof(EPermission), permission)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.SteamUserPermissions), permission)); + return null; + } + } + + if (botConfig.TradingPreferences > ETradingPreferences.All) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.TradingPreferences), botConfig.TradingPreferences)); + return null; + } // Support encrypted passwords if ((botConfig.PasswordFormat != CryptoHelper.ECryptoMethod.PlainText) && !string.IsNullOrEmpty(botConfig.SteamPassword)) { @@ -189,21 +242,7 @@ namespace ArchiSteamFarm { botConfig.SteamPassword = CryptoHelper.Decrypt(botConfig.PasswordFormat, botConfig.SteamPassword); } - // User might not know what he's doing - // Ensure that he can't screw core ASF variables - if ((botConfig.OnlineStatus < EPersonaState.Offline) || (botConfig.OnlineStatus >= EPersonaState.Max)) { - ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(botConfig.OnlineStatus), botConfig.OnlineStatus)); - return null; - } - - if (botConfig.GamesPlayedWhileIdle.Count > ArchiHandler.MaxGamesPlayedConcurrently) { - ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningTooManyGamesToPlay, ArchiHandler.MaxGamesPlayedConcurrently, nameof(botConfig.GamesPlayedWhileIdle))); - - HashSet validGames = botConfig.GamesPlayedWhileIdle.Take(ArchiHandler.MaxGamesPlayedConcurrently).ToHashSet(); - botConfig.GamesPlayedWhileIdle.IntersectWith(validGames); - botConfig.GamesPlayedWhileIdle.TrimExcess(); - } - + botConfig.ShouldSerializeSensitiveDetails = false; return botConfig; } @@ -242,7 +281,8 @@ namespace ArchiSteamFarm { RejectInvalidFriendInvites = 1, RejectInvalidTrades = 2, RejectInvalidGroupInvites = 4, - DismissInventoryNotifications = 8 + DismissInventoryNotifications = 8, + All = RejectInvalidFriendInvites | RejectInvalidTrades | RejectInvalidGroupInvites | DismissInventoryNotifications } internal enum EFarmingOrder : byte { @@ -259,7 +299,9 @@ namespace ArchiSteamFarm { BadgeLevelsAscending, BadgeLevelsDescending, RedeemDateTimesAscending, - RedeemDateTimesDescending + RedeemDateTimesDescending, + MarketableAscending, + MarketableDescending } internal enum EPermission : byte { @@ -274,7 +316,8 @@ namespace ArchiSteamFarm { None = 0, Forwarding = 1, Distributing = 2, - KeepMissingGames = 4 + KeepMissingGames = 4, + All = Forwarding | Distributing | KeepMissingGames } [Flags] @@ -283,7 +326,8 @@ namespace ArchiSteamFarm { AcceptDonations = 1, SteamTradeMatcher = 2, MatchEverything = 4, - DontAcceptBotTrades = 8 + DontAcceptBotTrades = 8, + All = AcceptDonations | SteamTradeMatcher | MatchEverything | DontAcceptBotTrades } } } \ No newline at end of file diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 99885f708..df16d9fcb 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -894,7 +894,7 @@ namespace ArchiSteamFarm { } ShouldResumeFarming = true; - SortGamesToFarm(); + await SortGamesToFarm().ConfigureAwait(false); return true; } @@ -928,7 +928,7 @@ namespace ArchiSteamFarm { return game.CardsRemaining > 0; } - private void SortGamesToFarm() { + private async Task SortGamesToFarm() { // Put priority idling appIDs on top IOrderedEnumerable gamesToFarm = GamesToFarm.OrderByDescending(game => Bot.IsPriorityIdling(game.AppID)); @@ -952,6 +952,25 @@ namespace ArchiSteamFarm { break; case BotConfig.EFarmingOrder.CardDropsDescending: gamesToFarm = gamesToFarm.ThenByDescending(game => game.CardsRemaining); + break; + case BotConfig.EFarmingOrder.MarketableAscending: + case BotConfig.EFarmingOrder.MarketableDescending: + HashSet marketableAppIDs = await Bot.GetMarketableAppIDs().ConfigureAwait(false); + + if ((marketableAppIDs != null) && (marketableAppIDs.Count > 0)) { + switch (Bot.BotConfig.FarmingOrder) { + case BotConfig.EFarmingOrder.MarketableAscending: + gamesToFarm = gamesToFarm.ThenBy(game => marketableAppIDs.Contains(game.AppID)); + break; + case BotConfig.EFarmingOrder.MarketableDescending: + gamesToFarm = gamesToFarm.ThenByDescending(game => marketableAppIDs.Contains(game.AppID)); + break; + default: + Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder))); + return; + } + } + break; case BotConfig.EFarmingOrder.HoursAscending: gamesToFarm = gamesToFarm.ThenBy(game => game.HoursPlayed); diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs index 10ce93304..879e624b6 100644 --- a/ArchiSteamFarm/GlobalConfig.cs +++ b/ArchiSteamFarm/GlobalConfig.cs @@ -263,10 +263,6 @@ namespace ArchiSteamFarm { return null; } - globalConfig.ShouldSerializeSensitiveDetails = false; - - // User might not know what he's doing - // Ensure that he can't screw core ASF variables if (globalConfig.ConnectionTimeout == 0) { ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(globalConfig.ConnectionTimeout), globalConfig.ConnectionTimeout)); return null; @@ -282,11 +278,22 @@ namespace ArchiSteamFarm { return null; } + if (!Enum.IsDefined(typeof(EOptimizationMode), globalConfig.OptimizationMode)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(globalConfig.OptimizationMode), globalConfig.OptimizationMode)); + return null; + } + if ((globalConfig.SteamProtocols <= 0) || (globalConfig.SteamProtocols > ProtocolTypes.All)) { ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(globalConfig.SteamProtocols), globalConfig.SteamProtocols)); return null; } + if (!Enum.IsDefined(typeof(EUpdateChannel), globalConfig.UpdateChannel)) { + ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(globalConfig.UpdateChannel), globalConfig.UpdateChannel)); + return null; + } + + globalConfig.ShouldSerializeSensitiveDetails = false; return globalConfig; }