From 0db004ae35d92fd2a3d5b66fb27da46272f1ae7e Mon Sep 17 00:00:00 2001 From: JustArchi Date: Sat, 19 Dec 2020 00:25:22 +0100 Subject: [PATCH] Misc reordering --- ArchiSteamFarm/Bot.cs | 542 +++++++++++++++++++++--------------------- 1 file changed, 271 insertions(+), 271 deletions(-) diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index ed7c2b154..cae268a00 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -352,6 +352,23 @@ namespace ArchiSteamFarm { } } + [PublicAPI] + public async Task DeleteAllRelatedFiles() { + await BotDatabase.MakeReadOnly().ConfigureAwait(false); + + foreach (string filePath in RelatedFiles.Select(file => file.FilePath).Where(File.Exists)) { + try { + File.Delete(filePath); + } catch (Exception e) { + ArchiLogger.LogGenericException(e); + + return false; + } + } + + return true; + } + [PublicAPI] public static Bot? GetBot(string botName) { if (string.IsNullOrEmpty(botName)) { @@ -491,6 +508,129 @@ namespace ArchiSteamFarm { return GetFilePath(BotName, fileType); } + [PublicAPI] + public static HashSet GetItemsForFullSets(IReadOnlyCollection inventory, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract) { + if ((inventory == null) || (inventory.Count == 0)) { + throw new ArgumentNullException(nameof(inventory)); + } + + if ((amountsToExtract == null) || (amountsToExtract.Count == 0)) { + throw new ArgumentNullException(nameof(amountsToExtract)); + } + + HashSet result = new(); + Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary>> itemsPerClassIDPerSet = inventory.GroupBy(item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(grouping => grouping.Key, grouping => grouping.GroupBy(item => item.ClassID).ToDictionary(group => group.Key, group => group.ToHashSet())); + + foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract) { + if (!itemsPerClassIDPerSet.TryGetValue(set, out Dictionary>? itemsPerClassID)) { + continue; + } + + if (itemsPerSet < itemsPerClassID.Count) { + throw new InvalidOperationException(nameof(inventory) + " && " + nameof(amountsToExtract)); + } + + if (itemsPerSet > itemsPerClassID.Count) { + continue; + } + + foreach (HashSet itemsOfClass in itemsPerClassID.Values) { + uint classRemaining = setsToExtract; + + foreach (Steam.Asset item in itemsOfClass.TakeWhile(_ => classRemaining > 0)) { + if (item.Amount > classRemaining) { + Steam.Asset itemToSend = item.CreateShallowCopy(); + itemToSend.Amount = classRemaining; + result.Add(itemToSend); + + classRemaining = 0; + } else { + result.Add(item); + + classRemaining -= item.Amount; + } + } + } + } + + return result; + } + + [PublicAPI] + public async Task?> GetPossiblyCompletedBadgeAppIDs() { + using IDocument? badgePage = await ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false); + + if (badgePage == null) { + ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges); + + return null; + } + + byte maxPages = 1; + IElement? htmlNode = badgePage.SelectSingleNode("(//a[@class='pagelink'])[last()]"); + + if (htmlNode != null) { + string lastPage = htmlNode.TextContent; + + if (string.IsNullOrEmpty(lastPage)) { + ArchiLogger.LogNullError(nameof(lastPage)); + + return null; + } + + if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) { + ArchiLogger.LogNullError(nameof(maxPages)); + + return null; + } + } + + HashSet? firstPageResult = GetPossiblyCompletedBadgeAppIDs(badgePage); + + if (firstPageResult == null) { + return null; + } + + if (maxPages == 1) { + return firstPageResult; + } + + switch (ASF.GlobalConfig?.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + for (byte page = 2; page <= maxPages; page++) { + HashSet? pageIDs = await GetPossiblyCompletedBadgeAppIDs(page).ConfigureAwait(false); + + if (pageIDs == null) { + return null; + } + + firstPageResult.UnionWith(pageIDs); + } + + return firstPageResult; + default: + HashSet?>> tasks = new(maxPages - 1); + + for (byte page = 2; page <= maxPages; page++) { + // We need a copy of variable being passed when in for loops, as loop will proceed before our task is launched + byte currentPage = page; + tasks.Add(GetPossiblyCompletedBadgeAppIDs(currentPage)); + } + + IList?> results = await Utilities.InParallel(tasks).ConfigureAwait(false); + + foreach (HashSet? result in results) { + if (result == null) { + return null; + } + + firstPageResult.UnionWith(result); + } + + return firstPageResult; + } + } + [PublicAPI] public async Task GetTradeHoldDuration(ulong steamID, ulong tradeID) { if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { @@ -550,6 +690,37 @@ namespace ArchiSteamFarm { }; } + [PublicAPI] + public async Task?> LoadCardsPerSet(IReadOnlyCollection appIDs) { + if ((appIDs == null) || (appIDs.Count == 0)) { + throw new ArgumentNullException(nameof(appIDs)); + } + + ISet uniqueAppIDs = appIDs as ISet ?? appIDs.ToHashSet(); + + switch (ASF.GlobalConfig?.OptimizationMode) { + case GlobalConfig.EOptimizationMode.MinMemoryUsage: + Dictionary result = new(uniqueAppIDs.Count); + + foreach (uint appID in uniqueAppIDs) { + byte cardCount = await ArchiWebHandler.GetCardCountForGame(appID).ConfigureAwait(false); + + if (cardCount == 0) { + return null; + } + + result.Add(appID, cardCount); + } + + return result; + default: + IEnumerable> tasks = uniqueAppIDs.Select(async appID => (AppID: appID, Cards: await ArchiWebHandler.GetCardCountForGame(appID).ConfigureAwait(false))); + IList<(uint AppID, byte Cards)> results = await Utilities.InParallel(tasks).ConfigureAwait(false); + + return results.All(tuple => tuple.Cards > 0) ? results.ToDictionary(res => res.AppID, res => res.Cards) : null; + } + } + [PublicAPI] public async Task SendMessage(ulong steamID, string message) { if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { @@ -650,6 +821,106 @@ namespace ArchiSteamFarm { return true; } + [PublicAPI] + public async Task SendMessage(ulong chatGroupID, ulong chatID, string message) { + if (chatGroupID == 0) { + throw new ArgumentOutOfRangeException(nameof(chatGroupID)); + } + + if (chatID == 0) { + throw new ArgumentOutOfRangeException(nameof(chatID)); + } + + if (string.IsNullOrEmpty(message)) { + throw new ArgumentNullException(nameof(message)); + } + + if (!IsConnectedAndLoggedOn) { + return false; + } + + ArchiLogger.LogChatMessage(true, message, chatGroupID, chatID); + + string? steamMessagePrefix = ASF.GlobalConfig != null ? ASF.GlobalConfig.SteamMessagePrefix : GlobalConfig.DefaultSteamMessagePrefix; + ushort maxMessageLength = (ushort) (MaxMessageLength - ReservedMessageLength - (steamMessagePrefix?.Length ?? 0)); + + // We must escape our message prior to sending it + message = Escape(message); + + int i = 0; + + // ReSharper disable ArrangeMissingParentheses - conflict with Roslyn + while (i < message.Length) { + int partLength; + bool copyNewline = false; + + if (message.Length - i > maxMessageLength) { + int lastNewLine = message.LastIndexOf(Environment.NewLine, i + maxMessageLength - Environment.NewLine.Length, maxMessageLength - Environment.NewLine.Length, StringComparison.Ordinal); + + if (lastNewLine > i) { + partLength = lastNewLine - i + Environment.NewLine.Length; + copyNewline = true; + } else { + partLength = maxMessageLength; + } + } else { + partLength = message.Length - i; + } + + // If our message is of max length and ends with a single '\' then we can't split it here, it escapes the next character + if ((partLength >= maxMessageLength) && (message[i + partLength - 1] == '\\') && (message[i + partLength - 2] != '\\')) { + // Instead, we'll cut this message one char short and include the rest in next iteration + partLength--; + } + + // ReSharper restore ArrangeMissingParentheses + string messagePart = message.Substring(i, partLength); + + messagePart = steamMessagePrefix + (i > 0 ? "…" : "") + messagePart + (maxMessageLength < message.Length - i ? "…" : ""); + + await MessagingSemaphore.WaitAsync().ConfigureAwait(false); + + try { + bool sent = false; + + for (byte j = 0; (j < WebBrowser.MaxTries) && !sent && IsConnectedAndLoggedOn; j++) { + EResult result = await ArchiHandler.SendMessage(chatGroupID, chatID, messagePart).ConfigureAwait(false); + + switch (result) { + case EResult.Busy: + case EResult.Fail: + case EResult.RateLimitExceeded: + case EResult.ServiceUnavailable: + case EResult.Timeout: + await Task.Delay(5000).ConfigureAwait(false); + + continue; + case EResult.OK: + sent = true; + + break; + default: + ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(result), result)); + + return false; + } + } + + if (!sent) { + ArchiLogger.LogGenericWarning(Strings.WarningFailed); + + return false; + } + } finally { + MessagingSemaphore.Release(); + } + + i += partLength - (copyNewline ? Environment.NewLine.Length : 0); + } + + return true; + } + [PublicAPI] public bool SetUserInput(ASF.EUserInputType inputType, string inputValue) { if ((inputType == ASF.EUserInputType.None) || !Enum.IsDefined(typeof(ASF.EUserInputType), inputType)) { @@ -727,23 +998,6 @@ namespace ArchiSteamFarm { } } - [PublicAPI] - public async Task DeleteAllRelatedFiles() { - await BotDatabase.MakeReadOnly().ConfigureAwait(false); - - foreach (string filePath in RelatedFiles.Select(file => file.FilePath).Where(File.Exists)) { - try { - File.Delete(filePath); - } catch (Exception e) { - ArchiLogger.LogGenericException(e); - - return false; - } - } - - return true; - } - internal bool DeleteRedeemedKeysFiles() { string unusedKeysFilePath = GetFilePath(EFileType.KeysToRedeemUnused); @@ -1410,106 +1664,6 @@ namespace ArchiSteamFarm { SteamFriends.RequestFriendInfo(SteamID, EClientPersonaStateFlag.PlayerName | EClientPersonaStateFlag.Presence); } - [PublicAPI] - public async Task SendMessage(ulong chatGroupID, ulong chatID, string message) { - if (chatGroupID == 0) { - throw new ArgumentOutOfRangeException(nameof(chatGroupID)); - } - - if (chatID == 0) { - throw new ArgumentOutOfRangeException(nameof(chatID)); - } - - if (string.IsNullOrEmpty(message)) { - throw new ArgumentNullException(nameof(message)); - } - - if (!IsConnectedAndLoggedOn) { - return false; - } - - ArchiLogger.LogChatMessage(true, message, chatGroupID, chatID); - - string? steamMessagePrefix = ASF.GlobalConfig != null ? ASF.GlobalConfig.SteamMessagePrefix : GlobalConfig.DefaultSteamMessagePrefix; - ushort maxMessageLength = (ushort) (MaxMessageLength - ReservedMessageLength - (steamMessagePrefix?.Length ?? 0)); - - // We must escape our message prior to sending it - message = Escape(message); - - int i = 0; - - // ReSharper disable ArrangeMissingParentheses - conflict with Roslyn - while (i < message.Length) { - int partLength; - bool copyNewline = false; - - if (message.Length - i > maxMessageLength) { - int lastNewLine = message.LastIndexOf(Environment.NewLine, i + maxMessageLength - Environment.NewLine.Length, maxMessageLength - Environment.NewLine.Length, StringComparison.Ordinal); - - if (lastNewLine > i) { - partLength = lastNewLine - i + Environment.NewLine.Length; - copyNewline = true; - } else { - partLength = maxMessageLength; - } - } else { - partLength = message.Length - i; - } - - // If our message is of max length and ends with a single '\' then we can't split it here, it escapes the next character - if ((partLength >= maxMessageLength) && (message[i + partLength - 1] == '\\') && (message[i + partLength - 2] != '\\')) { - // Instead, we'll cut this message one char short and include the rest in next iteration - partLength--; - } - - // ReSharper restore ArrangeMissingParentheses - string messagePart = message.Substring(i, partLength); - - messagePart = steamMessagePrefix + (i > 0 ? "…" : "") + messagePart + (maxMessageLength < message.Length - i ? "…" : ""); - - await MessagingSemaphore.WaitAsync().ConfigureAwait(false); - - try { - bool sent = false; - - for (byte j = 0; (j < WebBrowser.MaxTries) && !sent && IsConnectedAndLoggedOn; j++) { - EResult result = await ArchiHandler.SendMessage(chatGroupID, chatID, messagePart).ConfigureAwait(false); - - switch (result) { - case EResult.Busy: - case EResult.Fail: - case EResult.RateLimitExceeded: - case EResult.ServiceUnavailable: - case EResult.Timeout: - await Task.Delay(5000).ConfigureAwait(false); - - continue; - case EResult.OK: - sent = true; - - break; - default: - ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(result), result)); - - return false; - } - } - - if (!sent) { - ArchiLogger.LogGenericWarning(Strings.WarningFailed); - - return false; - } - } finally { - MessagingSemaphore.Release(); - } - - i += partLength - (copyNewline ? Environment.NewLine.Length : 0); - } - - return true; - } - internal async Task SendTypingMessage(ulong steamID) { if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) { throw new ArgumentOutOfRangeException(nameof(steamID)); @@ -2937,81 +3091,6 @@ namespace ArchiSteamFarm { } } - [PublicAPI] - public async Task?> GetPossiblyCompletedBadgeAppIDs() { - using IDocument? badgePage = await ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false); - - if (badgePage == null) { - ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges); - - return null; - } - - byte maxPages = 1; - IElement? htmlNode = badgePage.SelectSingleNode("(//a[@class='pagelink'])[last()]"); - - if (htmlNode != null) { - string lastPage = htmlNode.TextContent; - - if (string.IsNullOrEmpty(lastPage)) { - ArchiLogger.LogNullError(nameof(lastPage)); - - return null; - } - - if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) { - ArchiLogger.LogNullError(nameof(maxPages)); - - return null; - } - } - - HashSet? firstPageResult = GetPossiblyCompletedBadgeAppIDs(badgePage); - - if (firstPageResult == null) { - return null; - } - - if (maxPages == 1) { - return firstPageResult; - } - - switch (ASF.GlobalConfig?.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - for (byte page = 2; page <= maxPages; page++) { - HashSet? pageIDs = await GetPossiblyCompletedBadgeAppIDs(page).ConfigureAwait(false); - - if (pageIDs == null) { - return null; - } - - firstPageResult.UnionWith(pageIDs); - } - - return firstPageResult; - default: - HashSet?>> tasks = new(maxPages - 1); - - for (byte page = 2; page <= maxPages; page++) { - // We need a copy of variable being passed when in for loops, as loop will proceed before our task is launched - byte currentPage = page; - tasks.Add(GetPossiblyCompletedBadgeAppIDs(currentPage)); - } - - IList?> results = await Utilities.InParallel(tasks).ConfigureAwait(false); - - foreach (HashSet? result in results) { - if (result == null) { - return null; - } - - firstPageResult.UnionWith(result); - } - - return firstPageResult; - } - } - private async Task?> GetPossiblyCompletedBadgeAppIDs(byte page) { if (page == 0) { throw new ArgumentOutOfRangeException(nameof(page)); @@ -3138,85 +3217,6 @@ namespace ArchiSteamFarm { } } - [PublicAPI] - public async Task?> LoadCardsPerSet(IReadOnlyCollection appIDs) { - if ((appIDs == null) || (appIDs.Count == 0)) { - throw new ArgumentNullException(nameof(appIDs)); - } - - ISet uniqueAppIDs = appIDs as ISet ?? appIDs.ToHashSet(); - - switch (ASF.GlobalConfig?.OptimizationMode) { - case GlobalConfig.EOptimizationMode.MinMemoryUsage: - Dictionary result = new(uniqueAppIDs.Count); - - foreach (uint appID in uniqueAppIDs) { - byte cardCount = await ArchiWebHandler.GetCardCountForGame(appID).ConfigureAwait(false); - - if (cardCount == 0) { - return null; - } - - result.Add(appID, cardCount); - } - - return result; - default: - IEnumerable> tasks = uniqueAppIDs.Select(async appID => (AppID: appID, Cards: await ArchiWebHandler.GetCardCountForGame(appID).ConfigureAwait(false))); - IList<(uint AppID, byte Cards)> results = await Utilities.InParallel(tasks).ConfigureAwait(false); - - return results.All(tuple => tuple.Cards > 0) ? results.ToDictionary(res => res.AppID, res => res.Cards) : null; - } - } - - [PublicAPI] - public static HashSet GetItemsForFullSets(IReadOnlyCollection inventory, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), (uint SetsToExtract, byte ItemsPerSet)> amountsToExtract) { - if ((inventory == null) || (inventory.Count == 0)) { - throw new ArgumentNullException(nameof(inventory)); - } - - if ((amountsToExtract == null) || (amountsToExtract.Count == 0)) { - throw new ArgumentNullException(nameof(amountsToExtract)); - } - - HashSet result = new(); - Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary>> itemsPerClassIDPerSet = inventory.GroupBy(item => (item.RealAppID, item.Type, item.Rarity)).ToDictionary(grouping => grouping.Key, grouping => grouping.GroupBy(item => item.ClassID).ToDictionary(group => group.Key, group => group.ToHashSet())); - - foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, (uint setsToExtract, byte itemsPerSet)) in amountsToExtract) { - if (!itemsPerClassIDPerSet.TryGetValue(set, out Dictionary>? itemsPerClassID)) { - continue; - } - - if (itemsPerSet < itemsPerClassID.Count) { - throw new InvalidOperationException(nameof(inventory) + " && " + nameof(amountsToExtract)); - } - - if (itemsPerSet > itemsPerClassID.Count) { - continue; - } - - foreach (HashSet itemsOfClass in itemsPerClassID.Values) { - uint classRemaining = setsToExtract; - - foreach (Steam.Asset item in itemsOfClass.TakeWhile(_ => classRemaining > 0)) { - if (item.Amount > classRemaining) { - Steam.Asset itemToSend = item.CreateShallowCopy(); - itemToSend.Amount = classRemaining; - result.Add(itemToSend); - - classRemaining = 0; - } else { - result.Add(item); - - classRemaining -= item.Amount; - } - } - } - } - - return result; - } - private void OnVanityURLChangedCallback(ArchiHandler.VanityURLChangedCallback callback) { if (callback == null) { throw new ArgumentNullException(nameof(callback));