Many improvements

Most notable:
- Support for older configs, with auto-selecting defaults for missing ones
- Auto exit when all bots are done
This commit is contained in:
JustArchi
2015-10-31 03:27:58 +01:00
parent fa6dbed593
commit 411f796fab
6 changed files with 168 additions and 109 deletions

View File

@@ -35,40 +35,56 @@ namespace ArchiSteamFarm {
internal class Bot { internal class Bot {
private const ushort CallbackSleep = 500; // In miliseconds private const ushort CallbackSleep = 500; // In miliseconds
private readonly Dictionary<string, string> Config = new Dictionary<string, string>(); private static readonly HashSet<Bot> Bots = new HashSet<Bot>();
internal readonly string BotName;
private readonly string ConfigFile; private readonly string ConfigFile;
private readonly string SentryFile; private readonly string SentryFile;
private readonly CardsFarmer CardsFarmer; private bool IsRunning = false;
internal ulong BotID { get; private set; }
private string AuthCode, TwoFactorAuth; private string AuthCode, TwoFactorAuth;
internal readonly string BotName;
internal ArchiHandler ArchiHandler { get; private set; } internal ArchiHandler ArchiHandler { get; private set; }
internal ArchiWebHandler ArchiWebHandler { get; private set; } internal ArchiWebHandler ArchiWebHandler { get; private set; }
internal CallbackManager CallbackManager { get; private set; } internal CallbackManager CallbackManager { get; private set; }
internal CardsFarmer CardsFarmer { get; private set; }
internal SteamClient SteamClient { get; private set; } internal SteamClient SteamClient { get; private set; }
internal SteamFriends SteamFriends { get; private set; } internal SteamFriends SteamFriends { get; private set; }
internal SteamUser SteamUser { get; private set; } internal SteamUser SteamUser { get; private set; }
internal Trading Trading { get; private set; } internal Trading Trading { get; private set; }
// Config variables // Config variables
internal bool Enabled { get { return bool.Parse(Config["Enabled"]); } } internal bool Enabled { get; private set; } = true;
private string SteamLogin { get { return Config["SteamLogin"]; } } internal string SteamLogin { get; private set; } = "null";
private string SteamPassword { get { return Config["SteamPassword"]; } } internal string SteamPassword { get; private set; } = "null";
private string SteamNickname { get { return Config["SteamNickname"]; } } internal string SteamNickname { get; private set; } = "null";
private string SteamApiKey { get { return Config["SteamApiKey"]; } } internal string SteamApiKey { get; private set; } = "null";
private string SteamParentalPIN { get { return Config["SteamParentalPIN"]; } } internal string SteamParentalPIN { get; private set; } = "0";
internal ulong SteamMasterID { get { return ulong.Parse(Config["SteamMasterID"]); } } internal ulong SteamMasterID { get; private set; } = 76561198006963719;
private ulong SteamMasterClanID { get { return ulong.Parse(Config["SteamMasterClanID"]); } } internal ulong SteamMasterClanID { get; private set; } = 0;
internal HashSet<uint> Blacklist { get; } = new HashSet<uint>(); internal bool ShutdownOnFarmingFinished { get; private set; } = true;
internal bool Statistics { get { return bool.Parse(Config["Statistics"]); } } internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint> { 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) { internal Bot(string botName) {
BotName = botName; BotName = botName;
CardsFarmer = new CardsFarmer(this);
ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml"); ConfigFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".xml");
SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin"); SentryFile = Path.Combine(Program.ConfigDirectoryPath, BotName + ".bin");
@@ -79,44 +95,11 @@ namespace ArchiSteamFarm {
return; return;
} }
Start(); lock (Bots) {
} Bots.Add(this);
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;
} }
// Initialize
SteamClient = new SteamClient(); SteamClient = new SteamClient();
ArchiHandler = new ArchiHandler(); ArchiHandler = new ArchiHandler();
@@ -141,20 +124,106 @@ namespace ArchiSteamFarm {
CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse); CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);
ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey); ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
CardsFarmer = new CardsFarmer(this);
Trading = new Trading(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(); SteamClient.Connect();
Task.Run(() => HandleCallbacks()); IsRunning = true;
Task.Run(() => HandleCallbacks());
} }
internal void Stop() { internal void Stop() {
if (SteamClient == null) { if (!IsRunning) {
return; return;
} }
SteamClient.Disconnect(); SteamClient.Disconnect();
SteamClient = null; IsRunning = false;
CallbackManager = null; }
internal void Shutdown() {
Stop();
lock (Bots) {
Bots.Remove(this);
}
Program.OnBotShutdown(this);
}
internal void OnFarmingFinished() {
if (ShutdownOnFarmingFinished) {
Shutdown();
}
} }
internal void PlayGame(params ulong[] gameIDs) { internal void PlayGame(params ulong[] gameIDs) {
@@ -163,7 +232,7 @@ namespace ArchiSteamFarm {
private void HandleCallbacks() { private void HandleCallbacks() {
TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep);
while (CallbackManager != null) { while (IsRunning) {
CallbackManager.RunWaitCallbacks(timeSpan); CallbackManager.RunWaitCallbacks(timeSpan);
} }
} }
@@ -186,21 +255,17 @@ namespace ArchiSteamFarm {
sentryHash = CryptoHelper.SHAHash(sentryFileContent); sentryHash = CryptoHelper.SHAHash(sentryFileContent);
} }
string steamLogin = SteamLogin; if (SteamLogin.Equals("null")) {
if (string.IsNullOrEmpty(steamLogin) || steamLogin.Equals("null")) { SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
steamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
Config["SteamLogin"] = steamLogin;
} }
string steamPassword = SteamPassword; if (SteamPassword.Equals("null")) {
if (string.IsNullOrEmpty(steamPassword) || steamPassword.Equals("null")) { SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
steamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
Config["SteamPassword"] = steamPassword;
} }
SteamUser.LogOn(new SteamUser.LogOnDetails { SteamUser.LogOn(new SteamUser.LogOnDetails {
Username = steamLogin, Username = SteamLogin,
Password = steamPassword, Password = SteamPassword,
AuthCode = AuthCode, AuthCode = AuthCode,
TwoFactorCode = TwoFactorAuth, TwoFactorCode = TwoFactorAuth,
SentryFileHash = sentryHash SentryFileHash = sentryHash
@@ -304,10 +369,6 @@ namespace ArchiSteamFarm {
return; return;
} }
if (callback.ClientSteamID != 0) {
BotID = callback.ClientSteamID;
}
EResult result = callback.Result; EResult result = callback.Result;
switch (result) { switch (result) {
case EResult.AccountLogonDenied: case EResult.AccountLogonDenied:
@@ -319,18 +380,15 @@ namespace ArchiSteamFarm {
case EResult.OK: case EResult.OK:
Logging.LogGenericInfo(BotName, "Successfully logged on!"); Logging.LogGenericInfo(BotName, "Successfully logged on!");
string steamNickname = SteamNickname; if (!SteamNickname.Equals("null")) {
if (!string.IsNullOrEmpty(steamNickname) && !steamNickname.Equals("null")) { SteamFriends.SetPersonaName(SteamNickname);
SteamFriends.SetPersonaName(steamNickname);
} }
string steamParentalPIN = SteamParentalPIN; if (SteamParentalPIN.Equals("null")) {
if (string.IsNullOrEmpty(steamParentalPIN) || steamParentalPIN.Equals("null")) { SteamParentalPIN = Program.GetUserInput(BotName, Program.EUserInputType.SteamParentalPIN);
steamParentalPIN = Program.GetUserInput(BotName, Program.EUserInputType.SteamParentalPIN);
Config["SteamParentalPIN"] = 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; ulong clanID = SteamMasterClanID;
if (clanID != 0) { if (clanID != 0) {
@@ -353,7 +411,7 @@ namespace ArchiSteamFarm {
break; break;
default: default:
Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult); Logging.LogGenericWarning(BotName, "Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult);
Stop(); Shutdown();
break; break;
} }
} }

View File

@@ -99,11 +99,12 @@ namespace ArchiSteamFarm {
if (await Farm(appID).ConfigureAwait(false)) { if (await Farm(appID).ConfigureAwait(false)) {
appIDs.Remove(appID); appIDs.Remove(appID);
} else { } else {
break; return;
} }
} }
Logging.LogGenericInfo(Bot.BotName, "Farming finished!"); Logging.LogGenericInfo(Bot.BotName, "Farming finished!");
Bot.OnFarmingFinished();
} }
private async Task<bool?> ShouldFarm(ulong appID) { private async Task<bool?> ShouldFarm(ulong appID) {

View File

@@ -24,7 +24,6 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
@@ -44,7 +43,7 @@ namespace ArchiSteamFarm {
internal const string ConfigDirectoryPath = "config"; internal const string ConfigDirectoryPath = "config";
private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest"; private const string LatestGithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases/latest";
private static readonly HashSet<Bot> Bots = new HashSet<Bot>(); private static readonly ManualResetEvent ShutdownResetEvent = new ManualResetEvent(false);
internal static readonly object ConsoleLock = new object(); internal static readonly object ConsoleLock = new object();
internal static string Version { get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); } } internal static string Version { get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); } }
@@ -71,7 +70,7 @@ namespace ArchiSteamFarm {
} }
internal static void Exit(int exitCode = 0) { internal static void Exit(int exitCode = 0) {
ShutdownAllBots(); Bot.ShutdownAllBots();
Environment.Exit(exitCode); Environment.Exit(exitCode);
} }
@@ -101,12 +100,11 @@ namespace ArchiSteamFarm {
return result; return result;
} }
private static void ShutdownAllBots() { internal static void OnBotShutdown(Bot bot) {
lock (Bots) { if (Bot.GetRunningBotsCount() == 0) {
foreach (Bot bot in Bots) { Logging.LogGenericInfo("Main", "No bots are running, exiting");
bot.Stop(); Thread.Sleep(5000); // This might be the only message user gets, consider giving him some time
} ShutdownResetEvent.Set();
Bots.Clear();
} }
} }
@@ -126,18 +124,18 @@ namespace ArchiSteamFarm {
Exit(1); Exit(1);
} }
lock (Bots) { foreach (var configFile in Directory.EnumerateFiles(ConfigDirectoryPath, "*.xml")) {
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectoryPath, "*.xml")) { string botName = Path.GetFileNameWithoutExtension(configFile);
string botName = Path.GetFileNameWithoutExtension(configFile); Bot bot = new Bot(botName);
Bot bot = new Bot(botName); if (!bot.Enabled) {
Bots.Add(bot); Logging.LogGenericInfo(botName, "Not starting this instance because it's disabled in config file");
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();
} }
} }
} }

