Rewrite IPC from HttpListener to KestrelHttpServer (#898)

* Debug tests

* Update

* Add support for IPCPassword

* Misc

* Misc

* Update

* Misc

* Cut dependencies to bare minimum

* Update

* Update

* Update

* Update

* Add support for websockets

* Cleanup and preparation for merge

* Add missing mapping of / -> index.html

* Add support for custom path + misc

* Misc

* Declare latest compatibility version

* Fix harmless error on /Api/Log websocket disconnect
This commit is contained in:
Łukasz Domeradzki 2018-09-08 00:05:23 +02:00 committed by GitHub
parent 5b667476e4
commit e18c8ffa55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1550 additions and 1400 deletions

View file

@ -197,6 +197,8 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INVOCATION_PARENS_ARRANGEMENT/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_INITIALIZER_ELEMENTS_ON_LINE/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
@ -528,4 +530,8 @@ limitations under the License.</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsParsFormattingSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsWrapperSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Int64 x:Key="/Default/Environment/UnitTesting/ParallelProcessesCount/@EntryValue">8</s:Int64></wpf:ResourceDictionary>
<s:Int64 x:Key="/Default/Environment/UnitTesting/ParallelProcessesCount/@EntryValue">8</s:Int64>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Archi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=cryptkey/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Domeradzki/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=_0141ukasz/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -40,9 +40,17 @@
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="2.0.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.8.7" />
<PackageReference Include="Humanizer" Version="2.4.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="2.2.0-preview1-35029" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NLog" Version="4.5.9" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.6.0" />
<PackageReference Include="protobuf-net" Version="3.0.0-alpha.3" />
</ItemGroup>

View file

@ -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

View file

@ -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<uint> SalesBlacklist = ImmutableHashSet.Create<uint>(267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900, 762800, 876740);
private static readonly ImmutableHashSet<string> DefaultIPCPrefixes = ImmutableHashSet.Create("http://127.0.0.1:" + DefaultIPCPort + "/");
private static readonly ImmutableHashSet<uint> DefaultBlacklist = ImmutableHashSet.Create<uint>();
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<string> 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);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,112 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.IO;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Controllers.Api;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NLog.Web;
namespace ArchiSteamFarm.IPC {
internal static class ArchiKestrel {
private const string ConfigurationFile = nameof(IPC) + ".config";
internal static HistoryTarget HistoryTarget { get; private set; }
private static IWebHost KestrelWebHost;
internal static void OnNewHistoryTarget(HistoryTarget historyTarget = null) {
if (HistoryTarget != null) {
HistoryTarget.NewHistoryEntry -= LogController.OnNewHistoryEntry;
HistoryTarget = null;
}
if (historyTarget != null) {
historyTarget.NewHistoryEntry += LogController.OnNewHistoryEntry;
HistoryTarget = historyTarget;
}
}
internal static async Task Start() {
if (KestrelWebHost != null) {
return;
}
// The order of dependency injection matters, pay attention to it
IWebHostBuilder builder = new WebHostBuilder();
// Set default directories
builder.UseContentRoot(SharedInfo.HomeDirectory);
builder.UseWebRoot(SharedInfo.WebsiteDirectory);
// Check if custom config is available
string absoluteConfigDirectory = Path.Combine(Directory.GetCurrentDirectory(), SharedInfo.ConfigDirectory);
if (File.Exists(Path.Combine(absoluteConfigDirectory, ConfigurationFile))) {
// Set up custom config to be used
builder.UseConfiguration(new ConfigurationBuilder().SetBasePath(absoluteConfigDirectory).AddJsonFile(ConfigurationFile, false, true).Build());
// Use custom config for Kestrel and Logging configuration
builder.UseKestrel((builderContext, options) => options.Configure(builderContext.Configuration.GetSection("Kestrel")));
builder.ConfigureLogging((hostingContext, logging) => logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")));
} else {
// Use ASF defaults for Kestrel and Logging
builder.UseKestrel(options => options.ListenLocalhost(1242));
builder.ConfigureLogging(logging => logging.SetMinimumLevel(Debugging.IsUserDebugging ? LogLevel.Trace : LogLevel.Warning));
}
// Enable NLog integration for logging
builder.UseNLog();
// Specify Startup class for IPC
builder.UseStartup<Startup>();
// Init history logger for /Api/Log usage
Logging.InitHistoryLogger();
// Start the server
IWebHost kestrelWebHost = builder.Build();
try {
await kestrelWebHost.StartAsync().ConfigureAwait(false);
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
kestrelWebHost.Dispose();
return;
}
KestrelWebHost = kestrelWebHost;
}
internal static async Task Stop() {
if (KestrelWebHost == null) {
return;
}
await KestrelWebHost.StopAsync().ConfigureAwait(false);
KestrelWebHost.Dispose();
KestrelWebHost = null;
}
}
}

View file

@ -0,0 +1,74 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Requests;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/ASF")]
public sealed class ASFController : ControllerBase {
[HttpGet]
public ActionResult<GenericResponse<ASFResponse>> Get() {
uint memoryUsage = (uint) GC.GetTotalMemory(false) / 1024;
DateTime processStartTime;
using (Process process = Process.GetCurrentProcess()) {
processStartTime = process.StartTime;
}
ASFResponse response = new ASFResponse(SharedInfo.BuildInfo.Variant, Program.GlobalConfig, memoryUsage, processStartTime, SharedInfo.Version);
return Ok(new GenericResponse<ASFResponse>(response));
}
[HttpPost]
public async Task<ActionResult<GenericResponse>> Post([FromBody] ASFRequest request) {
if (request == null) {
ASF.ArchiLogger.LogNullError(nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request))));
}
if (request.KeepSensitiveDetails) {
if (string.IsNullOrEmpty(request.GlobalConfig.WebProxyPassword) && !string.IsNullOrEmpty(Program.GlobalConfig.WebProxyPassword)) {
request.GlobalConfig.WebProxyPassword = Program.GlobalConfig.WebProxyPassword;
}
}
request.GlobalConfig.ShouldSerializeEverything = false;
string filePath = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName);
if (!await GlobalConfig.Write(filePath, request.GlobalConfig).ConfigureAwait(false)) {
return BadRequest(new GenericResponse(false, Strings.WarningFailed));
}
return Ok(new GenericResponse(true));
}
}
}

