diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index b363279d6..2fa4a4c13 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -665,6 +665,15 @@ namespace ArchiSteamFarm { return 0; } + internal async Task GetSteamAwardsPage() { + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + const string request = SteamStoreURL + "/SteamAwards?l=english"; + return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); + } + internal async Task GetTradeHoldDuration(ulong tradeID) { if (tradeID == 0) { Bot.ArchiLogger.LogNullError(nameof(tradeID)); @@ -1075,6 +1084,32 @@ namespace ArchiSteamFarm { return true; } + internal async Task SteamAwardsVote(byte voteID, uint appID) { + if ((voteID == 0) || (appID == 0)) { + Bot.ArchiLogger.LogNullError(nameof(voteID) + " || " + nameof(appID)); + return false; + } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + + string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid"); + if (string.IsNullOrEmpty(sessionID)) { + Bot.ArchiLogger.LogNullError(nameof(sessionID)); + return false; + } + + const string request = SteamStoreURL + "/salevote"; + Dictionary data = new Dictionary(3) { + { "sessionid", sessionID }, + { "voteid", voteID.ToString() }, + { "appid", appID.ToString() } + }; + + return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false); + } + internal async Task UnpackBooster(uint appID, ulong itemID) { if ((appID == 0) || (itemID == 0)) { Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(itemID)); diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 68631c897..28f8d6f9a 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -1352,7 +1352,7 @@ namespace ArchiSteamFarm { SteamSaleEvent = null; } - if (BotConfig.AutoDiscoveryQueue) { + if (BotConfig.AutoSteamSaleEvent) { SteamSaleEvent = new SteamSaleEvent(this); } } diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index 9683d22a6..b8f1d9fe5 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -39,7 +39,7 @@ namespace ArchiSteamFarm { internal readonly bool AcceptGifts; [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool AutoDiscoveryQueue; + internal readonly bool AutoSteamSaleEvent; [JsonProperty] internal readonly string CustomGamePlayedWhileFarming; diff --git a/ArchiSteamFarm/SteamSaleEvent.cs b/ArchiSteamFarm/SteamSaleEvent.cs index 045ffde53..c3585eae5 100644 --- a/ArchiSteamFarm/SteamSaleEvent.cs +++ b/ArchiSteamFarm/SteamSaleEvent.cs @@ -31,20 +31,20 @@ namespace ArchiSteamFarm { private const byte MaxSingleQueuesDaily = 3; // This is mainly a pre-caution for infinite queue clearing private readonly Bot Bot; - private readonly Timer SteamDiscoveryQueueTimer; + private readonly Timer SaleEventTimer; internal SteamSaleEvent(Bot bot) { Bot = bot ?? throw new ArgumentNullException(nameof(bot)); - SteamDiscoveryQueueTimer = new Timer( - async e => await ExploreDiscoveryQueue().ConfigureAwait(false), + SaleEventTimer = new Timer( + async e => await Task.WhenAll(ExploreDiscoveryQueue(), VoteForSteamAwards()).ConfigureAwait(false), null, TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(Program.LoadBalancingDelay * Bot.Bots.Count), // Delay TimeSpan.FromHours(6.1) // Period ); } - public void Dispose() => SteamDiscoveryQueueTimer.Dispose(); + public void Dispose() => SaleEventTimer.Dispose(); private async Task ExploreDiscoveryQueue() { if (!Bot.IsConnectedAndLoggedOn) { @@ -102,5 +102,56 @@ namespace ArchiSteamFarm { bool result = text.StartsWith("You can get ", StringComparison.Ordinal); return result; } + + private async Task VoteForSteamAwards() { + HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetSteamAwardsPage().ConfigureAwait(false); + + HtmlNodeCollection nominationNodes = htmlDocument?.DocumentNode.SelectNodes("//div[@class='vote_nominations store_horizontal_autoslider']"); + if (nominationNodes == null) { + // Event ended, error or likewise + return; + } + + foreach (HtmlNode nominationNode in nominationNodes) { + HtmlNode myVoteNode = nominationNode.SelectSingleNode("./div[@class='vote_nomination your_vote']"); + if (myVoteNode != null) { + // Already voted + continue; + } + + string voteIDText = nominationNode.GetAttributeValue("data-voteid", null); + if (string.IsNullOrEmpty(voteIDText)) { + Bot.ArchiLogger.LogNullError(nameof(voteIDText)); + return; + } + + if (!byte.TryParse(voteIDText, out byte voteID) || (voteID == 0)) { + Bot.ArchiLogger.LogNullError(nameof(voteID)); + return; + } + + HtmlNodeCollection voteNodes = nominationNode.SelectNodes("./div[starts-with(@class, 'vote_nomination')]"); + if (voteNodes == null) { + Bot.ArchiLogger.LogNullError(nameof(voteNodes)); + return; + } + + // Random a game we'll actually vote for, we don't want to make GabeN angry by rigging votes... + HtmlNode voteNode = voteNodes[Utilities.RandomNext(voteNodes.Count)]; + + string appIDText = voteNode.GetAttributeValue("data-vote-appid", null); + if (string.IsNullOrEmpty(appIDText)) { + Bot.ArchiLogger.LogNullError(nameof(appIDText)); + return; + } + + if (!uint.TryParse(appIDText, out uint appID) || (appID == 0)) { + Bot.ArchiLogger.LogNullError(nameof(appID)); + return; + } + + await Bot.ArchiWebHandler.SteamAwardsVote(voteID, appID).ConfigureAwait(false); + } + } } } \ No newline at end of file diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index 74537e01a..fbffd0f7a 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -104,6 +104,21 @@ namespace ArchiSteamFarm { } } + internal static int RandomNext(int maxWithout) { + if (maxWithout <= 0) { + ASF.ArchiLogger.LogNullError(nameof(maxWithout)); + return -1; + } + + if (maxWithout == 1) { + return 0; + } + + lock (Random) { + return Random.Next(maxWithout); + } + } + internal static string ReadLineMasked(char mask = '*') { StringBuilder result = new StringBuilder(); diff --git a/ArchiSteamFarm/config/example.json b/ArchiSteamFarm/config/example.json index f1191774b..ff7344365 100644 --- a/ArchiSteamFarm/config/example.json +++ b/ArchiSteamFarm/config/example.json @@ -1,6 +1,6 @@ { "AcceptGifts": false, - "AutoDiscoveryQueue": false, + "AutoSteamSaleEvent": false, "CustomGamePlayedWhileFarming": null, "CustomGamePlayedWhileIdle": null, "DismissInventoryNotifications": false,