View File

@@ -56,7 +56,7 @@ namespace ArchiSteamFarm {
// Extra // Extra
internal ulong OtherSteamID64 { internal ulong OtherSteamID64 {
get { get { // This is quite costly, consider getting only once
return new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64(); return new SteamID((uint) accountid_other, EUniverse.Public, EAccountType.Individual).ConvertToUInt64();
} }
} }

View File

@@ -86,10 +86,6 @@ namespace ArchiSteamFarm {
return result; return result;
} }
internal static async Task<HttpResponseMessage> UrlToHttpResponse(string websiteAddress) {
return await UrlToHttpResponse(websiteAddress, null).ConfigureAwait(false);
}
internal static async Task<HtmlDocument> UrlToHtmlDocument(string websiteAddress, Dictionary<string, string> cookieVariables = null) { internal static async Task<HtmlDocument> UrlToHtmlDocument(string websiteAddress, Dictionary<string, string> cookieVariables = null) {
if (string.IsNullOrEmpty(websiteAddress)) { if (string.IsNullOrEmpty(websiteAddress)) {
return null; return null;

View File

@@ -3,17 +3,17 @@
<!-- Every bot should have it's own unique .xml configuration file, this is example on which you can base on --> <!-- Every bot should have it's own unique .xml configuration file, this is example on which you can base on -->
<!-- Master switch to turn account on and off, set to "true" after you're done --> <!-- Master switch to turn account on and off, set to "true" after you're done -->
<!-- TIP: This bot instance won't run unless below switch is set to "true" --> <!-- TIP: This bot instance won't run unless below switch is set to "true" -->
<Enabled type="bool" value="false"/> <Enabled type="bool" value="true"/>
<!-- This is your steam login, the one you use for logging in to steam --> <!-- This is your steam login, the one you use for logging in to steam -->
<!-- TIP: You can use "null" if you wish to enter login on every startup --> <!-- TIP: You can use "null" if you wish to enter login on every startup -->
<SteamLogin type="string" value="Foo"/> <SteamLogin type="string" value="null"/>
<!-- This is your steam password, the one you use for logging in to steam --> <!-- This is your steam password, the one you use for logging in to steam -->
<!-- TIP: You can use "null" if you wish to enter password on every startup --> <!-- TIP: You can use "null" if you wish to enter password on every startup -->
<SteamPassword type="string" value="Bar"/> <SteamPassword type="string" value="null"/>
<!-- This is steam nickname, the one you want to use for bot. Can be anything up to 32 characters --> <!-- This is steam nickname, the one you want to use for bot. Can be anything up to 32 characters -->
<!-- TIP: You can use "null" if you wish to preserve your actual nickname --> <!-- TIP: You can use "null" if you wish to preserve your actual nickname -->
@@ -35,6 +35,12 @@
<!-- TIP: Most likely you don't want to change it --> <!-- TIP: Most likely you don't want to change it -->
<SteamMasterClanID type="ulong" value="0"/> <SteamMasterClanID type="ulong" value="0"/>
<!-- This switch defines if bot should disconnect once farming is finished -->
<!-- When no bots are active, ASF will shutdown as well -->
<!-- Some people may want to keep their bots 24/7, other disconnect them after job is done -->
<!-- Choose yourself what you prefer -->
<ShutdownOnFarmingFinished type="bool" value="true"/>
<!-- Comma-separated list of IDs that should not be considered for farming --> <!-- Comma-separated list of IDs that should not be considered for farming -->
<!-- TIP: Most likely you don't want to change it --> <!-- TIP: Most likely you don't want to change it -->
<Blacklist type="HashSet(uint)" value="368020"/> <Blacklist type="HashSet(uint)" value="368020"/>