View file

@ -0,0 +1,117 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Requests;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/Bot")]
public sealed class BotController : ControllerBase {
[HttpDelete("{botNames:required}")]
public async Task<ActionResult<GenericResponse>> Delete(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
}
IEnumerable<Task<bool>> tasks = bots.Select(bot => bot.DeleteAllRelatedFiles());
ICollection<bool> results;
switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<bool>(bots.Count);
foreach (Task<bool> task in tasks) {
results.Add(await task.ConfigureAwait(false));
}
break;
default:
results = await Task.WhenAll(tasks).ConfigureAwait(false);
break;
}
if (results.Any(result => !result)) {
return BadRequest(new GenericResponse(false, Strings.WarningFailed));
}
return Ok(new GenericResponse(true));
}
[HttpGet("{botNames:required}")]
public ActionResult<GenericResponse<HashSet<Bot>>> Get(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse<HashSet<Bot>>(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse<HashSet<Bot>>(false, string.Format(Strings.BotNotFound, botNames)));
}
return Ok(new GenericResponse<HashSet<Bot>>(bots));
}
[HttpPost("{botName:required}")]
public async Task<ActionResult<GenericResponse>> Post(string botName, [FromBody] BotRequest request) {
if (string.IsNullOrEmpty(botName) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botName) + " || " + nameof(request))));
}
if (request.KeepSensitiveDetails && Bot.Bots.TryGetValue(botName, out Bot bot)) {
if (string.IsNullOrEmpty(request.BotConfig.SteamLogin) && !string.IsNullOrEmpty(bot.BotConfig.SteamLogin)) {
request.BotConfig.SteamLogin = bot.BotConfig.SteamLogin;
}
if (string.IsNullOrEmpty(request.BotConfig.SteamParentalPIN) && !string.IsNullOrEmpty(bot.BotConfig.SteamParentalPIN)) {
request.BotConfig.SteamParentalPIN = bot.BotConfig.SteamParentalPIN;
}
if (string.IsNullOrEmpty(request.BotConfig.SteamPassword) && !string.IsNullOrEmpty(bot.BotConfig.SteamPassword)) {
request.BotConfig.SteamPassword = bot.BotConfig.SteamPassword;
}
}
request.BotConfig.ShouldSerializeEverything = false;
string filePath = Path.Combine(SharedInfo.ConfigDirectory, botName + SharedInfo.ConfigExtension);
if (!await BotConfig.Write(filePath, request.BotConfig).ConfigureAwait(false)) {
return BadRequest(new GenericResponse(false, Strings.WarningFailed));
}
return Ok(new GenericResponse(true));
}
}
}

View file

@ -0,0 +1,57 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/Command")]
public sealed class CommandController : ControllerBase {
[HttpPost("{command:required}")]
public async Task<ActionResult<GenericResponse<string>>> Post(string command) {
if (string.IsNullOrEmpty(command)) {
ASF.ArchiLogger.LogNullError(nameof(command));
return BadRequest(new GenericResponse<string>(false, string.Format(Strings.ErrorIsEmpty, nameof(command))));
}
if (Program.GlobalConfig.SteamOwnerID == 0) {
return BadRequest(new GenericResponse<string>(false, string.Format(Strings.ErrorIsInvalid, nameof(Program.GlobalConfig.SteamOwnerID))));
}
Bot targetBot = Bot.Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value).FirstOrDefault();
if (targetBot == null) {
return BadRequest(new GenericResponse<string>(false, Strings.ErrorNoBotsDefined));
}
if (!string.IsNullOrEmpty(Program.GlobalConfig.CommandPrefix) && !command.StartsWith(Program.GlobalConfig.CommandPrefix, StringComparison.Ordinal)) {
command = Program.GlobalConfig.CommandPrefix + command;
}
string content = await targetBot.Response(Program.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false);
return Ok(new GenericResponse<string>(content));
}
}
}

