From d012255a217cd15987c8d0a0b883703617382f2f Mon Sep 17 00:00:00 2001 From: JustArchi Date: Tue, 29 Mar 2016 14:33:05 +0200 Subject: [PATCH] EXPERIMENTAL: Steam session improvements 1. Make sure that every call to steamcommunity has active session 2. Move whole userspace logic for session handling to ArchiWebHandler (and Bot) 3. Implement session caching and TTL so we won't send IsLoggedIn() on each ArchiWebHandler call 4. Instead of restarting whole steam account, just refresh the session via ArchiWebHandler instead --- ArchiSteamFarm/ArchiWebHandler.cs | 100 +++++++++++++++++++++++------- ArchiSteamFarm/Bot.cs | 41 +++++++----- ArchiSteamFarm/CardsFarmer.cs | 5 -- ArchiSteamFarm/GlobalConfig.cs | 3 +- ArchiSteamFarm/WebBrowser.cs | 2 +- 5 files changed, 106 insertions(+), 45 deletions(-) diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 1702d7255..1717981fa 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -32,21 +32,23 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Xml; +using System.Threading; namespace ArchiSteamFarm { internal sealed class ArchiWebHandler { private const string SteamCommunity = "steamcommunity.com"; + private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds private static string SteamCommunityURL = "https://" + SteamCommunity; - private static int Timeout = 30 * 1000; + private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000; private readonly Bot Bot; private readonly Dictionary Cookie = new Dictionary(4); - - internal bool IsInitialized { get; private set; } + private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1); private ulong SteamID; + private DateTime LastSessionRefreshCheck = DateTime.MinValue; internal static void Init() { Timeout = Program.GlobalConfig.HttpTimeout * 1000; @@ -61,12 +63,8 @@ namespace ArchiSteamFarm { Bot = bot; } - internal void OnDisconnected() { - IsInitialized = false; - } - - internal async Task Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) { - if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce) || IsInitialized) { + internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) { + if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) { return false; } @@ -127,9 +125,11 @@ namespace ArchiSteamFarm { // The below is used for display purposes only Cookie["webTradeEligibility"] = "{\"allowed\":0,\"reason\":0,\"allowed_at_time\":0,\"steamguard_required_days\":0,\"sales_this_year\":0,\"max_sales_per_year\":0,\"forms_requested\":0}"; - await UnlockParentalAccount(parentalPin).ConfigureAwait(false); + if (!UnlockParentalAccount(parentalPin).Result) { + return false; + } - IsInitialized = true; + LastSessionRefreshCheck = DateTime.Now; return true; } @@ -152,15 +152,34 @@ namespace ArchiSteamFarm { return htmlNode != null; } - internal async Task ReconnectIfNeeded() { - bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false); - if (isLoggedIn.HasValue && !isLoggedIn.Value) { - Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName); - Bot.RestartIfRunning().Forget(); + internal async Task RefreshSessionIfNeeded() { + DateTime now = DateTime.Now; + if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) { return true; } - return false; + await SessionSemaphore.WaitAsync().ConfigureAwait(false); + + now = DateTime.Now; + if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) { + SessionSemaphore.Release(); + return true; + } + + bool result; + + bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false); + if (isLoggedIn.GetValueOrDefault(true)) { + result = true; + now = DateTime.Now; + LastSessionRefreshCheck = now; + } else { + Logging.LogGenericInfo("Refreshing our session!", Bot.BotName); + result = await Bot.RefreshSession().ConfigureAwait(false); + } + + SessionSemaphore.Release(); + return result; } internal async Task> GetOwnedGames() { @@ -168,6 +187,10 @@ namespace ArchiSteamFarm { return null; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + string request = SteamCommunityURL + "/profiles/" + SteamID + "/games/?xml=1"; XmlDocument response = null; @@ -273,6 +296,10 @@ namespace ArchiSteamFarm { return false; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + string sessionID; if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false; @@ -303,6 +330,10 @@ namespace ArchiSteamFarm { return false; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + string sessionID; if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false; @@ -361,6 +392,10 @@ namespace ArchiSteamFarm { } internal async Task> GetMyTradableInventory() { + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + JObject jObject = null; for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) { jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1", Cookie).ConfigureAwait(false); @@ -394,6 +429,10 @@ namespace ArchiSteamFarm { return false; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + string sessionID; if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false; @@ -453,6 +492,10 @@ namespace ArchiSteamFarm { return null; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + HtmlDocument htmlDocument = null; for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/badges?l=english&p=" + page, Cookie).ConfigureAwait(false); @@ -471,6 +514,10 @@ namespace ArchiSteamFarm { return null; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + HtmlDocument htmlDocument = null; for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) { htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/gamecards/" + appID + "?l=english", Cookie).ConfigureAwait(false); @@ -489,6 +536,10 @@ namespace ArchiSteamFarm { return false; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + HttpResponseMessage response = null; for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { response = await WebBrowser.UrlGet(SteamCommunityURL + "/profiles/" + SteamID + "/inventory", Cookie).ConfigureAwait(false); @@ -507,6 +558,10 @@ namespace ArchiSteamFarm { return false; } + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + string sessionID; if (!Cookie.TryGetValue("sessionid", out sessionID)) { return false; @@ -530,9 +585,9 @@ namespace ArchiSteamFarm { return true; } - private async Task UnlockParentalAccount(string parentalPin) { + private async Task UnlockParentalAccount(string parentalPin) { if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) { - return; + return true; } Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName); @@ -550,13 +605,13 @@ namespace ArchiSteamFarm { if (response == null) { Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); - return; + return false; } IEnumerable setCookieValues; if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) { Logging.LogNullError("setCookieValues", Bot.BotName); - return; + return false; } foreach (string setCookieValue in setCookieValues) { @@ -573,10 +628,11 @@ namespace ArchiSteamFarm { Cookie["steamparental"] = setCookie; Logging.LogGenericInfo("Success!", Bot.BotName); - return; + return true; } Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName); + return false; } } } diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 41764b68e..04629bfdb 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -187,6 +187,7 @@ namespace ArchiSteamFarm { CallbackManager.Subscribe(OnLoggedOn); CallbackManager.Subscribe(OnLoginKey); CallbackManager.Subscribe(OnMachineAuth); + CallbackManager.Subscribe(OnWebAPIUserNonce); CallbackManager.Subscribe(OnNotifications); CallbackManager.Subscribe(OnOfflineMessage); @@ -261,12 +262,23 @@ namespace ArchiSteamFarm { } } - internal async Task RestartIfRunning() { + internal async Task RefreshSession() { if (!SteamClient.IsConnected) { - return; + return false; } - await Start().ConfigureAwait(false); + var userNonce = await SteamUser.RequestWebAPIUserNonce(); + if (userNonce == null || userNonce.Result != EResult.OK || string.IsNullOrEmpty(userNonce.Nonce)) { + Start().Forget(); + return false; + } + + if (!ArchiWebHandler.Init(SteamClient, userNonce.Nonce, BotConfig.SteamParentalPIN)) { + Start().Forget(); + return false; + } + + return true; } internal async Task OnFarmingFinished(bool farmedSomething) { @@ -1300,7 +1312,6 @@ namespace ArchiSteamFarm { } Logging.LogGenericInfo("Disconnected from Steam!", BotName); - ArchiWebHandler.OnDisconnected(); CardsFarmer.StopFarming().Forget(); // If we initiated disconnect, do not attempt to reconnect @@ -1354,15 +1365,6 @@ namespace ArchiSteamFarm { return; } - for (byte i = 0; i < WebBrowser.MaxRetries && !ArchiWebHandler.IsInitialized; i++) { - await Utilities.SleepAsync(1000).ConfigureAwait(false); - } - - if (!ArchiWebHandler.IsInitialized) { - Logging.LogGenericWarning("Reached timeout while waiting for ArchiWebHandler to initialize!"); - return; - } - bool acceptedSomething = false; foreach (KeyValue guestPass in callback.GuestPasses) { ulong gid = guestPass["gid"].AsUnsignedLong(); @@ -1538,9 +1540,10 @@ namespace ArchiSteamFarm { BotConfig.SteamParentalPIN = Program.GetUserInput(Program.EUserInputType.SteamParentalPIN, BotName); } - if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) { - await RestartIfRunning().ConfigureAwait(false); - return; + if (!ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN)) { + if (!await RefreshSession().ConfigureAwait(false)) { + return; + } } if (BotConfig.DismissInventoryNotifications) { @@ -1624,6 +1627,12 @@ namespace ArchiSteamFarm { } } + private void OnWebAPIUserNonce(SteamUser.WebAPIUserNonceCallback callback) { + if (callback == null) { + return; + } + } + private async void OnNotifications(ArchiHandler.NotificationsCallback callback) { if (callback == null || callback.Notifications == null) { return; diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 5cbf0d7e9..a133a121c 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -211,10 +211,6 @@ namespace ArchiSteamFarm { return true; } - if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) { - return false; - } - Logging.LogGenericInfo("Checking badges...", Bot.BotName); // Find the number of badge pages @@ -362,7 +358,6 @@ namespace ArchiSteamFarm { HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']"); if (htmlNode == null) { - await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false); return null; } diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs index fc5d2a11a..d8c3f5577 100644 --- a/ArchiSteamFarm/GlobalConfig.cs +++ b/ArchiSteamFarm/GlobalConfig.cs @@ -36,9 +36,10 @@ namespace ArchiSteamFarm { Experimental } + internal const byte DefaultHttpTimeout = 60; + private const byte DefaultMaxFarmingTime = 10; private const byte DefaultFarmingDelay = 5; - private const byte DefaultHttpTimeout = 60; private const ushort DefaultWCFPort = 1242; private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp; diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index b9d51fffe..ce3838a2f 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -224,7 +224,7 @@ namespace ArchiSteamFarm { if (!responseMessage.IsSuccessStatusCode) { if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) { - Logging.LogGenericError("Request: " + request + "failed!"); + Logging.LogGenericError("Request: " + request + " failed!"); Logging.LogGenericError("Status code: " + responseMessage.StatusCode); Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false)); }