diff --git a/.gitignore b/.gitignore
index 307b68247..7cf4a78ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,8 @@
# Ignore all config files, apart from ones we want to include
ArchiSteamFarm/config/*
-!ArchiSteamFarm/config/example.xml
-!ArchiSteamFarm/config/minimal.xml
+!ArchiSteamFarm/config/example.json
+!ArchiSteamFarm/config/minimal.json
# Ignore local debugging log file
ArchiSteamFarm/log.txt
diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj
index 1fcd4fd74..60835faec 100644
--- a/ArchiSteamFarm/ArchiSteamFarm.csproj
+++ b/ArchiSteamFarm/ArchiSteamFarm.csproj
@@ -36,7 +36,7 @@
bin\Debug\
DEBUG;TRACE
prompt
- 4
+ 3
AnyCPU
@@ -101,6 +101,8 @@
+
+
@@ -134,12 +136,6 @@
-
- PreserveNewest
-
-
- PreserveNewest
-
@@ -147,6 +143,9 @@
SteamAuth
+
+
+
@@ -155,15 +154,15 @@
mkdir "$(TargetDir)out" "$(TargetDir)out\config"
- copy "$(TargetDir)config\example.xml" "$(TargetDir)out\config"
- copy "$(TargetDir)config\minimal.xml" "$(TargetDir)out\config"
+ copy "$(TargetDir)config\example.json" "$(TargetDir)out\config"
+ copy "$(TargetDir)config\minimal.json" "$(TargetDir)out\config"
"$(SolutionDir)tools\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(TargetDir)out\ASF.exe.config"
mkdir -p "$(TargetDir)out" "$(TargetDir)out/config"
- cp "$(TargetDir)config/example.xml" "$(TargetDir)out/config"
- cp "$(TargetDir)config/minimal.xml" "$(TargetDir)out/config"
+ cp "$(TargetDir)config/example.json" "$(TargetDir)out/config"
+ cp "$(TargetDir)config/minimal.json" "$(TargetDir)out/config"
mono -O=all "$(SolutionDir)tools/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(TargetDir)out/ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
rm "$(TargetDir)out/ASF.exe.config"
diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs
index a42c1d101..f498a113b 100644
--- a/ArchiSteamFarm/ArchiWebHandler.cs
+++ b/ArchiSteamFarm/ArchiWebHandler.cs
@@ -37,17 +37,12 @@ namespace ArchiSteamFarm {
private const int Timeout = 1000 * WebBrowser.HttpTimeout; // In miliseconds
private readonly Bot Bot;
- private readonly string ApiKey;
private readonly Dictionary Cookie = new Dictionary(4);
private ulong SteamID;
- internal ArchiWebHandler(Bot bot, string apiKey) {
+ internal ArchiWebHandler(Bot bot) {
Bot = bot;
-
- if (!string.IsNullOrEmpty(apiKey) && !apiKey.Equals("null")) {
- ApiKey = apiKey;
- }
}
internal async Task Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
@@ -147,12 +142,12 @@ namespace ArchiSteamFarm {
}
internal List GetTradeOffers() {
- if (ApiKey == null) {
+ if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return null;
}
KeyValue response = null;
- using (dynamic iEconService = WebAPI.GetInterface("IEconService", ApiKey)) {
+ using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
@@ -269,12 +264,12 @@ namespace ArchiSteamFarm {
}
internal bool DeclineTradeOffer(ulong tradeID) {
- if (tradeID == 0 || ApiKey == null) {
+ if (tradeID == 0 || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return false;
}
KeyValue response = null;
- using (dynamic iEconService = WebAPI.GetInterface("IEconService", ApiKey)) {
+ using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs
index cae3b189c..981d6967f 100755
--- a/ArchiSteamFarm/Bot.cs
+++ b/ArchiSteamFarm/Bot.cs
@@ -33,7 +33,6 @@ using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
using System.Text;
namespace ArchiSteamFarm {
@@ -42,16 +41,17 @@ namespace ArchiSteamFarm {
private const ushort CallbackSleep = 500; // In miliseconds
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 string SentryFile;
private readonly Timer SendItemsTimer;
internal readonly string BotName;
internal readonly ArchiHandler ArchiHandler;
internal readonly ArchiWebHandler ArchiWebHandler;
+ internal readonly BotConfig BotConfig;
+ internal readonly BotDatabase BotDatabase;
internal readonly SteamClient SteamClient;
private readonly CallbackManager CallbackManager;
@@ -62,35 +62,10 @@ namespace ArchiSteamFarm {
private readonly Trading Trading;
internal bool KeepRunning { get; private set; } = false;
- internal SteamGuardAccount SteamGuardAccount { get; private set; }
-
- // Config variables
- internal bool Enabled { get; private set; } = false;
- 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; } = 0;
- internal ulong SteamMasterClanID { get; private set; } = 0;
- internal bool StartOnLaunch { get; private set; } = true;
- internal bool CardDropsRestricted { get; private set; } = false;
- internal bool FarmOffline { get; private set; } = false;
- internal bool HandleOfflineMessages { get; private set; } = false;
- internal bool ForwardKeysToOtherBots { get; private set; } = false;
- internal bool DistributeKeys { get; private set; } = false;
- internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
- internal bool ShutdownOnFarmingFinished { get; private set; } = false;
- 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; } = new HashSet();
- internal HashSet GamesPlayedWhileIdle { get; } = new HashSet() { 0 };
- internal bool Statistics { get; private set; } = true;
private bool InvalidPassword = false;
private bool LoggedInElsewhere = false;
- private string AuthCode, LoginKey, TwoFactorAuth;
+ private string AuthCode, TwoFactorAuth;
internal static string GetAnyBotName() {
foreach (string botName in Bots.Keys) {
@@ -136,22 +111,6 @@ namespace ArchiSteamFarm {
return;
}
- BotName = botName;
-
- string botPath = Path.Combine(Program.ConfigDirectory, botName);
- ConfigFile = botPath + ".xml";
- LoginKeyFile = botPath + ".key";
- MobileAuthenticatorFile = botPath + ".auth";
- SentryFile = botPath + ".bin";
-
- if (!ReadConfig()) {
- return;
- }
-
- if (!Enabled) {
- return;
- }
-
bool alreadyExists;
lock (Bots) {
alreadyExists = Bots.ContainsKey(botName);
@@ -164,6 +123,62 @@ namespace ArchiSteamFarm {
return;
}
+ BotName = botName;
+
+ string botPath = Path.Combine(Program.ConfigDirectory, botName);
+
+ // CONVERSION START
+ if (File.Exists(botPath + ".xml")) {
+ BotConfig = BotConfig.LoadOldFormat(botPath + ".xml");
+ if (BotConfig == null) {
+ return;
+ }
+
+ if (BotConfig.Convert(botPath + ".json")) {
+ try {
+ File.Delete(botPath + ".xml");
+ } catch (Exception e) {
+ Logging.LogGenericException(e, botName);
+ return;
+ }
+ }
+ }
+ // CONVERSION END
+
+ BotConfig = BotConfig.Load(botPath + ".json");
+ if (BotConfig == null) {
+ Logging.LogGenericError("Your config for this bot instance is invalid, it won't run!", botName);
+ return;
+ }
+
+ // CONVERSION START
+ if (File.Exists(botPath + ".key")) {
+ BotDatabase = BotDatabase.Load(botPath + ".db");
+ try {
+ BotDatabase.LoginKey = File.ReadAllText(botPath + ".key");
+ File.Delete(botPath + ".key");
+ } catch (Exception e) {
+ Logging.LogGenericException(e, BotName);
+ }
+ }
+ if (File.Exists(botPath + ".auth")) {
+ BotDatabase = BotDatabase.Load(botPath + ".db");
+ try {
+ BotDatabase.SteamGuardAccount = JsonConvert.DeserializeObject(File.ReadAllText(botPath + ".auth"));
+ File.Delete(botPath + ".auth");
+ } catch (Exception e) {
+ Logging.LogGenericException(e, BotName);
+ }
+ }
+ // CONVERSION END
+
+ if (!BotConfig.Enabled) {
+ return;
+ }
+
+ BotDatabase = BotDatabase.Load(botPath + ".db");
+ SentryFile = botPath + ".bin";
+
// Initialize
SteamClient = new SteamClient();
@@ -184,14 +199,6 @@ namespace ArchiSteamFarm {
CallbackManager.Subscribe(OnFriendMsg);
CallbackManager.Subscribe(OnFriendMsgHistory);
- if (UseAsfAsMobileAuthenticator && File.Exists(MobileAuthenticatorFile)) {
- try {
- SteamGuardAccount = JsonConvert.DeserializeObject(File.ReadAllText(MobileAuthenticatorFile));
- } catch (Exception e) {
- Logging.LogGenericException(e, botName);
- }
- }
-
SteamUser = SteamClient.GetHandler();
CallbackManager.Subscribe(OnAccountInfo);
CallbackManager.Subscribe(OnLoggedOff);
@@ -203,20 +210,20 @@ namespace ArchiSteamFarm {
CallbackManager.Subscribe(OnOfflineMessage);
CallbackManager.Subscribe(OnPurchaseResponse);
- ArchiWebHandler = new ArchiWebHandler(this, SteamApiKey);
+ ArchiWebHandler = new ArchiWebHandler(this);
CardsFarmer = new CardsFarmer(this);
Trading = new Trading(this);
- if (SendTradePeriod > 0 && SendItemsTimer == null) {
+ if (BotConfig.SendTradePeriod > 0 && SendItemsTimer == null) {
SendItemsTimer = new Timer(
async e => await ResponseSendTrade().ConfigureAwait(false),
null,
- TimeSpan.FromHours(SendTradePeriod), // Delay
- TimeSpan.FromHours(SendTradePeriod) // Period
+ TimeSpan.FromHours(BotConfig.SendTradePeriod), // Delay
+ TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period
);
}
- if (!StartOnLaunch) {
+ if (!BotConfig.StartOnLaunch) {
return;
}
@@ -225,15 +232,15 @@ namespace ArchiSteamFarm {
}
internal async Task AcceptAllConfirmations() {
- if (SteamGuardAccount == null) {
+ if (BotDatabase.SteamGuardAccount == null) {
return;
}
- await SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false);
+ await BotDatabase.SteamGuardAccount.RefreshSessionAsync().ConfigureAwait(false);
try {
- foreach (Confirmation confirmation in await SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false)) {
- if (SteamGuardAccount.AcceptConfirmation(confirmation)) {
+ foreach (Confirmation confirmation in await BotDatabase.SteamGuardAccount.FetchConfirmationsAsync().ConfigureAwait(false)) {
+ if (BotDatabase.SteamGuardAccount.AcceptConfirmation(confirmation)) {
Logging.LogGenericInfo("Accepting confirmation: Success!", BotName);
} else {
Logging.LogGenericWarning("Accepting confirmation: Failed!", BotName);
@@ -247,10 +254,10 @@ namespace ArchiSteamFarm {
}
internal void ResetGamesPlayed() {
- if (GamesPlayedWhileIdle.Contains(0)) {
+ if (BotConfig.GamesPlayedWhileIdle.Contains(0)) {
ArchiHandler.PlayGames(0);
} else {
- ArchiHandler.PlayGames(GamesPlayedWhileIdle);
+ ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle);
}
}
@@ -261,10 +268,10 @@ namespace ArchiSteamFarm {
}
internal async Task OnFarmingFinished(bool farmedSomething) {
- if (farmedSomething && SendOnFarmingFinished) {
+ if (farmedSomething && BotConfig.SendOnFarmingFinished) {
await ResponseSendTrade().ConfigureAwait(false);
}
- if (ShutdownOnFarmingFinished) {
+ if (BotConfig.ShutdownOnFarmingFinished) {
Shutdown();
}
}
@@ -417,13 +424,13 @@ namespace ArchiSteamFarm {
}
private async Task ResponseSendTrade() {
- if (SteamMasterID == 0) {
+ if (BotConfig.SteamMasterID == 0) {
return "Trade couldn't be send because SteamMasterID is not defined!";
}
string token = null;
- if (!string.IsNullOrEmpty(SteamTradeToken) && !SteamTradeToken.Equals("null")) {
- token = SteamTradeToken;
+ if (!string.IsNullOrEmpty(BotConfig.SteamTradeToken) && !BotConfig.SteamTradeToken.Equals("null")) {
+ token = BotConfig.SteamTradeToken;
}
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
@@ -433,7 +440,7 @@ namespace ArchiSteamFarm {
return "Nothing to send, inventory seems empty!";
}
- if (await ArchiWebHandler.SendTradeOffer(inventory, SteamMasterID, token).ConfigureAwait(false)) {
+ if (await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, token).ConfigureAwait(false)) {
await AcceptAllConfirmations().ConfigureAwait(false);
return "Trade offer sent successfully!";
} else {
@@ -455,12 +462,12 @@ namespace ArchiSteamFarm {
}
private string Response2FA() {
- if (SteamGuardAccount == null) {
+ if (BotDatabase.SteamGuardAccount == null) {
return "That bot doesn't have ASF 2FA enabled!";
}
long timeLeft = 30 - TimeAligner.GetSteamTime() % 30;
- return "2FA Token: " + SteamGuardAccount.GenerateSteamGuardCode() + " (expires in " + timeLeft + " seconds)";
+ return "2FA Token: " + BotDatabase.SteamGuardAccount.GenerateSteamGuardCode() + " (expires in " + timeLeft + " seconds)";
}
private static string Response2FA(string botName) {
@@ -477,7 +484,7 @@ namespace ArchiSteamFarm {
}
private string Response2FAOff() {
- if (SteamGuardAccount == null) {
+ if (BotDatabase.SteamGuardAccount == null) {
return "That bot doesn't have ASF 2FA enabled!";
}
@@ -542,7 +549,7 @@ namespace ArchiSteamFarm {
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OnCooldown:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.RegionLocked:
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items));
- if (DistributeKeys) {
+ if (BotConfig.DistributeKeys) {
do {
if (iterator.MoveNext()) {
currentBot = iterator.Current;
@@ -551,13 +558,13 @@ namespace ArchiSteamFarm {
}
} while (currentBot == this);
- if (!ForwardKeysToOtherBots) {
+ if (!BotConfig.ForwardKeysToOtherBots) {
key = reader.ReadLine();
}
break;
}
- if (!ForwardKeysToOtherBots) {
+ if (!BotConfig.ForwardKeysToOtherBots) {
key = reader.ReadLine();
break;
}
@@ -606,7 +613,7 @@ namespace ArchiSteamFarm {
break;
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK:
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items));
- if (DistributeKeys) {
+ if (BotConfig.DistributeKeys) {
do {
if (iterator.MoveNext()) {
currentBot = iterator.Current;
@@ -620,7 +627,7 @@ namespace ArchiSteamFarm {
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
response.Append(Environment.NewLine + "<" + currentBot.BotName + "> Key: " + key + " | Status: " + purchaseResult + " | Items: " + string.Join("", items));
- if (DistributeKeys && !ForwardKeysToOtherBots) {
+ if (BotConfig.DistributeKeys && !BotConfig.ForwardKeysToOtherBots) {
do {
if (iterator.MoveNext()) {
currentBot = iterator.Current;
@@ -830,12 +837,12 @@ namespace ArchiSteamFarm {
}
private bool LinkMobileAuthenticator() {
- if (SteamGuardAccount != null) {
+ if (BotDatabase.SteamGuardAccount != null) {
return false;
}
Logging.LogGenericInfo("Linking new ASF MobileAuthenticator...", BotName);
- UserLogin userLogin = new UserLogin(SteamLogin, SteamPassword);
+ UserLogin userLogin = new UserLogin(BotConfig.SteamLogin, BotConfig.SteamPassword);
LoginResult loginResult;
while ((loginResult = userLogin.DoLogin()) != LoginResult.LoginOkay) {
switch (loginResult) {
@@ -862,14 +869,7 @@ namespace ArchiSteamFarm {
}
}
- SteamGuardAccount = authenticatorLinker.LinkedAccount;
-
- try {
- File.WriteAllText(MobileAuthenticatorFile, JsonConvert.SerializeObject(SteamGuardAccount));
- } catch (Exception e) {
- Logging.LogGenericException(e, BotName);
- return false;
- }
+ BotDatabase.SteamGuardAccount = authenticatorLinker.LinkedAccount;
AuthenticatorLinker.FinalizeResult finalizeResult = authenticatorLinker.FinalizeAddAuthenticator(Program.GetUserInput(BotName, Program.EUserInputType.SMS));
if (finalizeResult != AuthenticatorLinker.FinalizeResult.Success) {
@@ -879,33 +879,30 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericInfo("Successfully linked ASF as new mobile authenticator for this account!", BotName);
- Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, SteamGuardAccount.RevocationCode);
+ Program.GetUserInput(BotName, Program.EUserInputType.RevocationCode, BotDatabase.SteamGuardAccount.RevocationCode);
return true;
}
private bool DelinkMobileAuthenticator() {
- if (SteamGuardAccount == null) {
+ if (BotDatabase.SteamGuardAccount == null) {
return false;
}
- bool result = SteamGuardAccount.DeactivateAuthenticator();
- SteamGuardAccount = null;
+ bool result = BotDatabase.SteamGuardAccount.DeactivateAuthenticator();
- try {
- File.Delete(MobileAuthenticatorFile);
- } catch (Exception e) {
- Logging.LogGenericException(e, BotName);
+ if (result) {
+ BotDatabase.SteamGuardAccount = null;
}
return result;
}
private void JoinMasterChat() {
- if (SteamMasterClanID == 0) {
+ if (BotConfig.SteamMasterClanID == 0) {
return;
}
- SteamFriends.JoinChat(SteamMasterClanID);
+ SteamFriends.JoinChat(BotConfig.SteamMasterClanID);
}
private void OnConnected(SteamClient.ConnectedCallback callback) {
@@ -926,14 +923,6 @@ namespace ArchiSteamFarm {
return;
}
- if (File.Exists(LoginKeyFile)) {
- try {
- LoginKey = File.ReadAllText(LoginKeyFile);
- } catch (Exception e) {
- Logging.LogGenericException(e, BotName);
- }
- }
-
byte[] sentryHash = null;
if (File.Exists(SentryFile)) {
try {
@@ -944,20 +933,20 @@ namespace ArchiSteamFarm {
}
}
- if (SteamLogin.Equals("null")) {
- SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
+ if (string.IsNullOrEmpty(BotConfig.SteamLogin)) {
+ BotConfig.SteamLogin = Program.GetUserInput(BotName, Program.EUserInputType.Login);
}
- if (SteamPassword.Equals("null") && string.IsNullOrEmpty(LoginKey)) {
- SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
+ if (string.IsNullOrEmpty(BotConfig.SteamPassword) && string.IsNullOrEmpty(BotDatabase.LoginKey)) {
+ BotConfig.SteamPassword = Program.GetUserInput(BotName, Program.EUserInputType.Password);
}
SteamUser.LogOn(new SteamUser.LogOnDetails {
- Username = SteamLogin,
- Password = SteamPassword,
+ Username = BotConfig.SteamLogin,
+ Password = BotConfig.SteamPassword,
AuthCode = AuthCode,
LoginID = LoginID,
- LoginKey = LoginKey,
+ LoginKey = BotDatabase.LoginKey,
TwoFactorCode = TwoFactorAuth,
SentryFileHash = sentryHash,
ShouldRememberPassword = true
@@ -983,15 +972,8 @@ namespace ArchiSteamFarm {
if (InvalidPassword) {
InvalidPassword = false;
- if (!string.IsNullOrEmpty(LoginKey)) { // InvalidPassword means usually that login key has expired, if we used it
- LoginKey = null;
-
- try {
- File.Delete(LoginKeyFile);
- } catch (Exception e) {
- Logging.LogGenericException(e, BotName);
- }
-
+ if (!string.IsNullOrEmpty(BotDatabase.LoginKey)) { // InvalidPassword means usually that login key has expired, if we used it
+ BotDatabase.LoginKey = null;
Logging.LogGenericInfo("Removed expired login key", BotName);
} else { // If we didn't use login key, InvalidPassword usually means we got captcha or other network-based throttling
Logging.LogGenericInfo("Will retry after 25 minutes...", BotName);
@@ -1024,7 +1006,7 @@ namespace ArchiSteamFarm {
return;
}
- if (callback.PatronID != SteamMasterID) {
+ if (callback.PatronID != BotConfig.SteamMasterID) {
return;
}
@@ -1040,7 +1022,7 @@ namespace ArchiSteamFarm {
return;
}
- if (callback.ChatterID != SteamMasterID) {
+ if (callback.ChatterID != BotConfig.SteamMasterID) {
return;
}
@@ -1069,7 +1051,7 @@ namespace ArchiSteamFarm {
// TODO: Accept clan invites from master?
break;
default:
- if (friend.SteamID == SteamMasterID) {
+ if (friend.SteamID == BotConfig.SteamMasterID) {
SteamFriends.AddFriend(friend.SteamID);
}
break;
@@ -1086,7 +1068,7 @@ namespace ArchiSteamFarm {
return;
}
- if (callback.Sender != SteamMasterID) {
+ if (callback.Sender != BotConfig.SteamMasterID) {
return;
}
@@ -1102,7 +1084,7 @@ namespace ArchiSteamFarm {
return;
}
- if (callback.SteamID != SteamMasterID) {
+ if (callback.SteamID != BotConfig.SteamMasterID) {
return;
}
@@ -1132,7 +1114,7 @@ namespace ArchiSteamFarm {
return;
}
- if (!FarmOffline) {
+ if (!BotConfig.FarmOffline) {
SteamFriends.SetPersonaState(EPersonaState.Online);
}
}
@@ -1160,13 +1142,13 @@ namespace ArchiSteamFarm {
switch (callback.Result) {
case EResult.AccountLogonDenied:
- AuthCode = Program.GetUserInput(SteamLogin, Program.EUserInputType.SteamGuard);
+ AuthCode = Program.GetUserInput(BotConfig.SteamLogin, Program.EUserInputType.SteamGuard);
break;
case EResult.AccountLoginDeniedNeedTwoFactor:
- if (SteamGuardAccount == null) {
- TwoFactorAuth = Program.GetUserInput(SteamLogin, Program.EUserInputType.TwoFactorAuthentication);
+ if (BotDatabase.SteamGuardAccount == null) {
+ TwoFactorAuth = Program.GetUserInput(BotConfig.SteamLogin, Program.EUserInputType.TwoFactorAuthentication);
} else {
- TwoFactorAuth = SteamGuardAccount.GenerateSteamGuardCode();
+ TwoFactorAuth = BotDatabase.SteamGuardAccount.GenerateSteamGuardCode();
}
break;
case EResult.InvalidPassword:
@@ -1176,7 +1158,7 @@ namespace ArchiSteamFarm {
case EResult.OK:
Logging.LogGenericInfo("Successfully logged on!", BotName);
- if (UseAsfAsMobileAuthenticator && TwoFactorAuth == null && SteamGuardAccount == null) {
+ if (BotConfig.UseAsfAsMobileAuthenticator && TwoFactorAuth == null && BotDatabase.SteamGuardAccount == null) {
LinkMobileAuthenticator();
}
@@ -1184,27 +1166,23 @@ namespace ArchiSteamFarm {
AuthCode = null;
TwoFactorAuth = null;
- if (!SteamNickname.Equals("null")) {
- await SteamFriends.SetPersonaName(SteamNickname);
- }
-
ResetGamesPlayed();
- if (SteamParentalPIN.Equals("null")) {
- SteamParentalPIN = Program.GetUserInput(BotName, Program.EUserInputType.SteamParentalPIN);
+ if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
+ BotConfig.SteamParentalPIN = Program.GetUserInput(BotName, Program.EUserInputType.SteamParentalPIN);
}
- if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, SteamParentalPIN).ConfigureAwait(false)) {
+ if (!await ArchiWebHandler.Init(SteamClient, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) {
await Restart().ConfigureAwait(false);
return;
}
- if (SteamMasterClanID != 0) {
- await ArchiWebHandler.JoinClan(SteamMasterClanID).ConfigureAwait(false);
+ if (BotConfig.SteamMasterClanID != 0) {
+ await ArchiWebHandler.JoinClan(BotConfig.SteamMasterClanID).ConfigureAwait(false);
JoinMasterChat();
}
- if (Statistics) {
+ if (BotConfig.Statistics) {
await ArchiWebHandler.JoinClan(ArchiSCFarmGroup).ConfigureAwait(false);
SteamFriends.JoinChat(ArchiSCFarmGroup);
}
@@ -1231,12 +1209,7 @@ namespace ArchiSteamFarm {
return;
}
- try {
- File.WriteAllText(LoginKeyFile, callback.LoginKey);
- } catch (Exception e) {
- Logging.LogGenericException(e, BotName);
- }
-
+ BotDatabase.LoginKey = callback.LoginKey;
SteamUser.AcceptNewLoginKey(callback);
}
@@ -1309,7 +1282,7 @@ namespace ArchiSteamFarm {
return;
}
- if (!HandleOfflineMessages) {
+ if (!BotConfig.HandleOfflineMessages) {
return;
}
@@ -1326,115 +1299,5 @@ 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 "GamesPlayedWhileIdle":
- GamesPlayedWhileIdle.Clear();
- foreach (string appID in value.Split(',')) {
- GamesPlayedWhileIdle.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/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs
new file mode 100644
index 000000000..0f37439d6
--- /dev/null
+++ b/ArchiSteamFarm/BotConfig.cs
@@ -0,0 +1,252 @@
+/*
+ _ _ _ ____ _ _____
+ / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
+ / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
+ / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
+/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
+
+ Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
+ Contact: JustArchi@JustArchi.net
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Xml;
+
+namespace ArchiSteamFarm {
+ internal sealed class BotConfig {
+ internal static readonly HashSet GlobalBlacklist = new HashSet { 303700, 335590, 368020, 425280 };
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool Enabled { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool StartOnLaunch { get; private set; } = true;
+
+ [JsonProperty]
+ internal string SteamLogin { get; set; } = null;
+
+ [JsonProperty]
+ internal string SteamPassword { get; set; } = null;
+
+ [JsonProperty]
+ internal string SteamParentalPIN { get; set; } = "0";
+
+ [JsonProperty]
+ internal string SteamApiKey { get; private set; } = null;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal ulong SteamMasterID { get; private set; } = 0;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal ulong SteamMasterClanID { get; private set; } = 0;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool CardDropsRestricted { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool FarmOffline { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool HandleOfflineMessages { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool ForwardKeysToOtherBots { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool DistributeKeys { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool ShutdownOnFarmingFinished { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool SendOnFarmingFinished { get; private set; } = false;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal string SteamTradeToken { get; private set; } = null;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal byte SendTradePeriod { get; private set; } = 0;
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal HashSet GamesPlayedWhileIdle { get; private set; } = new HashSet() { 0 };
+
+ [JsonProperty(Required = Required.DisallowNull)]
+ internal bool Statistics { get; private set; } = true;
+
+
+ internal static BotConfig Load(string path) {
+ if (!File.Exists(path)) {
+ return null;
+ }
+
+ BotConfig botConfig;
+ try {
+ botConfig = JsonConvert.DeserializeObject(File.ReadAllText(path));
+ } catch (Exception e) {
+ Logging.LogGenericException(e);
+ return null;
+ }
+
+ return botConfig;
+ }
+
+ // TODO: This should be removed soon
+ internal static BotConfig LoadOldFormat(string path) {
+ if (!File.Exists(path)) {
+ return null;
+ }
+
+ BotConfig botConfig = new BotConfig();
+
+ try {
+ using (XmlReader reader = XmlReader.Create(path)) {
+ 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":
+ botConfig.Enabled = bool.Parse(value);
+ break;
+ case "SteamLogin":
+ botConfig.SteamLogin = value;
+ break;
+ case "SteamPassword":
+ botConfig.SteamPassword = value;
+ break;
+ case "SteamApiKey":
+ botConfig.SteamApiKey = value;
+ break;
+ case "SteamTradeToken":
+ botConfig.SteamTradeToken = value;
+ break;
+ case "SteamParentalPIN":
+ botConfig.SteamParentalPIN = value;
+ break;
+ case "SteamMasterID":
+ botConfig.SteamMasterID = ulong.Parse(value);
+ break;
+ case "SteamMasterClanID":
+ botConfig.SteamMasterClanID = ulong.Parse(value);
+ break;
+ case "StartOnLaunch":
+ botConfig.StartOnLaunch = bool.Parse(value);
+ break;
+ case "UseAsfAsMobileAuthenticator":
+ botConfig.UseAsfAsMobileAuthenticator = bool.Parse(value);
+ break;
+ case "CardDropsRestricted":
+ botConfig.CardDropsRestricted = bool.Parse(value);
+ break;
+ case "FarmOffline":
+ botConfig.FarmOffline = bool.Parse(value);
+ break;
+ case "HandleOfflineMessages":
+ botConfig.HandleOfflineMessages = bool.Parse(value);
+ break;
+ case "ForwardKeysToOtherBots":
+ botConfig.ForwardKeysToOtherBots = bool.Parse(value);
+ break;
+ case "DistributeKeys":
+ botConfig.DistributeKeys = bool.Parse(value);
+ break;
+ case "ShutdownOnFarmingFinished":
+ botConfig.ShutdownOnFarmingFinished = bool.Parse(value);
+ break;
+ case "SendOnFarmingFinished":
+ botConfig.SendOnFarmingFinished = bool.Parse(value);
+ break;
+ case "SendTradePeriod":
+ botConfig.SendTradePeriod = byte.Parse(value);
+ break;
+ case "GamesPlayedWhileIdle":
+ botConfig.GamesPlayedWhileIdle.Clear();
+ foreach (string appID in value.Split(',')) {
+ botConfig.GamesPlayedWhileIdle.Add(uint.Parse(appID));
+ }
+ break;
+ case "Statistics":
+ botConfig.Statistics = bool.Parse(value);
+ break;
+ case "Blacklist":
+ case "SteamNickname":
+ break;
+ default:
+ Logging.LogGenericWarning("Unrecognized config value: " + key + "=" + value);
+ break;
+ }
+ }
+ }
+ } catch (Exception e) {
+ Logging.LogGenericException(e);
+ Logging.LogGenericError("Your config for this bot instance is invalid, it won't run!");
+ return null;
+ }
+
+ // Fixups for new format
+ if (botConfig.SteamLogin != null && botConfig.SteamLogin.Equals("null")) {
+ botConfig.SteamLogin = null;
+ }
+
+ if (botConfig.SteamPassword != null && botConfig.SteamPassword.Equals("null")) {
+ botConfig.SteamPassword = null;
+ }
+
+ if (botConfig.SteamApiKey != null && botConfig.SteamApiKey.Equals("null")) {
+ botConfig.SteamApiKey = null;
+ }
+
+ if (botConfig.SteamParentalPIN != null && botConfig.SteamParentalPIN.Equals("null")) {
+ botConfig.SteamParentalPIN = null;
+ }
+
+ return botConfig;
+ }
+
+ // This constructor is used only by deserializer
+ private BotConfig() { }
+
+ // TODO: This should be removed soon
+ internal bool Convert(string path) {
+ try {
+ File.WriteAllText(path, JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented));
+ } catch (Exception e) {
+ Logging.LogGenericException(e);
+ return false;
+ }
+
+ Logging.LogGenericWarning("Your config was converted to new ASF V2.0 format");
+ return true;
+ }
+ }
+}
diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs
new file mode 100644
index 000000000..29cbe80d4
--- /dev/null
+++ b/ArchiSteamFarm/BotDatabase.cs
@@ -0,0 +1,96 @@
+/*
+ _ _ _ ____ _ _____
+ / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
+ / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
+ / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
+/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
+
+ Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
+ Contact: JustArchi@JustArchi.net
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+using Newtonsoft.Json;
+using SteamAuth;
+using System;
+using System.IO;
+
+namespace ArchiSteamFarm {
+ internal sealed class BotDatabase {
+ internal string LoginKey {
+ get {
+ return _LoginKey;
+ }
+ set {
+ _LoginKey = value;
+ Save();
+ }
+ }
+
+ internal SteamGuardAccount SteamGuardAccount {
+ get {
+ return _SteamGuardAccount;
+ }
+ set {
+ _SteamGuardAccount = value;
+ Save();
+ }
+ }
+
+ [JsonProperty(Required = Required.AllowNull)]
+ private string _LoginKey;
+
+ [JsonProperty(Required = Required.AllowNull)]
+ private SteamGuardAccount _SteamGuardAccount;
+
+ private string FilePath;
+
+ internal static BotDatabase Load(string filePath) {
+ if (!File.Exists(filePath)) {
+ return new BotDatabase(filePath);
+ }
+
+ BotDatabase botDatabase;
+ try {
+ botDatabase = JsonConvert.DeserializeObject(File.ReadAllText(filePath));
+ } catch (Exception e) {
+ Logging.LogGenericException(e);
+ return null;
+ }
+
+ botDatabase.FilePath = filePath;
+ return botDatabase;
+ }
+
+ // This constructor is used when creating new database
+ private BotDatabase(string filePath) {
+ FilePath = filePath;
+ Save();
+ }
+
+ // This constructor is used only by deserializer
+ private BotDatabase() { }
+
+ private void Save() {
+ lock (FilePath) {
+ try {
+ File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
+ } catch (Exception e) {
+ Logging.LogGenericException(e);
+ }
+ }
+ }
+ }
+}
diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs
index 042bc7b93..4fd972c03 100755
--- a/ArchiSteamFarm/CardsFarmer.cs
+++ b/ArchiSteamFarm/CardsFarmer.cs
@@ -178,7 +178,7 @@ namespace ArchiSteamFarm {
bool farmedSomething = false;
// Now the algorithm used for farming depends on whether account is restricted or not
- if (Bot.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
+ if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName);
while (GamesToFarm.Count > 0) {
List gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
@@ -294,7 +294,7 @@ namespace ArchiSteamFarm {
}
// If we have restricted card drops, actually do check hours of all games that are left to farm
- if (Bot.CardDropsRestricted) {
+ if (Bot.BotConfig.CardDropsRestricted) {
tasks = new List(GamesToFarm.Keys.Count);
Logging.LogGenericInfo("Checking hours...", Bot.BotName);
foreach (uint appID in GamesToFarm.Keys) {
@@ -327,7 +327,7 @@ namespace ArchiSteamFarm {
continue;
}
- if (Bot.GlobalBlacklist.Contains(appID) || Bot.Blacklist.Contains(appID)) {
+ if (BotConfig.GlobalBlacklist.Contains(appID)) {
continue;
}
diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs
index 80781a6c3..93a156116 100644
--- a/ArchiSteamFarm/Program.cs
+++ b/ArchiSteamFarm/Program.cs
@@ -267,14 +267,25 @@ namespace ArchiSteamFarm {
// Before attempting to connect, initialize our list of CMs
Bot.RefreshCMs().Wait();
- foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.xml")) {
+ foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.json")) {
string botName = Path.GetFileNameWithoutExtension(configFile);
Bot bot = new Bot(botName);
- if (!bot.Enabled) {
+ if (!bot.BotConfig.Enabled) {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
}
}
+ // CONVERSION START
+ foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.xml")) {
+ string botName = Path.GetFileNameWithoutExtension(configFile);
+ Logging.LogGenericWarning("Found legacy " + botName + ".xml config file, it will now be converted to new ASF V2.0 format!");
+ Bot bot = new Bot(botName);
+ if (!bot.BotConfig.Enabled) {
+ Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
+ }
+ }
+ // CONVERSION END
+
// Check if we got any bots running
OnBotShutdown();
diff --git a/ArchiSteamFarm/Properties/AssemblyInfo.cs b/ArchiSteamFarm/Properties/AssemblyInfo.cs
index c3172fe94..fc1f591cf 100644
--- a/ArchiSteamFarm/Properties/AssemblyInfo.cs
+++ b/ArchiSteamFarm/Properties/AssemblyInfo.cs
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.7.0.0")]
-[assembly: AssemblyFileVersion("1.7.0.0")]
+[assembly: AssemblyVersion("2.0.0.0")]
+[assembly: AssemblyFileVersion("2.0.0.0")]
diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs
index a1b71dd89..84250d419 100644
--- a/ArchiSteamFarm/Trading.cs
+++ b/ArchiSteamFarm/Trading.cs
@@ -88,7 +88,7 @@ namespace ArchiSteamFarm {
return;
}
- if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.SteamMasterID) {
+ if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) {
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
} else {
diff --git a/ArchiSteamFarm/config/example.json b/ArchiSteamFarm/config/example.json
new file mode 100644
index 000000000..dd8ff62fa
--- /dev/null
+++ b/ArchiSteamFarm/config/example.json
@@ -0,0 +1,24 @@
+{
+ "Enabled": false,
+ "StartOnLaunch": true,
+ "SteamLogin": null,
+ "SteamPassword": null,
+ "SteamParentalPIN": "0",
+ "SteamApiKey": null,
+ "SteamMasterID": 0,
+ "SteamMasterClanID": 0,
+ "CardDropsRestricted": false,
+ "FarmOffline": false,
+ "HandleOfflineMessages": false,
+ "ForwardKeysToOtherBots": false,
+ "DistributeKeys": false,
+ "UseAsfAsMobileAuthenticator": false,
+ "ShutdownOnFarmingFinished": false,
+ "SendOnFarmingFinished": false,
+ "SteamTradeToken": "null",
+ "SendTradePeriod": 0,
+ "GamesPlayedWhileIdle": [
+ 0
+ ],
+ "Statistics": true
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/config/example.xml b/ArchiSteamFarm/config/example.xml
deleted file mode 100755
index e7063911b..000000000
--- a/ArchiSteamFarm/config/example.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ArchiSteamFarm/config/minimal.json b/ArchiSteamFarm/config/minimal.json
new file mode 100644
index 000000000..559e54280
--- /dev/null
+++ b/ArchiSteamFarm/config/minimal.json
@@ -0,0 +1,24 @@
+{
+ "Enabled": false,
+ "StartOnLaunch": true,
+ "SteamLogin": null,
+ "SteamPassword": null,
+ "SteamParentalPIN": "0",
+ "SteamApiKey": null,
+ "SteamMasterID": 0,
+ "SteamMasterClanID": 0,
+ "CardDropsRestricted": false,
+ "FarmOffline": false,
+ "HandleOfflineMessages": false,
+ "ForwardKeysToOtherBots": false,
+ "DistributeKeys": false,
+ "UseAsfAsMobileAuthenticator": false,
+ "ShutdownOnFarmingFinished": false,
+ "SendOnFarmingFinished": false,
+ "SteamTradeToken": null,
+ "SendTradePeriod": 0,
+ "GamesPlayedWhileIdle": [
+ 0
+ ],
+ "Statistics": true
+}
\ No newline at end of file
diff --git a/ArchiSteamFarm/config/minimal.xml b/ArchiSteamFarm/config/minimal.xml
deleted file mode 100644
index b00aa7997..000000000
--- a/ArchiSteamFarm/config/minimal.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-