View file

@ -0,0 +1,126 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Requests;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/GamesToRedeemInBackgroundController")]
public sealed class GamesToRedeemInBackgroundController : ControllerBase {
[HttpDelete("{botNames:required}")]
public async Task<ActionResult<GenericResponse>> Delete(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
}
IEnumerable<Task<bool>> tasks = bots.Select(bot => Task.Run(bot.DeleteRedeemedKeysFiles));
ICollection<bool> results;
switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<bool>(bots.Count);
foreach (Task<bool> task in tasks) {
results.Add(await task.ConfigureAwait(false));
}
break;
default:
results = await Task.WhenAll(tasks).ConfigureAwait(false);
break;
}
if (results.Any(result => !result)) {
return BadRequest(new GenericResponse(false, Strings.WarningFailed));
}
return Ok(new GenericResponse(true));
}
[HttpGet("{botNames:required}")]
public async Task<ActionResult<GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>>> Get(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>(false, string.Format(Strings.BotNotFound, botNames)));
}
IEnumerable<(string BotName, Task<(Dictionary<string, string> UnusedKeys, Dictionary<string, string> UsedKeys)> Task)> tasks = bots.Select(bot => (bot.BotName, bot.GetUsedAndUnusedKeys()));
ICollection<(string BotName, (Dictionary<string, string> UnusedKeys, Dictionary<string, string> UsedKeys))> results;
switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<(string BotName, (Dictionary<string, string> UnusedKeys, Dictionary<string, string> UsedKeys))>(bots.Count);
foreach ((string botName, Task<(Dictionary<string, string> UnusedKeys, Dictionary<string, string> UsedKeys)> task) in tasks) {
results.Add((botName, await task.ConfigureAwait(false)));
}
break;
default:
results = await Task.WhenAll(tasks.Select(async task => (task.BotName, await task.Task.ConfigureAwait(false)))).ConfigureAwait(false);
break;
}
Dictionary<string, GamesToRedeemInBackgroundResponse> result = new Dictionary<string, GamesToRedeemInBackgroundResponse>();
foreach ((string botName, (Dictionary<string, string> UnusedKeys, Dictionary<string, string> UsedKeys) taskResult) in results) {
result[botName] = new GamesToRedeemInBackgroundResponse(taskResult.UnusedKeys, taskResult.UsedKeys);
}
return Ok(new GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>(result));
}
[HttpPost("{botName:required}")]
public async Task<ActionResult<GenericResponse<OrderedDictionary>>> Post(string botName, [FromBody] GamesToRedeemInBackgroundRequest request) {
if (string.IsNullOrEmpty(botName) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(request));
return BadRequest(new GenericResponse<OrderedDictionary>(false, string.Format(Strings.ErrorIsEmpty, nameof(botName) + " || " + nameof(request))));
}
if (request.GamesToRedeemInBackground.Count == 0) {
return BadRequest(new GenericResponse<OrderedDictionary>(false, string.Format(Strings.ErrorIsEmpty, nameof(request.GamesToRedeemInBackground))));
}
if (!Bot.Bots.TryGetValue(botName, out Bot bot)) {
return BadRequest(new GenericResponse<OrderedDictionary>(false, string.Format(Strings.BotNotFound, botName)));
}
await bot.ValidateAndAddGamesToRedeemInBackground(request.GamesToRedeemInBackground).ConfigureAwait(false);
return Ok(new GenericResponse<OrderedDictionary>(request.GamesToRedeemInBackground));
}
}
}

View file

