From af61ad66c69cc766320b0246c5f7b0bcdc6d8271 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Mon, 22 Feb 2016 18:34:45 +0100 Subject: [PATCH] Big code review --- ArchiSteamFarm/ArchiHandler.cs | 30 +- ArchiSteamFarm/ArchiWebHandler.cs | 165 +++---- ArchiSteamFarm/Bot.cs | 445 ++++++++++--------- ArchiSteamFarm/CMsgClientClanInviteAction.cs | 13 +- ArchiSteamFarm/CardsFarmer.cs | 22 +- ArchiSteamFarm/Logging.cs | 118 +++-- ArchiSteamFarm/SteamItem.cs | 21 +- ArchiSteamFarm/SteamItemList.cs | 12 +- ArchiSteamFarm/SteamTradeOffer.cs | 46 +- ArchiSteamFarm/SteamTradeOfferRequest.cs | 18 +- ArchiSteamFarm/Utilities.cs | 8 - ArchiSteamFarm/WebBrowser.cs | 312 ++++--------- 12 files changed, 507 insertions(+), 703 deletions(-) diff --git a/ArchiSteamFarm/ArchiHandler.cs b/ArchiSteamFarm/ArchiHandler.cs index ba731398b..bd417c274 100644 --- a/ArchiSteamFarm/ArchiHandler.cs +++ b/ArchiSteamFarm/ArchiHandler.cs @@ -26,6 +26,7 @@ using SteamKit2; using SteamKit2.Internal; using System.Collections.Generic; using System.IO; +using System.Net; namespace ArchiSteamFarm { internal sealed class ArchiHandler : ClientMsgHandler { @@ -39,7 +40,7 @@ namespace ArchiSteamFarm { */ internal sealed class NotificationsCallback : CallbackMsg { - internal class Notification { + internal sealed class Notification { internal enum ENotificationType { Unknown = 0, Trading = 1, @@ -48,7 +49,7 @@ namespace ArchiSteamFarm { internal ENotificationType NotificationType { get; set; } } - internal List Notifications { get; private set; } + internal readonly List Notifications; internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) { JobID = jobID; @@ -67,8 +68,8 @@ namespace ArchiSteamFarm { } internal sealed class OfflineMessageCallback : CallbackMsg { - internal uint OfflineMessages { get; private set; } - internal List Users { get; private set; } + internal readonly uint OfflineMessages; + internal readonly List Users; internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) { JobID = jobID; @@ -94,10 +95,10 @@ namespace ArchiSteamFarm { OnCooldown = 53 } - internal EResult Result { get; private set; } - internal EPurchaseResult PurchaseResult { get; private set; } - internal KeyValue ReceiptInfo { get; private set; } - internal Dictionary Items { get; private set; } + internal readonly EResult Result; + internal readonly EPurchaseResult PurchaseResult; + internal readonly KeyValue ReceiptInfo; + internal readonly Dictionary Items; internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) { JobID = jobID; @@ -106,21 +107,22 @@ namespace ArchiSteamFarm { return; } - ReceiptInfo = new KeyValue(); - Items = new Dictionary(); - Result = (EResult) msg.eresult; PurchaseResult = (EPurchaseResult) msg.purchase_result_details; + ReceiptInfo = new KeyValue(); using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) { if (!ReceiptInfo.TryReadAsBinary(ms)) { return; } - foreach (KeyValue lineItem in ReceiptInfo["lineitems"].Children) { + List lineItems = ReceiptInfo["lineitems"].Children; + Items = new Dictionary(lineItems.Count); + + foreach (KeyValue lineItem in lineItems) { uint appID = (uint) lineItem["PackageID"].AsUnsignedLong(); string gameName = lineItem["ItemDescription"].AsString(); - gameName = Utilities.UrlDecode(gameName); // Apparently steam expects client to decode sent HTML + gameName = WebUtility.UrlDecode(gameName); // Apparently steam expects client to decode sent HTML Items.Add(appID, gameName); } } @@ -180,7 +182,7 @@ namespace ArchiSteamFarm { } internal void PlayGames(ICollection gameIDs) { - if (!Client.IsConnected) { + if (gameIDs == null || gameIDs.Count == 0 || !Client.IsConnected) { return; } diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 6096f4a81..13632b48a 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -27,7 +27,6 @@ using HtmlAgilityPack; using SteamKit2; using System; using System.Collections.Generic; -using System.Globalization; using System.Net; using System.Net.Http; using System.Text; @@ -39,60 +38,9 @@ namespace ArchiSteamFarm { private readonly Bot Bot; private readonly string ApiKey; - private readonly Dictionary Cookie = new Dictionary(); + private readonly Dictionary Cookie = new Dictionary(3); private ulong SteamID; - private string VanityURL; - - // This is required because home_process request must be done on final URL - private string GetHomeProcess() { - if (!string.IsNullOrEmpty(VanityURL)) { - return "https://steamcommunity.com/id/" + VanityURL + "/home_process"; - } else if (SteamID != 0) { - return "https://steamcommunity.com/profiles/" + SteamID + "/home_process"; - } else { - return null; - } - } - - private async Task UnlockParentalAccount(string parentalPin) { - if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) { - return; - } - - Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName); - Dictionary data = new Dictionary() { - { "pin", parentalPin } - }; - - HttpResponseMessage response = null; - for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { - response = await WebBrowser.UrlPost("https://steamcommunity.com/parental/ajaxunlock", data, Cookie, "https://steamcommunity.com/").ConfigureAwait(false); - } - - if (response == null) { - Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); - return; - } - - IEnumerable setCookieValues; - if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) { - Logging.LogNullError("setCookieValues", Bot.BotName); - return; - } - - foreach (string setCookieValue in setCookieValues) { - if (setCookieValue.Contains("steamparental=")) { - string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=") + 14); - setCookie = setCookie.Substring(0, setCookie.IndexOf(';')); - Cookie["steamparental"] = setCookie; - Logging.LogGenericInfo("Success!", Bot.BotName); - return; - } - } - - Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName); - } internal ArchiWebHandler(Bot bot, string apiKey) { Bot = bot; @@ -102,13 +50,12 @@ namespace ArchiSteamFarm { } } - internal async Task Init(SteamClient steamClient, string webAPIUserNonce, string vanityURL, string parentalPin) { + internal async Task Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) { if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) { return false; } SteamID = steamClient.SteamID; - VanityURL = vanityURL; string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString())); @@ -161,7 +108,6 @@ namespace ArchiSteamFarm { Cookie["sessionid"] = sessionID; Cookie["steamLogin"] = steamLogin; Cookie["steamLoginSecure"] = steamLoginSecure; - Cookie["birthtime"] = "-473356799"; await UnlockParentalAccount(parentalPin).ConfigureAwait(false); return true; @@ -229,28 +175,16 @@ namespace ArchiSteamFarm { SteamTradeOffer tradeOffer = new SteamTradeOffer { tradeofferid = trade["tradeofferid"].AsString(), accountid_other = trade["accountid_other"].AsInteger(), - message = trade["message"].AsString(), - expiration_time = trade["expiration_time"].AsInteger(), - trade_offer_state = trade["trade_offer_state"].AsEnum(), - items_to_give = new List(), - items_to_receive = new List(), - is_our_offer = trade["is_our_offer"].AsBoolean(), - time_created = trade["time_created"].AsInteger(), - time_updated = trade["time_updated"].AsInteger(), - from_real_time_trade = trade["from_real_time_trade"].AsBoolean(), - escrow_end_date = trade["escrow_end_date"].AsInteger(), - confirmation_method = trade["confirmation_method"].AsEnum() + trade_offer_state = trade["trade_offer_state"].AsEnum() }; foreach (KeyValue item in trade["items_to_give"].Children) { tradeOffer.items_to_give.Add(new SteamItem { appid = item["appid"].AsString(), contextid = item["contextid"].AsString(), assetid = item["assetid"].AsString(), - currencyid = item["currencyid"].AsString(), classid = item["classid"].AsString(), instanceid = item["instanceid"].AsString(), amount = item["amount"].AsString(), - missing = item["missing"].AsBoolean() }); } foreach (KeyValue item in trade["items_to_receive"].Children) { @@ -258,11 +192,9 @@ namespace ArchiSteamFarm { appid = item["appid"].AsString(), contextid = item["contextid"].AsString(), assetid = item["assetid"].AsString(), - currencyid = item["currencyid"].AsString(), classid = item["classid"].AsString(), instanceid = item["instanceid"].AsString(), amount = item["amount"].AsString(), - missing = item["missing"].AsBoolean() }); } result.Add(tradeOffer); @@ -283,7 +215,7 @@ namespace ArchiSteamFarm { string request = "https://steamcommunity.com/gid/" + clanID; - Dictionary data = new Dictionary() { + Dictionary data = new Dictionary(2) { {"sessionID", sessionID}, {"action", "join"} }; @@ -301,36 +233,6 @@ namespace ArchiSteamFarm { return true; } - internal async Task LeaveClan(ulong clanID) { - if (clanID == 0) { - return false; - } - - string sessionID; - if (!Cookie.TryGetValue("sessionid", out sessionID)) { - return false; - } - - string request = GetHomeProcess(); - Dictionary data = new Dictionary() { - {"sessionID", sessionID}, - {"action", "leaveGroup"}, - {"groupId", clanID.ToString()} - }; - - HttpResponseMessage response = null; - for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { - response = await WebBrowser.UrlPost(request, data, Cookie).ConfigureAwait(false); - } - - if (response == null) { - Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); - return false; - } - - return true; - } - internal async Task AcceptTradeOffer(ulong tradeID) { if (tradeID == 0) { return false; @@ -344,7 +246,7 @@ namespace ArchiSteamFarm { string referer = "https://steamcommunity.com/tradeoffer/" + tradeID; string request = referer + "/accept"; - Dictionary data = new Dictionary() { + Dictionary data = new Dictionary(3) { {"sessionid", sessionID}, {"serverid", "1"}, {"tradeofferid", tradeID.ToString()} @@ -404,11 +306,19 @@ namespace ArchiSteamFarm { return null; } - List result = new List(); - IEnumerable jTokens = jObject.SelectTokens("$.rgInventory.*"); + if (jTokens == null) { + Logging.LogNullError("jTokens", Bot.BotName); + return null; + } + + List result = new List(); foreach (JToken jToken in jTokens) { - result.Add(JsonConvert.DeserializeObject(jToken.ToString())); + try { + result.Add(JsonConvert.DeserializeObject(jToken.ToString())); + } catch (Exception e) { + Logging.LogGenericException(e, Bot.BotName); + } } return result; @@ -424,7 +334,7 @@ namespace ArchiSteamFarm { return false; } - List trades = new List(); + List trades = new List(1 + inventory.Count / Trading.MaxItemsPerTrade); SteamTradeOfferRequest singleTrade = null; for (ushort i = 0; i < inventory.Count; i++) { @@ -450,7 +360,7 @@ namespace ArchiSteamFarm { string request = referer + "/send"; foreach (SteamTradeOfferRequest trade in trades) { - Dictionary data = new Dictionary() { + Dictionary data = new Dictionary(6) { {"sessionid", sessionID}, {"serverid", "1"}, {"partner", partnerID.ToString()}, @@ -508,5 +418,44 @@ namespace ArchiSteamFarm { return htmlDocument; } + + private async Task UnlockParentalAccount(string parentalPin) { + if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) { + return; + } + + Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName); + Dictionary data = new Dictionary(1) { + { "pin", parentalPin } + }; + + HttpResponseMessage response = null; + for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) { + response = await WebBrowser.UrlPost("https://steamcommunity.com/parental/ajaxunlock", data, Cookie, "https://steamcommunity.com/").ConfigureAwait(false); + } + + if (response == null) { + Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName); + return; + } + + IEnumerable setCookieValues; + if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) { + Logging.LogNullError("setCookieValues", Bot.BotName); + return; + } + + foreach (string setCookieValue in setCookieValues) { + if (setCookieValue.Contains("steamparental=")) { + string setCookie = setCookieValue.Substring(setCookieValue.IndexOf("steamparental=") + 14); + setCookie = setCookie.Substring(0, setCookie.IndexOf(';')); + Cookie["steamparental"] = setCookie; + Logging.LogGenericInfo("Success!", Bot.BotName); + return; + } + } + + Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName); + } } } diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index ecbecbbbd..eff209183 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -41,14 +41,19 @@ namespace ArchiSteamFarm { private const ulong ArchiSCFarmGroup = 103582791440160998; private const ushort CallbackSleep = 500; // In miliseconds - private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes - internal static readonly ConcurrentDictionary Bots = new ConcurrentDictionary(); internal static readonly HashSet GlobalBlacklist = new HashSet { 303700, 335590, 368020, 425280 }; + private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes + private readonly string ConfigFile, LoginKeyFile, MobileAuthenticatorFile, SentryFile; private readonly Timer SendItemsTimer; + internal readonly string BotName; + internal readonly ArchiHandler ArchiHandler; + internal readonly ArchiWebHandler ArchiWebHandler; + internal readonly SteamClient SteamClient; + private readonly CallbackManager CallbackManager; private readonly CardsFarmer CardsFarmer; private readonly SteamApps SteamApps; @@ -56,15 +61,6 @@ namespace ArchiSteamFarm { private readonly SteamUser SteamUser; private readonly Trading Trading; - internal readonly string BotName; - internal readonly ArchiHandler ArchiHandler; - internal readonly ArchiWebHandler ArchiWebHandler; - internal readonly SteamClient SteamClient; - - private bool InvalidPassword = false; - private bool LoggedInElsewhere = false; - private string AuthCode, LoginKey, TwoFactorAuth; - internal bool KeepRunning { get; private set; } = false; internal SteamGuardAccount SteamGuardAccount { get; private set; } @@ -88,19 +84,12 @@ namespace ArchiSteamFarm { internal bool SendOnFarmingFinished { get; private set; } = false; internal string SteamTradeToken { get; private set; } = "null"; internal byte SendTradePeriod { get; private set; } = 0; - internal HashSet Blacklist { get; private set; } = new HashSet(); + internal HashSet Blacklist { get; } = new HashSet(); internal bool Statistics { get; private set; } = true; - private static bool IsValidCdKey(string key) { - if (string.IsNullOrEmpty(key)) { - return false; - } - - // Steam keys are offered in many formats: https://support.steampowered.com/kb_article.php?ref=7480-WUSF-3601 - // It's pointless to implement them all, so we'll just do a simple check if key is supposed to be valid - // Every valid key, apart from Prey one has at least two dashes - return Utilities.GetCharCountInString(key, '-') >= 2; - } + private bool InvalidPassword = false; + private bool LoggedInElsewhere = false; + private string AuthCode, LoginKey, TwoFactorAuth; internal static string GetAnyBotName() { foreach (string botName in Bots.Keys) { @@ -130,6 +119,17 @@ namespace ArchiSteamFarm { } } + private static bool IsValidCdKey(string key) { + if (string.IsNullOrEmpty(key)) { + return false; + } + + // Steam keys are offered in many formats: https://support.steampowered.com/kb_article.php?ref=7480-WUSF-3601 + // It's pointless to implement them all, so we'll just do a simple check if key is supposed to be valid + // Every valid key, apart from Prey one has at least two dashes + return Utilities.GetCharCountInString(key, '-') >= 2; + } + internal Bot(string botName) { if (Bots.ContainsKey(botName)) { return; @@ -137,10 +137,11 @@ namespace ArchiSteamFarm { BotName = botName; - ConfigFile = Path.Combine(Program.ConfigDirectory, botName + ".xml"); - LoginKeyFile = Path.Combine(Program.ConfigDirectory, botName + ".key"); - MobileAuthenticatorFile = Path.Combine(Program.ConfigDirectory, botName + ".auth"); - SentryFile = Path.Combine(Program.ConfigDirectory, botName + ".bin"); + string botPath = Path.Combine(Program.ConfigDirectory, botName); + ConfigFile = botPath + ".xml"; + LoginKeyFile = botPath + ".key"; + MobileAuthenticatorFile = botPath + ".auth"; + SentryFile = botPath + ".bin"; if (!ReadConfig()) { return; @@ -234,181 +235,6 @@ namespace ArchiSteamFarm { } } - private bool LinkMobileAuthenticator() { - if (SteamGuardAccount != null) { - return false; - } - - Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName); - UserLogin userLogin = new UserLogin(SteamLogin, SteamPassword); - LoginResult loginResult; - while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) { - switch (loginResult) { - case LoginResult.NeedEmail: - userLogin.EmailCode = Program.GetUserInput(BotName, Program.EUserInputType.SteamGuard); - break; - default: - Logging.LogGenericError("Unhandled situation: " + loginResult, BotName); - return false; - } - } - - AuthenticatorLinker authenticatorLinker = new AuthenticatorLinker(userLogin.Session); - - AuthenticatorLinker.LinkResult linkResult; - while ((linkResult = authenticatorLinker.AddAuthenticator()) != AuthenticatorLinker.LinkResult.AwaitingFinalization) { - switch (linkResult) { - case AuthenticatorLinker.LinkResult.MustProvidePhoneNumber: - authenticatorLinker.PhoneNumber = Program.GetUserInput(BotName, Program.EUserInputType.PhoneNumber); - break; - default: - Logging.LogGenericError("Unhandled situation: " + linkResult, BotName); - return false; - } - } - - SteamGuardAccount = authenticatorLinker.LinkedAccount; - - try { - File.WriteAllText(MobileAuthenticatorFile, JsonConvert.SerializeObject(SteamGuardAccount)); - } catch (Exception e) { - Logging.LogGenericException(e, BotName); - return false; - } - - AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(Program.GetUserInput(BotName, Program.EUserInputType.SMS)); - if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) { - Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName); - DelinkMobileAuthenticator(); - return false; - } - - Logging.LogGenericInfo("Successfully linked ASF as new mobile authenticator for this account!", BotName); - Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, SteamGuardAccount.RevocationCode); - return true; - } - - private bool DelinkMobileAuthenticator() { - if (SteamGuardAccount == null) { - return false; - } - - bool result = SteamGuardAccount.DeactivateAuthenticator(); - SteamGuardAccount = null; - - try { - File.Delete(MobileAuthenticatorFile); - } catch (Exception e) { - Logging.LogGenericException(e, BotName); - } - - return result; - } - - private bool ReadConfig() { - if (!File.Exists(ConfigFile)) { - return false; - } - - try { - using (XmlReader reader = XmlReader.Create(ConfigFile)) { - while (reader.Read()) { - if (reader.NodeType != XmlNodeType.Element) { - continue; - } - - string key = reader.Name; - if (string.IsNullOrEmpty(key)) { - continue; - } - - string value = reader.GetAttribute("value"); - if (string.IsNullOrEmpty(value)) { - continue; - } - - switch (key) { - case "Enabled": - Enabled = bool.Parse(value); - break; - case "SteamLogin": - SteamLogin = value; - break; - case "SteamPassword": - SteamPassword = value; - break; - case "SteamNickname": - SteamNickname = value; - break; - case "SteamApiKey": - SteamApiKey = value; - break; - case "SteamTradeToken": - SteamTradeToken = value; - break; - case "SteamParentalPIN": - SteamParentalPIN = value; - break; - case "SteamMasterID": - SteamMasterID = ulong.Parse(value); - break; - case "SteamMasterClanID": - SteamMasterClanID = ulong.Parse(value); - break; - case "StartOnLaunch": - StartOnLaunch = bool.Parse(value); - break; - case "UseAsfAsMobileAuthenticator": - UseAsfAsMobileAuthenticator = bool.Parse(value); - break; - case "CardDropsRestricted": - CardDropsRestricted = bool.Parse(value); - break; - case "FarmOffline": - FarmOffline = bool.Parse(value); - break; - case "HandleOfflineMessages": - HandleOfflineMessages = bool.Parse(value); - break; - case "ForwardKeysToOtherBots": - ForwardKeysToOtherBots = bool.Parse(value); - break; - case "DistributeKeys": - DistributeKeys = bool.Parse(value); - break; - case "ShutdownOnFarmingFinished": - ShutdownOnFarmingFinished = bool.Parse(value); - break; - case "SendOnFarmingFinished": - SendOnFarmingFinished = bool.Parse(value); - break; - case "SendTradePeriod": - SendTradePeriod = byte.Parse(value); - break; - case "Blacklist": - Blacklist.Clear(); - foreach (string appID in value.Split(',')) { - Blacklist.Add(uint.Parse(appID)); - } - break; - case "Statistics": - Statistics = bool.Parse(value); - break; - default: - Logging.LogGenericWarning("Unrecognized config value: " + key + "=" + value, BotName); - break; - } - } - } - } catch (Exception e) { - Logging.LogGenericException(e, BotName); - Logging.LogGenericError("Your config for this bot instance is invalid, it won't run!", BotName); - return false; - } - - return true; - } - internal async Task Restart() { Stop(); await Utilities.SleepAsync(500).ConfigureAwait(false); @@ -460,26 +286,6 @@ namespace ArchiSteamFarm { } } - private void HandleCallbacks() { - TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); - while (KeepRunning) { - CallbackManager.RunWaitCallbacks(timeSpan); - } - } - - private void SendMessage(ulong steamID, string message) { - if (steamID == 0 || string.IsNullOrEmpty(message)) { - return; - } - - // TODO: I really need something better - if (steamID < 110300000000000000) { - SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, message); - } else { - SteamFriends.SendChatRoomMessage(steamID, EChatEntryType.ChatMsg, message); - } - } - internal string ResponseStatus() { if (CardsFarmer.CurrentGamesFarming.Count > 0) { return "Bot " + BotName + " is currently farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm."; @@ -966,6 +772,13 @@ namespace ArchiSteamFarm { } } + private void HandleCallbacks() { + TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); + while (KeepRunning) { + CallbackManager.RunWaitCallbacks(timeSpan); + } + } + private async Task HandleMessage(ulong steamID, string message) { if (steamID == 0 || string.IsNullOrEmpty(message)) { return; @@ -974,6 +787,90 @@ namespace ArchiSteamFarm { SendMessage(steamID, await HandleMessage(message).ConfigureAwait(false)); } + private void SendMessage(ulong steamID, string message) { + if (steamID == 0 || string.IsNullOrEmpty(message)) { + return; + } + + // TODO: I really need something better + if (steamID < 110300000000000000) { + SteamFriends.SendChatMessage(steamID, EChatEntryType.ChatMsg, message); + } else { + SteamFriends.SendChatRoomMessage(steamID, EChatEntryType.ChatMsg, message); + } + } + + private bool LinkMobileAuthenticator() { + if (SteamGuardAccount != null) { + return false; + } + + Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName); + UserLogin userLogin = new UserLogin(SteamLogin, SteamPassword); + LoginResult loginResult; + while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) { + switch (loginResult) { + case LoginResult.NeedEmail: + userLogin.EmailCode = Program.GetUserInput(BotName, Program.EUserInputType.SteamGuard); + break; + default: + Logging.LogGenericError("Unhandled situation: " + loginResult, BotName); + return false; + } + } + + AuthenticatorLinker authenticatorLinker = new AuthenticatorLinker(userLogin.Session); + + AuthenticatorLinker.LinkResult linkResult; + while ((linkResult = authenticatorLinker.AddAuthenticator()) != AuthenticatorLinker.LinkResult.AwaitingFinalization) { + switch (linkResult) { + case AuthenticatorLinker.LinkResult.MustProvidePhoneNumber: + authenticatorLinker.PhoneNumber = Program.GetUserInput(BotName, Program.EUserInputType.PhoneNumber); + break; + default: + Logging.LogGenericError("Unhandled situation: " + linkResult, BotName); + return false; + } + } + + SteamGuardAccount = authenticatorLinker.LinkedAccount; + + try { + File.WriteAllText(MobileAuthenticatorFile, JsonConvert.SerializeObject(SteamGuardAccount)); + } catch (Exception e) { + Logging.LogGenericException(e, BotName); + return false; + } + + AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(Program.GetUserInput(BotName, Program.EUserInputType.SMS)); + if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) { + Logging.LogGenericError("Unhandled situation: " + finalizeResult, BotName); + DelinkMobileAuthenticator(); + return false; + } + + Logging.LogGenericInfo("Successfully linked ASF as new mobile authenticator for this account!", BotName); + Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, SteamGuardAccount.RevocationCode); + return true; + } + + private bool DelinkMobileAuthenticator() { + if (SteamGuardAccount == null) { + return false; + } + + bool result = SteamGuardAccount.DeactivateAuthenticator(); + SteamGuardAccount = null; + + try { + File.Delete(MobileAuthenticatorFile); + } catch (Exception e) { + Logging.LogGenericException(e, BotName); + } + + return result; + } + private void OnConnected(SteamClient.ConnectedCallback callback) { if (callback == null) { return; @@ -1252,7 +1149,7 @@ namespace ArchiSteamFarm { SteamParentalPIN = Program.GetUserInput(BotName, Program.EUserInputType.SteamParentalPIN); } - if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, callback.VanityURL, SteamParentalPIN).ConfigureAwait(false)) { + if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, SteamParentalPIN).ConfigureAwait(false)) { await Restart().ConfigureAwait(false); return; } @@ -1376,5 +1273,109 @@ namespace ArchiSteamFarm { await CardsFarmer.RestartFarming().ConfigureAwait(false); } } + + private bool ReadConfig() { + if (!File.Exists(ConfigFile)) { + return false; + } + + try { + using (XmlReader reader = XmlReader.Create(ConfigFile)) { + while (reader.Read()) { + if (reader.NodeType != XmlNodeType.Element) { + continue; + } + + string key = reader.Name; + if (string.IsNullOrEmpty(key)) { + continue; + } + + string value = reader.GetAttribute("value"); + if (string.IsNullOrEmpty(value)) { + continue; + } + + switch (key) { + case "Enabled": + Enabled = bool.Parse(value); + break; + case "SteamLogin": + SteamLogin = value; + break; + case "SteamPassword": + SteamPassword = value; + break; + case "SteamNickname": + SteamNickname = value; + break; + case "SteamApiKey": + SteamApiKey = value; + break; + case "SteamTradeToken": + SteamTradeToken = value; + break; + case "SteamParentalPIN": + SteamParentalPIN = value; + break; + case "SteamMasterID": + SteamMasterID = ulong.Parse(value); + break; + case "SteamMasterClanID": + SteamMasterClanID = ulong.Parse(value); + break; + case "StartOnLaunch": + StartOnLaunch = bool.Parse(value); + break; + case "UseAsfAsMobileAuthenticator": + UseAsfAsMobileAuthenticator = bool.Parse(value); + break; + case "CardDropsRestricted": + CardDropsRestricted = bool.Parse(value); + break; + case "FarmOffline": + FarmOffline = bool.Parse(value); + break; + case "HandleOfflineMessages": + HandleOfflineMessages = bool.Parse(value); + break; + case "ForwardKeysToOtherBots": + ForwardKeysToOtherBots = bool.Parse(value); + break; + case "DistributeKeys": + DistributeKeys = bool.Parse(value); + break; + case "ShutdownOnFarmingFinished": + ShutdownOnFarmingFinished = bool.Parse(value); + break; + case "SendOnFarmingFinished": + SendOnFarmingFinished = bool.Parse(value); + break; + case "SendTradePeriod": + SendTradePeriod = byte.Parse(value); + break; + case "Blacklist": + Blacklist.Clear(); + foreach (string appID in value.Split(',')) { + Blacklist.Add(uint.Parse(appID)); + } + break; + case "Statistics": + Statistics = bool.Parse(value); + break; + default: + Logging.LogGenericWarning("Unrecognized config value: " + key + "=" + value, BotName); + break; + } + } + } + } catch (Exception e) { + Logging.LogGenericException(e, BotName); + Logging.LogGenericError("Your config for this bot instance is invalid, it won't run!", BotName); + return false; + } + + return true; + } } } diff --git a/ArchiSteamFarm/CMsgClientClanInviteAction.cs b/ArchiSteamFarm/CMsgClientClanInviteAction.cs index afe853f0c..7121e62e9 100644 --- a/ArchiSteamFarm/CMsgClientClanInviteAction.cs +++ b/ArchiSteamFarm/CMsgClientClanInviteAction.cs @@ -24,12 +24,13 @@ using SteamKit2; using SteamKit2.Internal; +using System; using System.IO; namespace ArchiSteamFarm { internal sealed class CMsgClientClanInviteAction : ISteamSerializableMessage, ISteamSerializable { - internal ulong GroupID = 0; - internal bool AcceptInvite = true; + internal ulong GroupID { get; set; } = 0; + internal bool AcceptInvite { get; set; } = true; EMsg ISteamSerializableMessage.GetEMsg() { return EMsg.ClientAcknowledgeClanInvite; @@ -44,8 +45,8 @@ namespace ArchiSteamFarm { BinaryWriter binaryWriter = new BinaryWriter(stream); binaryWriter.Write(GroupID); binaryWriter.Write(AcceptInvite); - } catch { - throw new IOException(); + } catch (Exception e) { + Logging.LogGenericException(e); } } @@ -58,8 +59,8 @@ namespace ArchiSteamFarm { BinaryReader binaryReader = new BinaryReader(stream); GroupID = binaryReader.ReadUInt64(); AcceptInvite = binaryReader.ReadBoolean(); - } catch { - throw new IOException(); + } catch (Exception e) { + Logging.LogGenericException(e); } } } diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 8095227a4..f6195374f 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -36,15 +36,15 @@ namespace ArchiSteamFarm { private const byte StatusCheckSleep = 5; // In minutes, how long to wait before checking the appID again private const ushort MaxFarmingTime = 600; // In minutes, how long ASF is allowed to farm one game in solo mode + internal readonly ConcurrentDictionary GamesToFarm = new ConcurrentDictionary(); + internal readonly List CurrentGamesFarming = new List(); + private readonly ManualResetEvent FarmResetEvent = new ManualResetEvent(false); private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); private readonly Bot Bot; private readonly Timer Timer; - internal readonly ConcurrentDictionary GamesToFarm = new ConcurrentDictionary(); - internal readonly List CurrentGamesFarming = new List(); - private bool ManualMode = false; private bool NowFarming = false; @@ -118,6 +118,7 @@ namespace ArchiSteamFarm { } CurrentGamesFarming.Clear(); + CurrentGamesFarming.TrimExcess(); foreach (uint appID in appIDs.Keys) { CurrentGamesFarming.Add(appID); } @@ -138,6 +139,7 @@ namespace ArchiSteamFarm { } CurrentGamesFarming.Clear(); + CurrentGamesFarming.TrimExcess(); CurrentGamesFarming.Add(appID); Logging.LogGenericInfo("Now farming: " + appID, Bot.BotName); @@ -184,6 +186,7 @@ namespace ArchiSteamFarm { if (await FarmSolo(appID).ConfigureAwait(false)) { Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName); gamesToFarmSolo.Remove(appID); + gamesToFarmSolo.TrimExcess(); } else { NowFarming = false; return; @@ -212,6 +215,7 @@ namespace ArchiSteamFarm { } CurrentGamesFarming.Clear(); + CurrentGamesFarming.TrimExcess(); NowFarming = false; Logging.LogGenericInfo("Farming finished!", Bot.BotName); await Bot.OnFarmingFinished().ConfigureAwait(false); @@ -269,16 +273,16 @@ namespace ArchiSteamFarm { // Find APPIDs we need to farm Logging.LogGenericInfo("Checking other pages...", Bot.BotName); - List checkPagesTasks = new List(); + List tasks = new List(maxPages - 1); for (byte page = 1; page <= maxPages; page++) { if (page == 1) { CheckPage(htmlDocument); // Because we fetched page number 1 already } else { byte currentPage = page; // We need a copy of variable being passed when in for loops - checkPagesTasks.Add(Task.Run(async () => await CheckPage(currentPage).ConfigureAwait(false))); + tasks.Add(Task.Run(async () => await CheckPage(currentPage).ConfigureAwait(false))); } } - await Task.WhenAll(checkPagesTasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); if (GamesToFarm.Count == 0) { return true; @@ -286,12 +290,12 @@ namespace ArchiSteamFarm { // If we have restricted card drops, actually do check hours of all games that are left to farm if (Bot.CardDropsRestricted) { - List checkHoursTasks = new List(); + tasks = new List(GamesToFarm.Keys.Count); Logging.LogGenericInfo("Checking hours...", Bot.BotName); foreach (uint appID in GamesToFarm.Keys) { - checkHoursTasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false))); + tasks.Add(Task.Run(async () => await CheckHours(appID).ConfigureAwait(false))); } - await Task.WhenAll(checkHoursTasks).ConfigureAwait(false); + await Task.WhenAll(tasks).ConfigureAwait(false); } return true; diff --git a/ArchiSteamFarm/Logging.cs b/ArchiSteamFarm/Logging.cs index 946da7ec9..d69eeece7 100644 --- a/ArchiSteamFarm/Logging.cs +++ b/ArchiSteamFarm/Logging.cs @@ -34,7 +34,79 @@ namespace ArchiSteamFarm { internal static bool LogToFile { get; set; } = false; internal static void Init() { - File.Delete(Program.LogFile); + lock (FileLock) { + try { + File.Delete(Program.LogFile); + } catch (Exception e) { + bool logToFile = LogToFile; + LogToFile = false; + LogGenericException(e); + LogToFile = logToFile; + } + } + } + + internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { + if (string.IsNullOrEmpty(message)) { + return; + } + + Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?"); + } + + internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { + if (string.IsNullOrEmpty(message)) { + return; + } + + Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message); + } + + internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = "") { + if (exception == null) { + return; + } + + Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message); + Log("[!] StackTrace: " + exception.StackTrace); + + Exception innerException = exception.InnerException; + if (innerException != null) { + LogGenericException(innerException, botName, previousMethodName); + } + } + + internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { + if (string.IsNullOrEmpty(message)) { + return; + } + + Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message); + } + + internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { + if (string.IsNullOrEmpty(message)) { + return; + } + + Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message); + } + + internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = "") { + if (string.IsNullOrEmpty(nullObjectName)) { + return; + } + + LogGenericError(nullObjectName + " is null!", botName, previousMethodName); + } + + [Conditional("DEBUG")] + internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { + if (string.IsNullOrEmpty(message)) { + return; + } + + Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message); } private static void Log(string message) { @@ -51,44 +123,16 @@ namespace ArchiSteamFarm { if (LogToFile) { lock (FileLock) { - File.AppendAllText(Program.LogFile, loggedMessage); + try { + File.AppendAllText(Program.LogFile, loggedMessage); + } catch (Exception e) { + bool logToFile = LogToFile; + LogToFile = false; + LogGenericException(e); + LogToFile = logToFile; + } } } } - - internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { - Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?"); - } - - internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { - Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message); - } - - internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = "") { - Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message); - Log("[!] StackTrace: " + exception.StackTrace); - - Exception innerException = exception.InnerException; - if (innerException != null) { - LogGenericException(innerException, botName, previousMethodName); - } - } - - internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { - Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message); - } - - internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { - Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message); - } - - internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = "") { - LogGenericError(nullObjectName + " is null!", botName, previousMethodName); - } - - [Conditional("DEBUG")] - internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = "") { - Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message); - } } } diff --git a/ArchiSteamFarm/SteamItem.cs b/ArchiSteamFarm/SteamItem.cs index 2db069c85..50703e70b 100644 --- a/ArchiSteamFarm/SteamItem.cs +++ b/ArchiSteamFarm/SteamItem.cs @@ -28,13 +28,13 @@ namespace ArchiSteamFarm { internal sealed class SteamItem { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal string appid { get; set; } - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal string contextid { get; set; } - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal string assetid { get; set; } [JsonProperty] @@ -43,22 +43,13 @@ namespace ArchiSteamFarm { set { assetid = value; } } - [JsonProperty] - internal string currencyid { get; set; } - - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal string classid { get; set; } - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal string instanceid { get; set; } - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal string amount { get; set; } - - [JsonProperty] - internal bool missing { get; set; } - - [JsonProperty] - internal int pos { get; set; } } } diff --git a/ArchiSteamFarm/SteamItemList.cs b/ArchiSteamFarm/SteamItemList.cs index bbbc98475..44ce15c03 100644 --- a/ArchiSteamFarm/SteamItemList.cs +++ b/ArchiSteamFarm/SteamItemList.cs @@ -26,14 +26,8 @@ using Newtonsoft.Json; using System.Collections.Generic; namespace ArchiSteamFarm { - internal class SteamItemList { - [JsonProperty] - internal List assets { get; set; } = new List(); - - [JsonProperty] - internal List currency { get; set; } = new List(); - - [JsonProperty] - internal bool ready { get; set; } = false; + internal sealed class SteamItemList { + [JsonProperty(Required = Required.Always)] + internal List assets { get; } = new List(); } } diff --git a/ArchiSteamFarm/SteamTradeOffer.cs b/ArchiSteamFarm/SteamTradeOffer.cs index d81ec86be..9e29ca64d 100644 --- a/ArchiSteamFarm/SteamTradeOffer.cs +++ b/ArchiSteamFarm/SteamTradeOffer.cs @@ -29,7 +29,7 @@ using System.Collections.Generic; namespace ArchiSteamFarm { internal sealed class SteamTradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer - internal enum ETradeOfferState { + internal enum ETradeOfferState : byte { Unknown, Invalid, Active, @@ -44,50 +44,20 @@ namespace ArchiSteamFarm { OnHold } - internal enum ETradeOfferConfirmationMethod { - Invalid, - Email, - MobileApp - } - - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal string tradeofferid { get; set; } - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal int accountid_other { get; set; } - [JsonProperty] - internal string message { get; set; } - - [JsonProperty] - internal int expiration_time { get; set; } - - [JsonProperty] + [JsonProperty(Required = Required.Always)] internal ETradeOfferState trade_offer_state { get; set; } - [JsonProperty] - internal List items_to_give { get; set; } = new List(); + [JsonProperty(Required = Required.Always)] + internal List items_to_give { get; } = new List(); - [JsonProperty] - internal List items_to_receive { get; set; } = new List(); - - [JsonProperty] - internal bool is_our_offer { get; set; } - - [JsonProperty] - internal int time_created { get; set; } - - [JsonProperty] - internal int time_updated { get; set; } - - [JsonProperty] - internal bool from_real_time_trade { get; set; } - - [JsonProperty] - internal int escrow_end_date { get; set; } - - [JsonProperty] - internal ETradeOfferConfirmationMethod confirmation_method { get; set; } + [JsonProperty(Required = Required.Always)] + internal List items_to_receive { get; } = new List(); // Extra private ulong _OtherSteamID64 = 0; diff --git a/ArchiSteamFarm/SteamTradeOfferRequest.cs b/ArchiSteamFarm/SteamTradeOfferRequest.cs index 22f9c8595..15188ebc2 100644 --- a/ArchiSteamFarm/SteamTradeOfferRequest.cs +++ b/ArchiSteamFarm/SteamTradeOfferRequest.cs @@ -25,17 +25,17 @@ using Newtonsoft.Json; namespace ArchiSteamFarm { - internal class SteamTradeOfferRequest { - [JsonProperty] - internal bool newversion { get; set; } = true; + internal sealed class SteamTradeOfferRequest { + [JsonProperty(Required = Required.Always)] + internal bool newversion { get; } = true; - [JsonProperty] - internal int version { get; set; } = 2; + [JsonProperty(Required = Required.Always)] + internal int version { get; } = 2; - [JsonProperty] - internal SteamItemList me { get; set; } = new SteamItemList(); + [JsonProperty(Required = Required.Always)] + internal SteamItemList me { get; } = new SteamItemList(); - [JsonProperty] - internal SteamItemList them { get; set; } = new SteamItemList(); + [JsonProperty(Required = Required.Always)] + internal SteamItemList them { get; } = new SteamItemList(); } } diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index 64b9710ad..d797370e0 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -72,13 +72,5 @@ namespace ArchiSteamFarm { return count; } - - internal static string UrlDecode(string message) { - if (string.IsNullOrEmpty(message)) { - return null; - } - - return WebUtility.UrlDecode(message); - } } } diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index 57ff40ec5..7e4892952 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -30,27 +30,20 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using System.Xml; namespace ArchiSteamFarm { internal static class WebBrowser { - [Flags] - internal enum RequestOptions : byte { - None = 0, - FakeUserAgent = 1 << 0, - XMLHttpRequest = 1 << 1 - } - - private const string FakeUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36"; - internal const byte HttpTimeout = 180; // In seconds, how long we can wait for server's response - internal const byte MaxConnections = 20; // Defines maximum number of connections. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state + internal const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state internal const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it internal const byte MaxRetries = 5; // Defines maximum number of retries, UrlRequest() does not handle retry by itself (it's app responsibility) private static readonly string DefaultUserAgent = "ArchiSteamFarm/" + Program.Version; - private static readonly HttpClientHandler HttpClientHandler = new HttpClientHandler { UseCookies = false }; - private static readonly HttpClient HttpClient = new HttpClient(HttpClientHandler) { Timeout = TimeSpan.FromSeconds(HttpTimeout) }; + private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler { + UseCookies = false + }) { + Timeout = TimeSpan.FromSeconds(HttpTimeout) + }; internal static void Init() { // Most web services expect that UserAgent is set, so we declare it globally @@ -65,9 +58,86 @@ namespace ArchiSteamFarm { // Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets ServicePointManager.Expect100Continue = false; + + // Reuse ports if possible + // TODO: Mono doesn't support that feature yet + //ServicePointManager.ReusePort = true; } - private static async Task UrlRequest(string request, HttpMethod httpMethod, Dictionary data = null, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { + internal static async Task UrlGet(string request, Dictionary cookies = null, string referer = null) { + if (string.IsNullOrEmpty(request)) { + return null; + } + + return await UrlRequest(request, HttpMethod.Get, null, cookies, referer).ConfigureAwait(false); + } + + internal static async Task UrlPost(string request, Dictionary data = null, Dictionary cookies = null, string referer = null) { + if (string.IsNullOrEmpty(request)) { + return null; + } + + return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false); + } + + internal static async Task UrlGetToContent(string request, Dictionary cookies, string referer = null) { + if (string.IsNullOrEmpty(request)) { + return null; + } + + HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false); + if (httpResponse == null) { + return null; + } + + HttpContent httpContent = httpResponse.Content; + if (httpContent == null) { + return null; + } + + return await httpContent.ReadAsStringAsync().ConfigureAwait(false); + } + + internal static async Task UrlGetToHtmlDocument(string request, Dictionary cookies = null, string referer = null) { + if (string.IsNullOrEmpty(request)) { + return null; + } + + string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false); + if (string.IsNullOrEmpty(content)) { + return null; + } + + content = WebUtility.HtmlDecode(content); + HtmlDocument htmlDocument = new HtmlDocument(); + htmlDocument.LoadHtml(content); + + return htmlDocument; + } + + internal static async Task UrlGetToJObject(string request, Dictionary cookies = null, string referer = null) { + if (string.IsNullOrEmpty(request)) { + return null; + } + + string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false); + if (string.IsNullOrEmpty(content)) { + return null; + } + + JObject jObject; + + try { + jObject = JObject.Parse(content); + } catch (Exception e) { + Logging.LogGenericException(e); + return null; + } + + return jObject; + } + + private static async Task UrlRequest(string request, HttpMethod httpMethod, Dictionary data = null, Dictionary cookies = null, string referer = null) { if (string.IsNullOrEmpty(request) || httpMethod == null) { return null; } @@ -95,14 +165,6 @@ namespace ArchiSteamFarm { requestMessage.Headers.Referrer = new Uri(referer); } - if (requestOptions.HasFlag(RequestOptions.FakeUserAgent)) { - requestMessage.Headers.UserAgent.ParseAdd(FakeUserAgent); - } - - if (requestOptions.HasFlag(RequestOptions.XMLHttpRequest)) { - requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); - } - try { responseMessage = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false); } catch { // Request failed, we don't need to know the exact reason, swallow exception @@ -116,211 +178,5 @@ namespace ArchiSteamFarm { return responseMessage; } - - internal static async Task UrlGet(string request, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - return await UrlRequest(request, HttpMethod.Get, null, cookies, referer, requestOptions).ConfigureAwait(false); - } - - internal static async Task UrlPost(string request, Dictionary data = null, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - return await UrlRequest(request, HttpMethod.Post, data, cookies, referer, requestOptions).ConfigureAwait(false); - } - - internal static async Task HttpResponseToHtmlDocument(HttpResponseMessage httpResponse) { - if (httpResponse == null) { - return null; - } - - HttpContent httpContent = httpResponse.Content; - if (httpContent == null) { - return null; - } - - string content = await httpContent.ReadAsStringAsync().ConfigureAwait(false); - if (string.IsNullOrEmpty(content)) { - return null; - } - - content = WebUtility.HtmlDecode(content); - HtmlDocument htmlDocument = new HtmlDocument(); - htmlDocument.LoadHtml(content); - - return htmlDocument; - } - - internal static async Task UrlGetToContent(string request, Dictionary cookies, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer, requestOptions).ConfigureAwait(false); - if (httpResponse == null) { - return null; - } - - HttpContent httpContent = httpResponse.Content; - if (httpContent == null) { - return null; - } - - return await httpContent.ReadAsStringAsync().ConfigureAwait(false); - } - - internal static async Task UrlPostToContent(string request, Dictionary data = null, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - HttpResponseMessage httpResponse = await UrlPost(request, data, cookies, referer, requestOptions).ConfigureAwait(false); - if (httpResponse == null) { - return null; - } - - HttpContent httpContent = httpResponse.Content; - if (httpContent == null) { - return null; - } - - return await httpContent.ReadAsStringAsync().ConfigureAwait(false); - } - - internal static async Task UrlPostToJObject(string request, Dictionary data = null, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - string content = await UrlPostToContent(request, data, cookies, referer, requestOptions).ConfigureAwait(false); - if (string.IsNullOrEmpty(content)) { - return null; - } - - JObject jObject; - - try { - jObject = JObject.Parse(content); - } catch (Exception e) { - Logging.LogGenericException(e); - return null; - } - - return jObject; - } - - internal static async Task UrlGetToHtmlDocument(string request, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer, requestOptions).ConfigureAwait(false); - if (httpResponse == null) { - return null; - } - - return await HttpResponseToHtmlDocument(httpResponse).ConfigureAwait(false); - } - - internal static async Task UrlPostToHtmlDocument(string request, Dictionary data = null, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - HttpResponseMessage httpResponse = await UrlPost(request, data, cookies, referer, requestOptions).ConfigureAwait(false); - if (httpResponse == null) { - return null; - } - - return await HttpResponseToHtmlDocument(httpResponse).ConfigureAwait(false); - } - - internal static async Task UrlGetToTitle(string request, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - HtmlDocument htmlDocument = await UrlGetToHtmlDocument(request, cookies, referer, requestOptions).ConfigureAwait(false); - if (htmlDocument == null) { - return null; - } - - HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//head/title"); - if (htmlNode == null) { - return null; - } - - return htmlNode.InnerText; - } - - internal static async Task UrlGetToJArray(string request, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - string content = await UrlGetToContent(request, cookies, referer, requestOptions).ConfigureAwait(false); - if (string.IsNullOrEmpty(content)) { - return null; - } - - JArray jArray; - - try { - jArray = JArray.Parse(content); - } catch (Exception e) { - Logging.LogGenericException(e); - return null; - } - - return jArray; - } - - internal static async Task UrlGetToJObject(string request, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - string content = await UrlGetToContent(request, cookies, referer, requestOptions).ConfigureAwait(false); - if (string.IsNullOrEmpty(content)) { - return null; - } - - JObject jObject; - - try { - jObject = JObject.Parse(content); - } catch (Exception e) { - Logging.LogGenericException(e); - return null; - } - - return jObject; - } - - internal static async Task UrlGetToXML(string request, Dictionary cookies = null, string referer = null, RequestOptions requestOptions = RequestOptions.None) { - if (string.IsNullOrEmpty(request)) { - return null; - } - - string content = await UrlGetToContent(request, cookies, referer, requestOptions).ConfigureAwait(false); - if (string.IsNullOrEmpty(content)) { - return null; - } - - XmlDocument xmlDocument = new XmlDocument(); - - try { - xmlDocument.LoadXml(content); - } catch (XmlException e) { - Logging.LogGenericException(e); - return null; - } - - return xmlDocument; - } } }