diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index ed1448d98..228e5d031 100644 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -35,40 +35,56 @@ namespace ArchiSteamFarm { internal class Bot { private const ushort CallbackSleep = 500; // In miliseconds - private readonly Dictionary Config = new Dictionary(); + private static readonly HashSet Bots = new HashSet(); - internal readonly string BotName; private readonly string ConfigFile; private readonly string SentryFile; - private readonly CardsFarmer CardsFarmer; - - internal ulong BotID { get; private set; } + private bool IsRunning = false; private string AuthCode, TwoFactorAuth; + internal readonly string BotName; + internal ArchiHandler ArchiHandler { get; private set; } internal ArchiWebHandler ArchiWebHandler { get; private set; } internal CallbackManager CallbackManager { get; private set; } + internal CardsFarmer CardsFarmer { get; private set; } internal SteamClient SteamClient { get; private set; } internal SteamFriends SteamFriends { get; private set; } internal SteamUser SteamUser { get; private set; } internal Trading Trading { get; private set; } // Config variables - internal bool Enabled { get { return bool.Parse(Config["Enabled"]); } } - private string SteamLogin { get { return Config["SteamLogin"]; } } - private string SteamPassword { get { return Config["SteamPassword"]; } } - private string SteamNickname { get { return Config["SteamNickname"]; } } - private string SteamApiKey { get { return Config["SteamApiKey"]; } } - private string SteamParentalPIN { get { return Config["SteamParentalPIN"]; } } - internal ulong SteamMasterID { get { return ulong.Parse(Config["SteamMasterID"]); } } - private ulong SteamMasterClanID { get { return ulong.Parse(Config["SteamMasterClanID"]); } } - internal HashSet Blacklist { get; } = new HashSet(); - internal bool Statistics { get { return bool.Parse(Config["Statistics"]); } } + internal bool Enabled { get; private set; } = true; + internal string SteamLogin { get; private set; } = "null"; + internal string SteamPassword { get; private set; } = "null"; + internal string SteamNickname { get; private set; } = "null"; + internal string SteamApiKey { get; private set; } = "null"; + internal string SteamParentalPIN { get; private set; } = "0"; + internal ulong SteamMasterID { get; private set; } = 76561198006963719; + internal ulong SteamMasterClanID { get; private set; } = 0; + internal bool ShutdownOnFarmingFinished { get; private set; } = true; + internal HashSet Blacklist { get; private set; } = new HashSet { 368020 }; + internal bool Statistics { get; private set; } = true; + + internal static int GetRunningBotsCount() { + int result; + lock (Bots) { + result = Bots.Count; + } + return result; + } + + internal static void ShutdownAllBots() { + lock (Bots) { + foreach (Bot bot in Bots) { + bot.Shutdown(); + } + } + } internal Bot(string botName) { BotName = botName; - CardsFarmer = new CardsFarmer(this); ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml"); SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin"); @@ -79,44 +95,11 @@ namespace ArchiSteamFarm { return; } - Start(); - } - - private void ReadConfig() { - 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; - } - - Config.Add(key, value); - - switch (key) { - case "Blacklist": - foreach (string appID in value.Split(',')) { - Blacklist.Add(uint.Parse(appID)); - } - break; - } - } - } - } - - internal void Start() { - if (SteamClient != null) { - return; + lock (Bots) { + Bots.Add(this); } + // Initialize SteamClient = new SteamClient(); ArchiHandler = new ArchiHandler(); @@ -141,20 +124,106 @@ namespace ArchiSteamFarm { CallbackManager.Subscribe(OnPurchaseResponse); ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey); + CardsFarmer = new CardsFarmer(this); Trading = new Trading(this); + // Start + Start(); + } + + private void ReadConfig() { + 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 "SteamParentalPIN": + SteamParentalPIN = value; + break; + case "SteamMasterID": + SteamMasterID = ulong.Parse(value); + break; + case "SteamMasterClanID": + SteamMasterClanID = ulong.Parse(value); + break; + case "ShutdownOnFarmingFinished": + ShutdownOnFarmingFinished = bool.Parse(value); + break; + case "Blacklist": + foreach (string appID in value.Split(',')) { + Blacklist.Add(uint.Parse(appID)); + } + break; + case "Statistics": + Statistics = bool.Parse(value); + break; + default: + Logging.LogGenericWarning(BotName, "Unrecognized config value: " + key + "=" + value); + break; + } + } + } + } + + internal void Start() { + if (IsRunning) { + return; + } + SteamClient.Connect(); - Task.Run(() => HandleCallbacks()); + IsRunning = true; + + Task.Run(() => HandleCallbacks()); } internal void Stop() { - if (SteamClient == null) { + if (!IsRunning) { return; } SteamClient.Disconnect(); - SteamClient = null; - CallbackManager = null; + IsRunning = false; + } + + internal void Shutdown() { + Stop(); + lock (Bots) { + Bots.Remove(this); + } + Program.OnBotShutdown(this); + } + + internal void OnFarmingFinished() { + if (ShutdownOnFarmingFinished) { + Shutdown(); + } } internal void PlayGame(params ulong[] gameIDs) { @@ -163,7 +232,7 @@ namespace ArchiSteamFarm { private void HandleCallbacks() { TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); - while (CallbackManager != null) { + while (IsRunning) { CallbackManager.RunWaitCallbacks(timeSpan); } } @@ -186,21 +255,17 @@ namespace ArchiSteamFarm { sentryHash = CryptoHelper.SHAHash(sentryFileContent); } - string steamLogin = SteamLogin; - if (string.IsNullOrEmpty(steamLogin) || steamLogin.Equals("null")) { - steamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login); - Config["SteamLogin"] = steamLogin; + if (SteamLogin.Equals("null")) { + SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login); } - string steamPassword = SteamPassword; - if (string.IsNullOrEmpty(steamPassword) || steamPassword.Equals("null")) { - steamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password); - Config["SteamPassword"] = steamPassword; + if (SteamPassword.Equals("null")) { + SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password); } SteamUser.LogOn(new SteamUser.LogOnDetails { - Username = steamLogin, - Password = steamPassword, + Username = SteamLogin, + Password = SteamPassword, AuthCode = AuthCode, TwoFactorCode = TwoFactorAuth, SentryFileHash = sentryHash @@ -304,10 +369,6 @@ namespace ArchiSteamFarm { return; } - if (callback.ClientSteamID != 0) { - BotID = callback.ClientSteamID; - } - EResult result = callback.Result; switch (result) { case EResult.AccountLogonDenied: @@ -319,18 +380,15 @@ namespace ArchiSteamFarm { case EResult.OK: Logging.LogGenericInfo(BotName, "Successfully logged on!"); - string steamNickname = SteamNickname; - if (!string.IsNullOrEmpty(steamNickname) && !steamNickname.Equals("null")) { - SteamFriends.SetPersonaName(steamNickname); + if (!SteamNickname.Equals("null")) { + SteamFriends.SetPersonaName(SteamNickname); } - string steamParentalPIN = SteamParentalPIN; - if (string.IsNullOrEmpty(steamParentalPIN) || steamParentalPIN.Equals("null")) { - steamParentalPIN = Program.GetUserInput(BotName, Program.EUserInputType.SteamParentalPIN); - Config["SteamParentalPIN"] = steamParentalPIN; + if (SteamParentalPIN.Equals("null")) { + SteamParentalPIN = Program.GetUserInput(BotName, Program.EUserInputType.SteamParentalPIN); } - await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, callback.VanityURL, steamParentalPIN).ConfigureAwait(false); + await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, callback.VanityURL, SteamParentalPIN).ConfigureAwait(false); ulong clanID = SteamMasterClanID; if (clanID != 0) { @@ -353,7 +411,7 @@ namespace ArchiSteamFarm { break; default: Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult); - Stop(); + Shutdown(); break; } } diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 81bcee77c..a187a74d3 100644 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -99,11 +99,12 @@ namespace ArchiSteamFarm { if (await Farm(appID).ConfigureAwait(false)) { appIDs.Remove(appID); } else { - break; + return; } } Logging.LogGenericInfo(Bot.BotName, "Farming finished!"); + Bot.OnFarmingFinished(); } private async Task ShouldFarm(ulong appID) { diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index c248b1d87..caa045b70 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -24,7 +24,6 @@ using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; using System.IO; using System.Reflection; using System.Threading; @@ -44,7 +43,7 @@ namespace ArchiSteamFarm { internal const string ConfigDirectoryPath = "config"; private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest"; - private static readonly HashSet Bots = new HashSet(); + private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false); internal static readonly object ConsoleLock = new object(); internal static string Version { get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); } } @@ -71,7 +70,7 @@ namespace ArchiSteamFarm { } internal static void Exit(int exitCode = 0) { - ShutdownAllBots(); + Bot.ShutdownAllBots(); Environment.Exit(exitCode); } @@ -101,12 +100,11 @@ namespace ArchiSteamFarm { return result; } - private static void ShutdownAllBots() { - lock (Bots) { - foreach (Bot bot in Bots) { - bot.Stop(); - } - Bots.Clear(); + internal static void OnBotShutdown(Bot bot) { + if (Bot.GetRunningBotsCount() == 0) { + Logging.LogGenericInfo("Main", "No bots are running, exiting"); + Thread.Sleep(5000); // This might be the only message user gets, consider giving him some time + ShutdownResetEvent.Set(); } } @@ -126,18 +124,18 @@ namespace ArchiSteamFarm { Exit(1); } - lock (Bots) { - foreach (var configFile in Directory.EnumerateFiles(ConfigDirectoryPath, "*.xml")) { - string botName = Path.GetFileNameWithoutExtension(configFile); - Bot bot = new Bot(botName); - Bots.Add(bot); - if (!bot.Enabled) { - Logging.LogGenericInfo(botName, "Not starting this instance because it's disabled in config file"); - } + foreach (var configFile in Directory.EnumerateFiles(ConfigDirectoryPath, "*.xml")) { + string botName = Path.GetFileNameWithoutExtension(configFile); + Bot bot = new Bot(botName); + if (!bot.Enabled) { + Logging.LogGenericInfo(botName, "Not starting this instance because it's disabled in config file"); } } - Thread.Sleep(Timeout.Infinite); + // Check if we got any bots running + OnBotShutdown(null); + + ShutdownResetEvent.WaitOne(); } } } diff --git a/ArchiSteamFarm/SteamTradeOffer.cs b/ArchiSteamFarm/SteamTradeOffer.cs index c60fe6655..72c93eebc 100644 --- a/ArchiSteamFarm/SteamTradeOffer.cs +++ b/ArchiSteamFarm/SteamTradeOffer.cs @@ -56,7 +56,7 @@ namespace ArchiSteamFarm { // Extra internal ulong OtherSteamID64 { - get { + get { // This is quite costly, consider getting only once return new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64(); } } diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index bff16733c..fc65f005a 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -86,10 +86,6 @@ namespace ArchiSteamFarm { return result; } - internal static async Task UrlToHttpResponse(string websiteAddress) { - return await UrlToHttpResponse(websiteAddress, null).ConfigureAwait(false); - } - internal static async Task UrlToHtmlDocument(string websiteAddress, Dictionary cookieVariables = null) { if (string.IsNullOrEmpty(websiteAddress)) { return null; diff --git a/ArchiSteamFarm/config/example.xml b/ArchiSteamFarm/config/example.xml index ebc4bdb24..236059a7f 100644 --- a/ArchiSteamFarm/config/example.xml +++ b/ArchiSteamFarm/config/example.xml @@ -3,17 +3,17 @@ - - - + + + - + - + @@ -35,6 +35,12 @@ + + + + + +