@ -0,0 +1,140 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Collections.Concurrent;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/Log")]
public sealed class LogController : ControllerBase {
private static readonly ConcurrentDictionary<WebSocket, SemaphoreSlim> ActiveLogWebSockets = new ConcurrentDictionary<WebSocket, SemaphoreSlim>();
[HttpGet]
public async Task<ActionResult> Get() {
if (!HttpContext.WebSockets.IsWebSocketRequest) {
return BadRequest(new GenericResponse(false, string.Format(Strings.WarningFailedWithError, nameof(HttpContext.WebSockets.IsWebSocketRequest) + ": " + HttpContext.WebSockets.IsWebSocketRequest)));
}
// From now on we can return only EmptyResult as the response stream is already being used by existing websocket connection
try {
using (WebSocket webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false)) {
SemaphoreSlim sendSemaphore = new SemaphoreSlim(1, 1);
if (!ActiveLogWebSockets.TryAdd(webSocket, sendSemaphore)) {
sendSemaphore.Dispose();
return new EmptyResult();
}
try {
// Push initial history if available
if (ArchiKestrel.HistoryTarget != null) {
// ReSharper disable once AccessToDisposedClosure - we're waiting for completion with Task.WhenAll(), we're not going to exit using block
await Task.WhenAll(ArchiKestrel.HistoryTarget.ArchivedMessages.Select(archivedMessage => PostLoggedMessageUpdate(webSocket, sendSemaphore, archivedMessage))).ConfigureAwait(false);
}
while (webSocket.State == WebSocketState.Open) {
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new byte[0], CancellationToken.None).ConfigureAwait(false);
if (result.MessageType != WebSocketMessageType.Close) {
await webSocket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "You're not supposed to be sending any message but Close!", CancellationToken.None).ConfigureAwait(false);
break;
}
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
break;
}
} finally {
if (ActiveLogWebSockets.TryRemove(webSocket, out SemaphoreSlim closedSemaphore)) {
await closedSemaphore.WaitAsync().ConfigureAwait(false); // Ensure that our semaphore is truly closed by now
closedSemaphore.Dispose();
}
}
}
} catch (WebSocketException e) {
ASF.ArchiLogger.LogGenericDebuggingException(e);
}
return new EmptyResult();
}
internal static async void OnNewHistoryEntry(object sender, HistoryTarget.NewHistoryEntryArgs newHistoryEntryArgs) {
if ((sender == null) || (newHistoryEntryArgs == null)) {
ASF.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(newHistoryEntryArgs));
return;
}
if (ActiveLogWebSockets.Count == 0) {
return;
}
string json = JsonConvert.SerializeObject(new GenericResponse<string>(newHistoryEntryArgs.Message));
await Task.WhenAll(ActiveLogWebSockets.Where(kv => kv.Key.State == WebSocketState.Open).Select(kv => PostLoggedJsonUpdate(kv.Key, kv.Value, json))).ConfigureAwait(false);
}
private static async Task PostLoggedJsonUpdate(WebSocket webSocket, SemaphoreSlim sendSemaphore, string json) {
if ((webSocket == null) || (sendSemaphore == null) || string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(webSocket) + " || " + nameof(sendSemaphore) + " || " + nameof(json));
return;
}
if (webSocket.State != WebSocketState.Open) {
return;
}
await sendSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (webSocket.State != WebSocketState.Open) {
return;
}
await webSocket.SendAsync(Encoding.UTF8.GetBytes(json), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
} catch (WebSocketException e) {
ASF.ArchiLogger.LogGenericDebuggingException(e);
} finally {
sendSemaphore.Release();
}
}
private static async Task PostLoggedMessageUpdate(WebSocket webSocket, SemaphoreSlim sendSemaphore, string loggedMessage) {
if ((webSocket == null) || (sendSemaphore == null) || string.IsNullOrEmpty(loggedMessage)) {
ASF.ArchiLogger.LogNullError(nameof(webSocket) + " || " + nameof(sendSemaphore) + " || " + nameof(loggedMessage));
return;
}
if (webSocket.State != WebSocketState.Open) {
return;
}
string response = JsonConvert.SerializeObject(new GenericResponse<string>(loggedMessage));
await PostLoggedJsonUpdate(webSocket, sendSemaphore, response).ConfigureAwait(false);
}
}
}

View file

@ -0,0 +1,55 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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 ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/Structure")]
public sealed class StructureController : ControllerBase {
[HttpGet("{structure:required}")]
public ActionResult<GenericResponse<object>> Get(string structure) {
if (string.IsNullOrEmpty(structure)) {
ASF.ArchiLogger.LogNullError(nameof(structure));
return BadRequest(new GenericResponse<object>(false, string.Format(Strings.ErrorIsEmpty, nameof(structure))));
}
Type targetType = Utilities.ParseType(structure);
if (targetType == null) {
return BadRequest(new GenericResponse<object>(false, string.Format(Strings.ErrorIsInvalid, structure)));
}
object obj;
try {
obj = Activator.CreateInstance(targetType, true);
} catch (Exception e) {
return BadRequest(new GenericResponse<object>(false, string.Format(Strings.ErrorParsingObject, nameof(targetType)) + Environment.NewLine + e));
}
return Ok(new GenericResponse<object>(obj));
}
}
}

View file

