mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-22 17:28:37 +00:00
Decent work on config reload ability
It was always annoying to restart whole ASF process just to issue a small config update for one of the bots, with this commit - auto reload has been implemented. ASF will automatically restart given bot instance if it's config file gets changed. With introduction of this commit, we also ditch StartOnLaunch property (because it no longer means sense, any disabled bot can be started through config edit), and we also ditch auto-shutdown of ASF process, because it's possible to edit bot config file to start it. Some further work might be needed, this is pretty much work in progress. It would be super if ASF was also able to automatically decent new bot configs, and create new bots for them, but this is initial step.
This commit is contained in:
@@ -54,7 +54,6 @@ namespace ArchiSteamFarm {
|
||||
internal readonly string BotName;
|
||||
internal readonly ArchiHandler ArchiHandler;
|
||||
internal readonly ArchiWebHandler ArchiWebHandler;
|
||||
internal readonly BotConfig BotConfig;
|
||||
|
||||
private readonly string SentryFile;
|
||||
private readonly BotDatabase BotDatabase;
|
||||
@@ -66,25 +65,28 @@ namespace ArchiSteamFarm {
|
||||
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
|
||||
private readonly ConcurrentHashSet<ulong> SteamFamilySharingIDs = new ConcurrentHashSet<ulong>();
|
||||
private readonly ConcurrentHashSet<uint> OwnedPackageIDs = new ConcurrentHashSet<uint>();
|
||||
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
|
||||
private readonly SteamApps SteamApps;
|
||||
private readonly SteamClient SteamClient;
|
||||
private readonly SteamFriends SteamFriends;
|
||||
private readonly SteamUser SteamUser;
|
||||
private readonly Timer AcceptConfirmationsTimer, HeartBeatTimer, SendItemsTimer;
|
||||
private readonly Timer HeartBeatTimer;
|
||||
private readonly Trading Trading;
|
||||
|
||||
[JsonProperty]
|
||||
internal bool KeepRunning { get; private set; }
|
||||
|
||||
internal BotConfig BotConfig { get; private set; }
|
||||
|
||||
internal bool HasMobileAuthenticator => BotDatabase.MobileAuthenticator != null;
|
||||
internal bool IsConnectedAndLoggedOn => SteamClient.IsConnected && (SteamClient.SteamID != null);
|
||||
internal bool IsFarmingPossible => !PlayingBlocked && (LibraryLockedBySteamID == 0);
|
||||
|
||||
[JsonProperty]
|
||||
internal bool KeepRunning { get; private set; }
|
||||
|
||||
private bool FirstTradeSent, PlayingBlocked, SkipFirstShutdown;
|
||||
private string AuthCode, TwoFactorCode;
|
||||
private ulong LibraryLockedBySteamID;
|
||||
private EResult LastLogOnResult;
|
||||
private Timer FamilySharingInactivityTimer;
|
||||
private Timer AcceptConfirmationsTimer, FamilySharingInactivityTimer, SendItemsTimer;
|
||||
|
||||
internal static string GetAPIStatus() {
|
||||
var response = new {
|
||||
@@ -165,9 +167,9 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BotConfig.Enabled) {
|
||||
Logging.LogGenericInfo("Not initializing this instance because it's disabled in config file", botName);
|
||||
return;
|
||||
// Register bot as available for ASF
|
||||
if (!Bots.TryAdd(botName, this)) {
|
||||
throw new ArgumentException("That bot is already defined!");
|
||||
}
|
||||
|
||||
string botDatabaseFile = botPath + ".db";
|
||||
@@ -237,7 +239,11 @@ namespace ArchiSteamFarm {
|
||||
CallbackManager.Subscribe<ArchiHandler.SharedLibraryLockStatusCallback>(OnSharedLibraryLockStatus);
|
||||
|
||||
ArchiWebHandler = new ArchiWebHandler(this);
|
||||
CardsFarmer = new CardsFarmer(this, BotConfig.Paused);
|
||||
|
||||
CardsFarmer = new CardsFarmer(this) {
|
||||
Paused = BotConfig.Paused
|
||||
};
|
||||
|
||||
Trading = new Trading(this);
|
||||
|
||||
HeartBeatTimer = new Timer(
|
||||
@@ -247,43 +253,103 @@ namespace ArchiSteamFarm {
|
||||
TimeSpan.FromMinutes(1) // Period
|
||||
);
|
||||
|
||||
if (BotConfig.AcceptConfirmationsPeriod > 0) {
|
||||
AcceptConfirmationsTimer = new Timer(
|
||||
async e => await AcceptConfirmations(true).ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
|
||||
TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) // Period
|
||||
);
|
||||
Initialize().Forget();
|
||||
}
|
||||
|
||||
if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) {
|
||||
SendItemsTimer = new Timer(
|
||||
async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
|
||||
null,
|
||||
TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromMinutes(Bots.Count), // Delay
|
||||
TimeSpan.FromHours(BotConfig.SendTradePeriod) // Period
|
||||
);
|
||||
}
|
||||
private async Task Initialize() {
|
||||
BotConfig.NewConfigLoaded += OnNewConfigLoaded;
|
||||
BotConfig.InitializeWatcher();
|
||||
|
||||
// Register bot as available for ASF
|
||||
if (!Bots.TryAdd(botName, this)) {
|
||||
throw new ArgumentException("That bot is already defined!");
|
||||
}
|
||||
|
||||
if (!BotConfig.StartOnLaunch) {
|
||||
if (!BotConfig.Enabled) {
|
||||
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start
|
||||
Start().Forget();
|
||||
await Start().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnNewConfigLoaded(object sender, BotConfig.BotConfigEventArgs args) {
|
||||
if ((sender == null) || (args == null)) {
|
||||
Logging.LogNullError(nameof(sender) + " || " + nameof(args), BotName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.BotConfig == null) {
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.BotConfig == BotConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
await InitializationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
if (args.BotConfig == BotConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stop();
|
||||
BotConfig.NewConfigLoaded -= OnNewConfigLoaded;
|
||||
BotConfig = args.BotConfig;
|
||||
|
||||
CardsFarmer.Paused = BotConfig.Paused;
|
||||
|
||||
if (BotConfig.AcceptConfirmationsPeriod > 0) {
|
||||
TimeSpan delay = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(0.2 * Bots.Count);
|
||||
TimeSpan period = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod);
|
||||
|
||||
if (AcceptConfirmationsTimer == null) {
|
||||
AcceptConfirmationsTimer = new Timer(
|
||||
async e => await AcceptConfirmations(true).ConfigureAwait(false),
|
||||
null,
|
||||
delay, // Delay
|
||||
period // Period
|
||||
);
|
||||
} else {
|
||||
AcceptConfirmationsTimer.Change(delay, period);
|
||||
}
|
||||
} else {
|
||||
AcceptConfirmationsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
AcceptConfirmationsTimer?.Dispose();
|
||||
}
|
||||
|
||||
if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) {
|
||||
TimeSpan delay = TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromMinutes(Bots.Count);
|
||||
TimeSpan period = TimeSpan.FromHours(BotConfig.SendTradePeriod);
|
||||
|
||||
if (SendItemsTimer == null) {
|
||||
SendItemsTimer = new Timer(
|
||||
async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false),
|
||||
null,
|
||||
delay, // Delay
|
||||
period // Period
|
||||
);
|
||||
} else {
|
||||
SendItemsTimer.Change(delay, period);
|
||||
}
|
||||
} else {
|
||||
SendItemsTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
SendItemsTimer?.Dispose();
|
||||
}
|
||||
|
||||
await Initialize().ConfigureAwait(false);
|
||||
} finally {
|
||||
InitializationSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
ArchiWebHandler.Dispose();
|
||||
BotConfig.NewConfigLoaded -= OnNewConfigLoaded;
|
||||
BotConfig.Dispose();
|
||||
CardsFarmer.Dispose();
|
||||
HeartBeatTimer.Dispose();
|
||||
HandledGifts.Dispose();
|
||||
InitializationSemaphore.Dispose();
|
||||
SteamFamilySharingIDs.Dispose();
|
||||
OwnedPackageIDs.Dispose();
|
||||
Trading.Dispose();
|
||||
@@ -380,8 +446,6 @@ namespace ArchiSteamFarm {
|
||||
if (SteamClient.IsConnected) {
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
Events.OnBotShutdown();
|
||||
}
|
||||
|
||||
internal async Task LootIfNeeded() {
|
||||
@@ -533,6 +597,13 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
private void Destroy() {
|
||||
Stop();
|
||||
|
||||
Bot ignored;
|
||||
Bots.TryRemove(BotName, out ignored);
|
||||
}
|
||||
|
||||
private async Task HeartBeat() {
|
||||
if (!IsConnectedAndLoggedOn) {
|
||||
return;
|
||||
|
||||
@@ -28,13 +28,22 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
|
||||
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
|
||||
internal sealed class BotConfig {
|
||||
internal sealed class BotConfig : IDisposable {
|
||||
internal sealed class BotConfigEventArgs : EventArgs {
|
||||
internal readonly BotConfig BotConfig;
|
||||
|
||||
internal BotConfigEventArgs(BotConfig botConfig = null) {
|
||||
BotConfig = botConfig;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum EFarmingOrder : byte {
|
||||
Unordered,
|
||||
AppIDsAscending,
|
||||
@@ -53,9 +62,6 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool Paused = false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool StartOnLaunch = true;
|
||||
|
||||
[JsonProperty]
|
||||
internal string SteamLogin { get; set; }
|
||||
|
||||
@@ -131,6 +137,12 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
|
||||
|
||||
internal event EventHandler<BotConfigEventArgs> NewConfigLoaded;
|
||||
|
||||
private string FilePath;
|
||||
private FileSystemWatcher FileSystemWatcher;
|
||||
private DateTime LastWriteTime = DateTime.MinValue;
|
||||
|
||||
internal static BotConfig Load(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
Logging.LogNullError(nameof(filePath));
|
||||
@@ -155,6 +167,8 @@ namespace ArchiSteamFarm {
|
||||
return null;
|
||||
}
|
||||
|
||||
botConfig.FilePath = filePath;
|
||||
|
||||
// Support encrypted passwords
|
||||
if ((botConfig.PasswordFormat != CryptoHelper.ECryptoMethod.PlainText) && !string.IsNullOrEmpty(botConfig.SteamPassword)) {
|
||||
// In worst case password will result in null, which will have to be corrected by user during runtime
|
||||
@@ -178,5 +192,78 @@ namespace ArchiSteamFarm {
|
||||
|
||||
// This constructor is used only by deserializer
|
||||
private BotConfig() { }
|
||||
|
||||
internal void InitializeWatcher() {
|
||||
if (FileSystemWatcher != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(FilePath)) {
|
||||
Logging.LogNullError(nameof(FilePath));
|
||||
return;
|
||||
}
|
||||
|
||||
string fileDirectory = Path.GetDirectoryName(FilePath);
|
||||
if (string.IsNullOrEmpty(fileDirectory)) {
|
||||
Logging.LogNullError(nameof(fileDirectory));
|
||||
return;
|
||||
}
|
||||
|
||||
string fileName = Path.GetFileName(FilePath);
|
||||
if (string.IsNullOrEmpty(fileName)) {
|
||||
Logging.LogNullError(nameof(fileName));
|
||||
return;
|
||||
}
|
||||
|
||||
FileSystemWatcher = new FileSystemWatcher(fileDirectory, fileName) {
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite
|
||||
};
|
||||
|
||||
FileSystemWatcher.Changed += OnChanged;
|
||||
FileSystemWatcher.Deleted += OnDeleted;
|
||||
FileSystemWatcher.Renamed += OnRenamed;
|
||||
|
||||
FileSystemWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
private async void OnChanged(object sender, FileSystemEventArgs e) {
|
||||
if (NewConfigLoaded == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime lastWriteTime;
|
||||
|
||||
lock (FileSystemWatcher) {
|
||||
lastWriteTime = File.GetLastWriteTime(FilePath);
|
||||
if (LastWriteTime == lastWriteTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
LastWriteTime = lastWriteTime;
|
||||
}
|
||||
|
||||
// It's entirely possible that some process is still accessing our file, allow at least a second before trying to read it
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
// It's also possible that we got some other event in the meantime
|
||||
if (lastWriteTime != LastWriteTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
NewConfigLoaded?.Invoke(this, new BotConfigEventArgs(Load(FilePath)));
|
||||
}
|
||||
private void OnDeleted(object sender, FileSystemEventArgs e) => NewConfigLoaded?.Invoke(this, new BotConfigEventArgs());
|
||||
private void OnRenamed(object sender, RenamedEventArgs e) => NewConfigLoaded?.Invoke(this, new BotConfigEventArgs());
|
||||
|
||||
public void Dispose() {
|
||||
if (FileSystemWatcher == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileSystemWatcher.Changed -= OnChanged;
|
||||
FileSystemWatcher.Deleted -= OnDeleted;
|
||||
FileSystemWatcher.Renamed -= OnRenamed;
|
||||
FileSystemWatcher.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,17 +92,16 @@ namespace ArchiSteamFarm {
|
||||
private readonly Timer IdleFarmingTimer;
|
||||
|
||||
[JsonProperty]
|
||||
internal bool Paused { get; private set; }
|
||||
internal bool Paused { get; set; }
|
||||
|
||||
private bool KeepFarming, NowFarming;
|
||||
|
||||
internal CardsFarmer(Bot bot, bool paused) {
|
||||
internal CardsFarmer(Bot bot) {
|
||||
if (bot == null) {
|
||||
throw new ArgumentNullException(nameof(bot));
|
||||
}
|
||||
|
||||
Bot = bot;
|
||||
Paused = paused;
|
||||
|
||||
if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
|
||||
IdleFarmingTimer = new Timer(
|
||||
@@ -114,17 +113,6 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
CurrentGamesFarming.Dispose();
|
||||
GamesToFarm.Dispose();
|
||||
FarmingSemaphore.Dispose();
|
||||
FarmResetEvent.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
IdleFarmingTimer?.Dispose();
|
||||
}
|
||||
|
||||
internal async Task Pause() {
|
||||
Paused = true;
|
||||
if (NowFarming) {
|
||||
@@ -670,5 +658,16 @@ namespace ArchiSteamFarm {
|
||||
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", games.Select(game => game.AppID)), Bot.BotName);
|
||||
return success;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
CurrentGamesFarming.Dispose();
|
||||
GamesToFarm.Dispose();
|
||||
FarmingSemaphore.Dispose();
|
||||
FarmResetEvent.Dispose();
|
||||
|
||||
// Those are objects that might be null and the check should be in-place
|
||||
IdleFarmingTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SteamKit2;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Events {
|
||||
internal static void OnBotShutdown() {
|
||||
if (Program.ShutdownSequenceInitialized || Program.WCF.IsServerRunning() || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.LogGenericInfo("No bots are running, exiting");
|
||||
Task.Delay(5000).Wait();
|
||||
Program.Shutdown();
|
||||
}
|
||||
|
||||
internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<IPEndPoint> Servers = new ConcurrentHashSet<IPEndPoint>();
|
||||
|
||||
internal event EventHandler ServerListUpdated = delegate { };
|
||||
internal event EventHandler ServerListUpdated;
|
||||
|
||||
public Task<IEnumerable<IPEndPoint>> FetchServerListAsync() => Task.FromResult<IEnumerable<IPEndPoint>>(Servers);
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace ArchiSteamFarm {
|
||||
return Task.Delay(0);
|
||||
}
|
||||
|
||||
ServerListUpdated(this, EventArgs.Empty);
|
||||
ServerListUpdated?.Invoke(this, EventArgs.Empty);
|
||||
return Task.Delay(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,18 +40,18 @@ namespace ArchiSteamFarm {
|
||||
Server // Normal + WCF server
|
||||
}
|
||||
|
||||
internal static readonly WCF WCF = new WCF();
|
||||
|
||||
private static readonly object ConsoleLock = new object();
|
||||
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
|
||||
private static readonly WCF WCF = new WCF();
|
||||
|
||||
internal static bool IsRunningAsService { get; private set; }
|
||||
internal static bool ShutdownSequenceInitialized { get; private set; }
|
||||
internal static EMode Mode { get; private set; } = EMode.Normal;
|
||||
internal static GlobalConfig GlobalConfig { get; private set; }
|
||||
internal static GlobalDatabase GlobalDatabase { get; private set; }
|
||||
internal static WebBrowser WebBrowser { get; private set; }
|
||||
|
||||
private static bool ShutdownSequenceInitialized;
|
||||
|
||||
internal static void Exit(byte exitCode = 0) {
|
||||
Shutdown();
|
||||
Environment.Exit(exitCode);
|
||||
@@ -131,7 +131,7 @@ namespace ArchiSteamFarm {
|
||||
return !string.IsNullOrEmpty(result) ? result.Trim() : null;
|
||||
}
|
||||
|
||||
internal static void Shutdown() {
|
||||
private static void Shutdown() {
|
||||
if (!InitShutdownSequence()) {
|
||||
return;
|
||||
}
|
||||
@@ -340,8 +340,6 @@ namespace ArchiSteamFarm {
|
||||
// Before attempting to connect, initialize our list of CMs
|
||||
Bot.InitializeCMs(GlobalDatabase.CellID, GlobalDatabase.ServerListProvider);
|
||||
|
||||
bool isRunning = false;
|
||||
|
||||
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
|
||||
switch (botName) {
|
||||
case SharedInfo.ASF:
|
||||
@@ -350,19 +348,7 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
Bot bot = new Bot(botName);
|
||||
if ((bot.BotConfig == null) || !bot.BotConfig.Enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bot.BotConfig.StartOnLaunch) {
|
||||
isRunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we got any bots running
|
||||
if (!isRunning) {
|
||||
Events.OnBotShutdown();
|
||||
new Bot(botName).Forget();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,13 +27,12 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal static class Utilities {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
|
||||
internal static void Forget(this Task task) { }
|
||||
internal static void Forget(this object obj) { }
|
||||
|
||||
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
|
||||
if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
|
||||
|
||||
@@ -85,8 +85,6 @@ namespace ArchiSteamFarm {
|
||||
StopServer();
|
||||
}
|
||||
|
||||
internal bool IsServerRunning() => ServiceHost != null;
|
||||
|
||||
internal void StartServer() {
|
||||
if (ServiceHost != null) {
|
||||
return;
|
||||
|
||||
@@ -61,10 +61,6 @@ namespace ConfigGenerator {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Paused { get; set; } = false;
|
||||
|
||||
[Category("\tAdvanced")]
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool StartOnLaunch { get; set; } = true;
|
||||
|
||||
[Category("\t\tCore")]
|
||||
[JsonProperty]
|
||||
public string SteamLogin { get; set; } = null;
|
||||
|
||||
Reference in New Issue
Block a user