diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings
index 97b4c09dd..24d3d074d 100644
--- a/ArchiSteamFarm.sln.DotSettings
+++ b/ArchiSteamFarm.sln.DotSettings
@@ -197,6 +197,8 @@
False
False
True
+ True
+ 1
END_OF_LINE
NEVER
@@ -528,4 +530,8 @@ limitations under the License.
True
True
True
- 8
\ No newline at end of file
+ 8
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj
index 6731b5aff..79d1444bf 100644
--- a/ArchiSteamFarm/ArchiSteamFarm.csproj
+++ b/ArchiSteamFarm/ArchiSteamFarm.csproj
@@ -40,9 +40,17 @@
+
+
+
+
+
+
+
+
diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs
index d10f1457c..7704e6747 100755
--- a/ArchiSteamFarm/Bot.cs
+++ b/ArchiSteamFarm/Bot.cs
@@ -40,7 +40,7 @@ using SteamKit2.Discovery;
using SteamKit2.Unified.Internal;
namespace ArchiSteamFarm {
- internal sealed class Bot : IDisposable {
+ public sealed class Bot : IDisposable {
internal const ushort CallbackSleep = 500; // In miliseconds
internal const ushort MaxMessagePrefixLength = MaxMessageLength - ReservedMessageLength - 2; // 2 for a minimum of 2 characters (escape one and real one)
internal const byte MinPlayingBlockedTTL = 60; // Delay in seconds added when account was occupied during our disconnect, to not disconnect other Steam client session too soon
diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs
index 805a47a1f..dc8f8a2c3 100644
--- a/ArchiSteamFarm/GlobalConfig.cs
+++ b/ArchiSteamFarm/GlobalConfig.cs
@@ -32,7 +32,7 @@ using SteamKit2;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
- internal sealed class GlobalConfig {
+ public sealed class GlobalConfig {
private const bool DefaultAutoRestart = true;
private const string DefaultCommandPrefix = "!";
private const byte DefaultConfirmationsLimiterDelay = 10;
@@ -46,7 +46,6 @@ namespace ArchiSteamFarm {
private const byte DefaultInventoryLimiterDelay = 3;
private const bool DefaultIPC = false;
private const string DefaultIPCPassword = null;
- private const ushort DefaultIPCPort = 1242;
private const byte DefaultLoginLimiterDelay = 10;
private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultMaxTradeHoldDuration = 15;
@@ -63,7 +62,6 @@ namespace ArchiSteamFarm {
private const string DefaultWebProxyUsername = null;
internal static readonly ImmutableHashSet SalesBlacklist = ImmutableHashSet.Create(267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900, 762800, 876740);
- private static readonly ImmutableHashSet DefaultIPCPrefixes = ImmutableHashSet.Create("http://127.0.0.1:" + DefaultIPCPort + "/");
private static readonly ImmutableHashSet DefaultBlacklist = ImmutableHashSet.Create();
private static readonly SemaphoreSlim WriteSemaphore = new SemaphoreSlim(1, 1);
@@ -110,9 +108,6 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal readonly string IPCPassword = DefaultIPCPassword;
- [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace, Required = Required.DisallowNull)]
- internal readonly ImmutableHashSet IPCPrefixes = DefaultIPCPrefixes;
-
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte LoginLimiterDelay = DefaultLoginLimiterDelay;
@@ -336,7 +331,6 @@ namespace ArchiSteamFarm {
public bool ShouldSerializeInventoryLimiterDelay() => ShouldSerializeEverything || (InventoryLimiterDelay != DefaultInventoryLimiterDelay);
public bool ShouldSerializeIPC() => ShouldSerializeEverything || (IPC != DefaultIPC);
public bool ShouldSerializeIPCPassword() => ShouldSerializeEverything || (IPCPassword != DefaultIPCPassword);
- public bool ShouldSerializeIPCPrefixes() => ShouldSerializeEverything || ((IPCPrefixes != DefaultIPCPrefixes) && !IPCPrefixes.SetEquals(DefaultIPCPrefixes));
public bool ShouldSerializeLoginLimiterDelay() => ShouldSerializeEverything || (LoginLimiterDelay != DefaultLoginLimiterDelay);
public bool ShouldSerializeMaxFarmingTime() => ShouldSerializeEverything || (MaxFarmingTime != DefaultMaxFarmingTime);
public bool ShouldSerializeMaxTradeHoldDuration() => ShouldSerializeEverything || (MaxTradeHoldDuration != DefaultMaxTradeHoldDuration);
diff --git a/ArchiSteamFarm/IPC.cs b/ArchiSteamFarm/IPC.cs
deleted file mode 100644
index 6cc0997c8..000000000
--- a/ArchiSteamFarm/IPC.cs
+++ /dev/null
@@ -1,1359 +0,0 @@
-// _ _ _ ____ _ _____
-// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
-// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
-// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
-// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
-//
-// Copyright 2015-2018 Ł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 System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Net;
-using System.Net.WebSockets;
-using System.Reflection;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using ArchiSteamFarm.Localization;
-using Newtonsoft.Json;
-
-namespace ArchiSteamFarm {
- internal static class IPC {
- private const byte FailedAuthorizationsCooldown = 1; // In hours
- private const byte MaxFailedAuthorizationAttempts = 5;
-
- internal static bool IsRunning => IsHandlingRequests || IsListening;
-
- private static readonly ConcurrentDictionary ActiveLogWebSockets = new ConcurrentDictionary();
- private static readonly SemaphoreSlim AuthorizationSemaphore = new SemaphoreSlim(1, 1);
-
- private static readonly HashSet CompressableContentTypes = new HashSet {
- "application/javascript",
- "application/json",
- "text/css",
- "text/html",
- "text/plain"
- };
-
- private static readonly ConcurrentDictionary FailedAuthorizations = new ConcurrentDictionary();
-
- private static readonly Dictionary MimeTypes = new Dictionary(8) {
- { ".css", "text/css" },
- { ".html", "text/html" },
- { ".ico", "image/x-icon" },
- { ".jpg", "image/jpeg" },
- { ".js", "application/javascript" },
- { ".json", "application/json" },
- { ".png", "image/png" },
- { ".txt", "text/plain" }
- };
-
- private static bool IsListening {
- get {
- try {
- return HttpListener?.IsListening == true;
- } catch (ObjectDisposedException) {
- // HttpListener can dispose itself on error
- return false;
- }
- }
- }
-
- private static Timer ClearFailedAuthorizationsTimer;
- private static HistoryTarget HistoryTarget;
- private static HttpListener HttpListener;
- private static bool IsHandlingRequests;
-
- internal static void OnNewHistoryTarget(HistoryTarget historyTarget = null) {
- if (HistoryTarget != null) {
- HistoryTarget.NewHistoryEntry -= OnNewHistoryEntry;
- HistoryTarget = null;
- }
-
- if (historyTarget != null) {
- historyTarget.NewHistoryEntry += OnNewHistoryEntry;
- HistoryTarget = historyTarget;
- }
- }
-
- internal static void Start(IReadOnlyCollection prefixes) {
- if ((prefixes == null) || (prefixes.Count == 0)) {
- ASF.ArchiLogger.LogNullError(nameof(prefixes));
- return;
- }
-
- if (!HttpListener.IsSupported) {
- ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, "!HttpListener.IsSupported"));
- return;
- }
-
- if (IsListening) {
- return;
- }
-
- HttpListener = new HttpListener { IgnoreWriteExceptions = true };
-
- try {
- foreach (string prefix in prefixes) {
- ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCStarting, prefix));
- HttpListener.Prefixes.Add(prefix);
- }
-
- HttpListener.Start();
- } catch (Exception e) {
- // HttpListener can dispose itself on error, so don't keep it around
- HttpListener = null;
- ASF.ArchiLogger.LogGenericException(e);
- return;
- }
-
- if (ClearFailedAuthorizationsTimer == null) {
- ClearFailedAuthorizationsTimer = new Timer(
- e => FailedAuthorizations.Clear(),
- null,
- TimeSpan.FromHours(FailedAuthorizationsCooldown), // Delay
- TimeSpan.FromHours(FailedAuthorizationsCooldown) // Period
- );
- }
-
- Logging.InitHistoryLogger();
- Utilities.InBackground(HandleRequests, true);
- ASF.ArchiLogger.LogGenericInfo(Strings.IPCReady);
- }
-
- internal static void Stop() {
- if (!HttpListener.IsSupported) {
- ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, "!HttpListener.IsSupported"));
- return;
- }
-
- if (!IsListening) {
- return;
- }
-
- if (ClearFailedAuthorizationsTimer != null) {
- ClearFailedAuthorizationsTimer.Dispose();
- ClearFailedAuthorizationsTimer = null;
- }
-
- // We must set HttpListener to null before stopping it, so HandleRequests() knows that exception is expected
- HttpListener httpListener = HttpListener;
- HttpListener = null;
-
- httpListener.Stop();
- }
-
- private static async Task HandleApi(HttpListenerContext context, string[] arguments, byte argumentsIndex) {
- if ((context == null) || (arguments == null) || (argumentsIndex == 0)) {
- ASF.ArchiLogger.LogNullError(nameof(context) + " || " + nameof(arguments) + " || " + nameof(argumentsIndex));
- return false;
- }
-
- if (arguments.Length <= argumentsIndex) {
- return false;
- }
-
- switch (arguments[argumentsIndex]) {
- case "ASF" when context.Request.HttpMethod == HttpMethods.Get:
- return await HandleApiASFGet(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "ASF" when context.Request.HttpMethod == HttpMethods.Post:
- return await HandleApiASFPost(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Bot/" when context.Request.HttpMethod == HttpMethods.Delete:
- return await HandleApiBotDelete(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Bot/" when context.Request.HttpMethod == HttpMethods.Get:
- return await HandleApiBotGet(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Bot/" when context.Request.HttpMethod == HttpMethods.Post:
- return await HandleApiBotPost(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Command/" when context.Request.HttpMethod == HttpMethods.Post:
- return await HandleApiCommandPost(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "GamesToRedeemInBackground/" when context.Request.HttpMethod == HttpMethods.Delete:
- return await HandleApiGamesToRedeemInBackgroundDelete(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "GamesToRedeemInBackground/" when context.Request.HttpMethod == HttpMethods.Get:
- return await HandleApiGamesToRedeemInBackgroundGet(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "GamesToRedeemInBackground/" when context.Request.HttpMethod == HttpMethods.Post:
- return await HandleApiGamesToRedeemInBackgroundPost(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Log" when context.Request.HttpMethod == HttpMethods.Get:
- return await HandleApiLogGet(context, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Structure/" when context.Request.HttpMethod == HttpMethods.Get:
- return await HandleApiStructureGet(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Type/" when context.Request.HttpMethod == HttpMethods.Get:
- return await HandleApiTypeGet(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "WWW/" when arguments.Length > ++argumentsIndex:
- switch (arguments[argumentsIndex]) {
- case "Directory/" when context.Request.HttpMethod == HttpMethods.Get:
- return await HandleApiWWWDirectoryGet(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- case "Send" when context.Request.HttpMethod == HttpMethods.Post:
- return await HandleApiWWWSendPost(context.Request, context.Response, arguments, ++argumentsIndex).ConfigureAwait(false);
- default:
- return false;
- }
- default:
- return false;
- }
- }
-
- private static async Task HandleApiASFGet(HttpListenerRequest request, HttpListenerResponse response, string[] arguments, byte argumentsIndex) {
- if ((request == null) || (response == null) || (arguments == null) || (argumentsIndex == 0)) {
- ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(arguments) + " || " + nameof(argumentsIndex));
- return false;
- }
-
- uint memoryUsage = (uint) GC.GetTotalMemory(false) / 1024;
-
- DateTime processStartTime;
-
- using (Process process = Process.GetCurrentProcess()) {
- processStartTime = process.StartTime;
- }
-
- ASFResponse asfResponse = new ASFResponse(SharedInfo.BuildInfo.Variant, Program.GlobalConfig, memoryUsage, processStartTime, SharedInfo.Version);
-
- await ResponseJsonObject(request, response, new GenericResponse(true, "OK", asfResponse)).ConfigureAwait(false);
- return true;
- }
-
- private static async Task HandleApiASFPost(HttpListenerRequest request, HttpListenerResponse response, string[] arguments, byte argumentsIndex) {
- if ((request == null) || (response == null) || (arguments == null) || (argumentsIndex == 0)) {
- ASF.ArchiLogger.LogNullError(nameof(request) + " || " + nameof(response) + " || " + nameof(arguments) + " || " + nameof(argumentsIndex));
- return false;
- }
-
- const string requiredContentType = "application/json";
-
- if (string.IsNullOrEmpty(request.ContentType) || ((request.ContentType != requiredContentType) && !request.ContentType.StartsWith(requiredContentType + ";", StringComparison.Ordinal))) {
- await ResponseJsonObject(request, response, new GenericResponse