@ -0,0 +1,85 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Generic;
using System.Linq;
using System.Reflection;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/Type")]
public sealed class TypeController : ControllerBase {
[HttpGet("{type:required}")]
public ActionResult<GenericResponse<TypeResponse>> Get(string type) {
if (string.IsNullOrEmpty(type)) {
ASF.ArchiLogger.LogNullError(nameof(type));
return BadRequest(new GenericResponse<TypeResponse>(false, string.Format(Strings.ErrorIsEmpty, nameof(type))));
}
Type targetType = Utilities.ParseType(type);
if (targetType == null) {
return BadRequest(new GenericResponse<object>(false, string.Format(Strings.ErrorIsInvalid, type)));
}
string baseType = targetType.BaseType?.GetUnifiedName();
HashSet<string> customAttributes = targetType.CustomAttributes.Select(attribute => attribute.AttributeType.GetUnifiedName()).ToHashSet();
string underlyingType = null;
Dictionary<string, string> body = new Dictionary<string, string>();
if (targetType.IsClass) {
foreach (FieldInfo field in targetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(field => !field.IsPrivate)) {
JsonPropertyAttribute jsonProperty = field.GetCustomAttribute<JsonPropertyAttribute>();
if (jsonProperty != null) {
body[jsonProperty.PropertyName ?? field.Name] = field.FieldType.GetUnifiedName();
}
}
foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(property => property.CanRead && !property.GetMethod.IsPrivate)) {
JsonPropertyAttribute jsonProperty = property.GetCustomAttribute<JsonPropertyAttribute>();
if (jsonProperty != null) {
body[jsonProperty.PropertyName ?? property.Name] = property.PropertyType.GetUnifiedName();
}
}
} else if (targetType.IsEnum) {
Type enumType = Enum.GetUnderlyingType(targetType);
underlyingType = enumType.GetUnifiedName();
foreach (object value in Enum.GetValues(targetType)) {
body[value.ToString()] = Convert.ChangeType(value, enumType).ToString();
}
}
TypeResponse.TypeProperties properties = new TypeResponse.TypeProperties(baseType, customAttributes.Count > 0 ? customAttributes : null, underlyingType);
TypeResponse response = new TypeResponse(body, properties);
return Ok(new GenericResponse<TypeResponse>(response));
}
}
}

View file

@ -0,0 +1,79 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Requests;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Localization;
using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController]
[Route("Api/WWW")]
public sealed class WWWController : ControllerBase {
[HttpGet("Directory/{directory:required}")]
public ActionResult<GenericResponse<HashSet<string>>> DirectoryGet(string directory) {
if (string.IsNullOrEmpty(directory)) {
ASF.ArchiLogger.LogNullError(nameof(directory));
return BadRequest(new GenericResponse<HashSet<string>>(false, string.Format(Strings.ErrorIsEmpty, nameof(directory))));
}
string directoryPath = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.WebsiteDirectory, directory);
if (!Directory.Exists(directoryPath)) {
return BadRequest(new GenericResponse<HashSet<string>>(false, string.Format(Strings.ErrorIsInvalid, directory)));
}
string[] files;
try {
files = Directory.GetFiles(directoryPath);
} catch (Exception e) {
return BadRequest(new GenericResponse<HashSet<string>>(false, string.Format(Strings.ErrorParsingObject, nameof(files)) + Environment.NewLine + e));
}
HashSet<string> result = files.Select(Path.GetFileName).ToHashSet();
return Ok(new GenericResponse<HashSet<string>>(result));
}
[HttpPost("Send")]
public async Task<ActionResult<GenericResponse<string>>> SendPost([FromBody] WWWSendRequest request) {
if (request == null) {
ASF.ArchiLogger.LogNullError(nameof(request));
return BadRequest(new GenericResponse<string>(false, string.Format(Strings.ErrorIsEmpty, nameof(request))));
}
if (string.IsNullOrEmpty(request.URL) || !Uri.TryCreate(request.URL, UriKind.Absolute, out Uri uri) || !uri.Scheme.Equals(Uri.UriSchemeHttps)) {
return BadRequest(new GenericResponse<string>(false, string.Format(Strings.ErrorIsInvalid, nameof(request.URL))));
}
WebBrowser.HtmlDocumentResponse urlResponse = await Program.WebBrowser.UrlGetToHtmlDocument(request.URL).ConfigureAwait(false);
if (urlResponse?.Content == null) {
return BadRequest(new GenericResponse<string>(false, string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
}
return Ok(new GenericResponse<string>(urlResponse.Content.DocumentNode.InnerHtml));
}
}
}

View file

@ -0,0 +1,114 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace ArchiSteamFarm.IPC.Middleware {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class ApiAuthenticationMiddleware {
private const byte FailedAuthorizationsCooldownInHours = 1;
private const byte MaxFailedAuthorizationAttempts = 5;
private static readonly SemaphoreSlim AuthorizationSemaphore = new SemaphoreSlim(1, 1);
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private static readonly Timer ClearFailedAuthorizationsTimer = new Timer(
e => FailedAuthorizations.Clear(),
null,
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours), // Delay
TimeSpan.FromHours(FailedAuthorizationsCooldownInHours) // Period
);
private static readonly ConcurrentDictionary<IPAddress, byte> FailedAuthorizations = new ConcurrentDictionary<IPAddress, byte>();
private readonly RequestDelegate Next;
public ApiAuthenticationMiddleware(RequestDelegate next) => Next = next ?? throw new ArgumentNullException(nameof(next));
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public async Task InvokeAsync(HttpContext context) {
if (context == null) {
ASF.ArchiLogger.LogNullError(nameof(context));
return;
}
HttpStatusCode authenticationStatus = await GetAuthenticationStatus(context).ConfigureAwait(false);
if (authenticationStatus != HttpStatusCode.OK) {
await context.Response.Generate(authenticationStatus).ConfigureAwait(false);
return;
}
await Next(context).ConfigureAwait(false);
}
private static async Task<HttpStatusCode> GetAuthenticationStatus(HttpContext context) {
if (context == null) {
ASF.ArchiLogger.LogNullError(nameof(context));
return HttpStatusCode.InternalServerError;
}
if (string.IsNullOrEmpty(Program.GlobalConfig.IPCPassword)) {
return HttpStatusCode.OK;
}
IPAddress clientIP = context.Connection.RemoteIpAddress;
if (FailedAuthorizations.TryGetValue(clientIP, out byte attempts)) {
if (attempts >= MaxFailedAuthorizationAttempts) {
return HttpStatusCode.Forbidden;
}
}
if (!context.Request.Headers.TryGetValue("Authentication", out StringValues passwords) && !context.Request.Query.TryGetValue("password", out passwords)) {
return HttpStatusCode.Unauthorized;
}
bool authorized = passwords.First() == Program.GlobalConfig.IPCPassword;
await AuthorizationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (FailedAuthorizations.TryGetValue(clientIP, out attempts)) {
if (attempts >= MaxFailedAuthorizationAttempts) {
return HttpStatusCode.Forbidden;
}
}
if (!authorized) {
FailedAuthorizations[clientIP] = FailedAuthorizations.TryGetValue(clientIP, out attempts) ? ++attempts : (byte) 1;
}
} finally {
AuthorizationSemaphore.Release();
}
return authorized ? HttpStatusCode.OK : HttpStatusCode.Unauthorized;
}
}
}

View file

@ -0,0 +1,34 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class ASFRequest : SensitiveDetailsRequest {
[JsonProperty(Required = Required.Always)]
internal readonly GlobalConfig GlobalConfig;
// Deserialized from JSON
private ASFRequest() { }
}
}

View file

@ -0,0 +1,34 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class BotRequest : SensitiveDetailsRequest {
[JsonProperty(Required = Required.Always)]
internal readonly BotConfig BotConfig;
// Deserialized from JSON
private BotRequest() { }
}
}

View file

@ -0,0 +1,35 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class GamesToRedeemInBackgroundRequest {
[JsonProperty(Required = Required.Always)]
internal readonly OrderedDictionary GamesToRedeemInBackground;
// Deserialized from JSON
private GamesToRedeemInBackgroundRequest() { }
}
}

View file

@ -0,0 +1,29 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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 Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests {
public abstract class SensitiveDetailsRequest {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool KeepSensitiveDetails = true;
}
}

View file

@ -0,0 +1,34 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class WWWSendRequest {
[JsonProperty(Required = Required.Always)]
internal readonly string URL;
// Deserialized from JSON
private WWWSendRequest() { }
}
}

View file

@ -0,0 +1,54 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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 Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses {
public sealed class ASFResponse {
[JsonProperty]
private readonly string BuildVariant;
[JsonProperty]
private readonly GlobalConfig GlobalConfig;
[JsonProperty]
private readonly uint MemoryUsage;
[JsonProperty]
private readonly DateTime ProcessStartTime;
[JsonProperty]
private readonly Version Version;
internal ASFResponse(string buildVariant, GlobalConfig globalConfig, uint memoryUsage, DateTime processStartTime, Version version) {
if (string.IsNullOrEmpty(buildVariant) || (globalConfig == null) || (memoryUsage == 0) || (processStartTime == DateTime.MinValue) || (version == null)) {
throw new ArgumentNullException(nameof(buildVariant) + " || " + nameof(globalConfig) + " || " + nameof(memoryUsage) + " || " + nameof(processStartTime) + " || " + nameof(version));
}
BuildVariant = buildVariant;
GlobalConfig = globalConfig;
MemoryUsage = memoryUsage;
ProcessStartTime = processStartTime;
Version = version;
}
}
}

View file

@ -0,0 +1,38 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Collections.Generic;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses {
public sealed class GamesToRedeemInBackgroundResponse {
[JsonProperty]
private readonly Dictionary<string, string> UnusedKeys;
[JsonProperty]
private readonly Dictionary<string, string> UsedKeys;
internal GamesToRedeemInBackgroundResponse(Dictionary<string, string> unusedKeys = null, Dictionary<string, string> usedKeys = null) {
UnusedKeys = unusedKeys;
UsedKeys = usedKeys;
}
}
}

View file

@ -0,0 +1,60 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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 Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses {
public sealed class GenericResponse<T> : GenericResponse where T : class {
[JsonProperty]
private readonly T Result;
internal GenericResponse(T result) : base(true, "OK") => Result = result ?? throw new ArgumentNullException(nameof(result));
internal GenericResponse(bool success, string message) : base(success, message) { }
}
public class GenericResponse {
[JsonProperty]
private readonly string Message;
[JsonProperty]
private readonly bool Success;
internal GenericResponse(bool success) {
if (!success) {
// Returning failed generic response without a message should never happen
throw new ArgumentException(nameof(success));
}
Success = true;
Message = "OK";
}
internal GenericResponse(bool success, string message) {
if (string.IsNullOrEmpty(message)) {
throw new ArgumentNullException(nameof(message));
}
Success = success;
Message = message;
}
}
}

View file

@ -0,0 +1,60 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Generic;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses {
public sealed class TypeResponse {
[JsonProperty]
private readonly Dictionary<string, string> Body;
[JsonProperty]
private readonly TypeProperties Properties;
internal TypeResponse(Dictionary<string, string> body, TypeProperties properties) {
if ((body == null) || (properties == null)) {
throw new ArgumentNullException(nameof(body) + " || " + nameof(properties));
}
Body = body;
Properties = properties;
}
internal sealed class TypeProperties {
[JsonProperty]
private readonly string BaseType;
[JsonProperty]
private readonly HashSet<string> CustomAttributes;
[JsonProperty]
private readonly string UnderlyingType;
internal TypeProperties(string baseType = null, HashSet<string> customAttributes = null, string underlyingType = null) {
BaseType = baseType;
CustomAttributes = customAttributes;
UnderlyingType = underlyingType;
}
}
}
}

View file

@ -0,0 +1,108 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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 ArchiSteamFarm.IPC.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace ArchiSteamFarm.IPC {
internal sealed class Startup {
private readonly IConfiguration Configuration;
public Startup(IConfiguration configuration) => Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
if ((app == null) || (env == null)) {
ASF.ArchiLogger.LogNullError(nameof(app) + " || " + nameof(env));
return;
}
// The order of dependency injection matters, pay attention to it
// Add workaround for missing PathBase feature, https://github.com/aspnet/Hosting/issues/1120
PathString pathBase = Configuration.GetSection("Kestrel").GetValue<PathString>("PathBase");
if (!string.IsNullOrEmpty(pathBase)) {
app.UsePathBase(pathBase);
}
// Add support for response compression
app.UseResponseCompression();
if (!string.IsNullOrEmpty(Program.GlobalConfig.IPCPassword)) {
// We need ApiAuthenticationMiddleware for IPCPassword
app.UseWhen(context => context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseMiddleware<ApiAuthenticationMiddleware>());
}
// We need WebSockets support for /Api/Log
app.UseWebSockets();
// We need static files support for IPC GUI
app.UseDefaultFiles();
app.UseStaticFiles();
// We need MVC for /Api
app.UseMvcWithDefaultRoute();
}
public void ConfigureServices(IServiceCollection services) {
if (services == null) {
ASF.ArchiLogger.LogNullError(nameof(services));
return;
}
// The order of dependency injection matters, pay attention to it
// Add support for response compression
services.AddResponseCompression();
// We need MVC for /Api, but we're going to use only a small subset of all available features
IMvcCoreBuilder mvc = services.AddMvcCore();
// Use latest compatibility version for MVC
mvc.SetCompatibilityVersion(CompatibilityVersion.Latest);
// Add standard formatters that can be used for serializing/deserializing requests/responses, they're already available in the core
mvc.AddFormatterMappings();
// Add JSON formatters that will be used as default ones if no specific formatters are asked for
mvc.AddJsonFormatters();
// Fix default contract resolver to use original names and not a camel case
// Also add debugging aid while we're at it
mvc.AddJsonOptions(
options => {
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
if (Debugging.IsUserDebugging) {
options.SerializerSettings.Formatting = Formatting.Indented;
}
}
);
}
}
}

View file

@ -0,0 +1,72 @@
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
//
// 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.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace ArchiSteamFarm.IPC {
internal static class Utilities {
internal static async Task Generate(this HttpResponse httpResponse, HttpStatusCode statusCode) {
if (httpResponse == null) {
ASF.ArchiLogger.LogNullError(nameof(httpResponse));
return;
}
ushort statusCodeNumber = (ushort) statusCode;
httpResponse.StatusCode = statusCodeNumber;
await httpResponse.WriteAsync(statusCodeNumber + " - " + statusCode).ConfigureAwait(false);
}
internal static string GetUnifiedName(this Type type) {
if (type == null) {
ASF.ArchiLogger.LogNullError(nameof(type));
return null;
}
return type.GenericTypeArguments.Length == 0 ? type.FullName : type.Namespace + "." + type.Name + string.Join("", type.GenericTypeArguments.Select(innerType => '[' + innerType.GetUnifiedName() + ']'));
}
internal static Type ParseType(string typeText) {
if (string.IsNullOrEmpty(typeText)) {
ASF.ArchiLogger.LogNullError(nameof(typeText));
return null;
}
Type targetType = Type.GetType(typeText);
if (targetType != null) {
return targetType;
}
// We can try one more time by trying to smartly guess the assembly name from the namespace, this will work for custom libraries like SteamKit2
int index = typeText.IndexOf('.');
if ((index <= 0) || (index >= typeText.Length - 1)) {
return null;
}
return Type.GetType(typeText + "," + typeText.Substring(0, index));
}
}
}

View file

@ -20,6 +20,7 @@
// limitations under the License.
using System.Linq;
using ArchiSteamFarm.IPC;
using NLog;
using NLog.Config;
using NLog.Targets;
@ -99,7 +100,7 @@ namespace ArchiSteamFarm {
LogManager.ReconfigExistingLoggers();
}
IPC.OnNewHistoryTarget(historyTarget);
ArchiKestrel.OnNewHistoryTarget(historyTarget);
}
internal static void OnUserInputEnd() {
@ -109,14 +110,14 @@ namespace ArchiSteamFarm {
return;
}
bool reconfig = false;
bool reconfigure = false;
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules.Where(consoleLoggingRule => !LogManager.Configuration.LoggingRules.Contains(consoleLoggingRule))) {
LogManager.Configuration.LoggingRules.Add(consoleLoggingRule);
reconfig = true;
reconfigure = true;
}
if (reconfig) {
if (reconfigure) {
LogManager.ReconfigExistingLoggers();
}
}
@ -128,15 +129,15 @@ namespace ArchiSteamFarm {
return;
}
bool reconfig = false;
bool reconfigure = false;
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) {
if (LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule)) {
reconfig = true;
reconfigure = true;
}
}
if (reconfig) {
if (reconfigure) {
LogManager.ReconfigExistingLoggers();
}
}
@ -162,7 +163,7 @@ namespace ArchiSteamFarm {
}
HistoryTarget historyTarget = LogManager.Configuration.AllTargets.OfType<HistoryTarget>().FirstOrDefault();
IPC.OnNewHistoryTarget(historyTarget);
ArchiKestrel.OnNewHistoryTarget(historyTarget);
}
}
}
}

View file

@ -28,6 +28,7 @@ using System.IO;
using System.Linq;
using System.Resources;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC;
using ArchiSteamFarm.Localization;
using Newtonsoft.Json;
using NLog;
@ -341,8 +342,8 @@ namespace ArchiSteamFarm {
WebBrowser.Init();
WebBrowser = new WebBrowser(ASF.ArchiLogger, GlobalConfig.WebProxy, true);
if (GlobalConfig.IPC && (GlobalConfig.IPCPrefixes.Count > 0)) {
IPC.Start(GlobalConfig.IPCPrefixes);
if (GlobalConfig.IPC) {
await ArchiKestrel.Start().ConfigureAwait(false);
}
}
@ -353,15 +354,9 @@ namespace ArchiSteamFarm {
ShutdownSequenceInitialized = true;
// Sockets created by HttpListener might still be running for a short while after complete app shutdown
// Sockets created by IPC might still be running for a short while after complete app shutdown
// Ensure that IPC is stopped before we finalize shutdown sequence
if (IPC.IsRunning) {
IPC.Stop();
for (byte i = 0; (i < WebBrowser.MaxTries) && IPC.IsRunning; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
}
await ArchiKestrel.Stop().ConfigureAwait(false);
if (Bot.Bots.Count > 0) {
IEnumerable<Task> tasks = Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(true)));

View file

@ -73,15 +73,6 @@ namespace ArchiSteamFarm {
return cookies.Count > 0 ? (from Cookie cookie in cookies where cookie.Name.Equals(name) select cookie.Value).FirstOrDefault() : null;
}
internal static string GetUnifiedName(this Type type) {
if (type == null) {
ASF.ArchiLogger.LogNullError(nameof(type));
return null;
}
return type.GenericTypeArguments.Length == 0 ? type.FullName : type.Namespace + "." + type.Name + string.Join("", type.GenericTypeArguments.Select(innerType => '[' + innerType.GetUnifiedName() + ']'));
}
internal static uint GetUnixTime() => (uint) DateTimeOffset.UtcNow.ToUnixTimeSeconds();
internal static void InBackground(Action action, bool longRunning = false) {
@ -185,4 +176,4 @@ namespace ArchiSteamFarm {
internal static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3, maxUnit: TimeUnit.Year, minUnit: TimeUnit.Second);
}
}
}

View file

@ -13,9 +13,6 @@
"InventoryLimiterDelay": 3,
"IPC": false,
"IPCPassword": null,
"IPCPrefixes": [
"http://127.0.0.1:1242/"
],
"LoginLimiterDelay": 10,
"MaxFarmingTime": 10,
"MaxTradeHoldDuration": 15,