Add ASF API swagger explorer

This commit is contained in:
JustArchi
2018-10-06 05:06:29 +02:00
parent 52a7bb048f
commit b15edf4559
21 changed files with 295 additions and 40 deletions

View File

@@ -11,8 +11,9 @@
<Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description> <Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description>
<ErrorReport>none</ErrorReport> <ErrorReport>none</ErrorReport>
<FileVersion>3.4.0.3</FileVersion> <FileVersion>3.4.0.3</FileVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<NoWarn /> <NoWarn>1591</NoWarn>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<PackageIconUrl>https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico</PackageIconUrl> <PackageIconUrl>https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico</PackageIconUrl>
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl> <PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
@@ -54,6 +55,7 @@
<PackageReference Include="NLog" Version="4.5.10" /> <PackageReference Include="NLog" Version="4.5.10" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.7.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="4.7.0" />
<PackageReference Include="protobuf-net" Version="3.0.0-alpha.3" /> <PackageReference Include="protobuf-net" Version="3.0.0-alpha.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'"> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">

View File

@@ -33,7 +33,7 @@ using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class BotConfig { public sealed class BotConfig {
private const bool DefaultAcceptGifts = false; private const bool DefaultAcceptGifts = false;
private const bool DefaultAutoSteamSaleEvent = false; private const bool DefaultAutoSteamSaleEvent = false;
private const EBotBehaviour DefaultBotBehaviour = EBotBehaviour.None; private const EBotBehaviour DefaultBotBehaviour = EBotBehaviour.None;

View File

@@ -30,8 +30,12 @@ using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api { namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController] [ApiController]
[Produces("application/json")]
[Route("Api/ASF")] [Route("Api/ASF")]
public sealed class ASFController : ControllerBase { public sealed class ASFController : ControllerBase {
/// <summary>
/// Fetches common info related to ASF as a whole.
/// </summary>
[HttpGet] [HttpGet]
public ActionResult<GenericResponse<ASFResponse>> ASFGet() { public ActionResult<GenericResponse<ASFResponse>> ASFGet() {
uint memoryUsage = (uint) GC.GetTotalMemory(false) / 1024; uint memoryUsage = (uint) GC.GetTotalMemory(false) / 1024;
@@ -46,6 +50,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<ASFResponse>(result)); return Ok(new GenericResponse<ASFResponse>(result));
} }
/// <summary>
/// Updates ASF's global config.
/// </summary>
[HttpPost] [HttpPost]
public async Task<ActionResult<GenericResponse>> ASFPost([FromBody] ASFRequest request) { public async Task<ActionResult<GenericResponse>> ASFPost([FromBody] ASFRequest request) {
if (request == null) { if (request == null) {
@@ -70,18 +77,27 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse(result)); return Ok(new GenericResponse(result));
} }
/// <summary>
/// Makes ASF shutdown itself.
/// </summary>
[HttpPost("Exit")] [HttpPost("Exit")]
public ActionResult<GenericResponse> ExitPost() { public ActionResult<GenericResponse> ExitPost() {
(bool success, string output) = Actions.Exit(); (bool success, string output) = Actions.Exit();
return Ok(new GenericResponse(success, output)); return Ok(new GenericResponse(success, output));
} }
/// <summary>
/// Makes ASF restart itself.
/// </summary>
[HttpPost("Restart")] [HttpPost("Restart")]
public ActionResult<GenericResponse> RestartPost() { public ActionResult<GenericResponse> RestartPost() {
(bool success, string output) = Actions.Restart(); (bool success, string output) = Actions.Restart();
return Ok(new GenericResponse(success, output)); return Ok(new GenericResponse(success, output));
} }
/// <summary>
/// Makes ASF update itself.
/// </summary>
[HttpPost("Update")] [HttpPost("Update")]
public async Task<ActionResult<GenericResponse<Version>>> UpdatePost() { public async Task<ActionResult<GenericResponse<Version>>> UpdatePost() {
(bool success, Version version) = await Actions.Update().ConfigureAwait(false); (bool success, Version version) = await Actions.Update().ConfigureAwait(false);

View File

@@ -32,8 +32,12 @@ using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api { namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController] [ApiController]
[Produces("application/json")]
[Route("Api/Bot")] [Route("Api/Bot")]
public sealed class BotController : ControllerBase { public sealed class BotController : ControllerBase {
/// <summary>
/// Deletes all files related to given bots.
/// </summary>
[HttpDelete("{botNames:required}")] [HttpDelete("{botNames:required}")]
public async Task<ActionResult<GenericResponse>> BotDelete(string botNames) { public async Task<ActionResult<GenericResponse>> BotDelete(string botNames) {
if (string.IsNullOrEmpty(botNames)) { if (string.IsNullOrEmpty(botNames)) {
@@ -50,6 +54,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse(results.All(result => result))); return Ok(new GenericResponse(results.All(result => result)));
} }
/// <summary>
/// Fetches common info related to given bots.
/// </summary>
[HttpGet("{botNames:required}")] [HttpGet("{botNames:required}")]
public ActionResult<GenericResponse<HashSet<Bot>>> BotGet(string botNames) { public ActionResult<GenericResponse<HashSet<Bot>>> BotGet(string botNames) {
if (string.IsNullOrEmpty(botNames)) { if (string.IsNullOrEmpty(botNames)) {
@@ -65,6 +72,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<HashSet<Bot>>(bots)); return Ok(new GenericResponse<HashSet<Bot>>(bots));
} }
/// <summary>
/// Updates bot config of given bot.
/// </summary>
[HttpPost("{botName:required}")] [HttpPost("{botName:required}")]
public async Task<ActionResult<GenericResponse>> BotPost(string botName, [FromBody] BotRequest request) { public async Task<ActionResult<GenericResponse>> BotPost(string botName, [FromBody] BotRequest request) {
if (string.IsNullOrEmpty(botName) || (request == null)) { if (string.IsNullOrEmpty(botName) || (request == null)) {
@@ -99,6 +109,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse(result)); return Ok(new GenericResponse(result));
} }
/// <summary>
/// Removes BGR output files of given bots.
/// </summary>
[HttpDelete("{botNames:required}/GamesToRedeemInBackground")] [HttpDelete("{botNames:required}/GamesToRedeemInBackground")]
public async Task<ActionResult<GenericResponse>> GamesToRedeemInBackgroundDelete(string botNames) { public async Task<ActionResult<GenericResponse>> GamesToRedeemInBackgroundDelete(string botNames) {
if (string.IsNullOrEmpty(botNames)) { if (string.IsNullOrEmpty(botNames)) {
@@ -115,6 +128,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(results.All(result => result) ? new GenericResponse(true) : new GenericResponse(false, Strings.WarningFailed)); return Ok(results.All(result => result) ? new GenericResponse(true) : new GenericResponse(false, Strings.WarningFailed));
} }
/// <summary>
/// Fetches BGR output files of given bots.
/// </summary>
[HttpGet("{botNames:required}/GamesToRedeemInBackground")] [HttpGet("{botNames:required}/GamesToRedeemInBackground")]
public async Task<ActionResult<GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>>> GamesToRedeemInBackgroundGet(string botNames) { public async Task<ActionResult<GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>>> GamesToRedeemInBackgroundGet(string botNames) {
if (string.IsNullOrEmpty(botNames)) { if (string.IsNullOrEmpty(botNames)) {
@@ -139,6 +155,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>(result)); return Ok(new GenericResponse<Dictionary<string, GamesToRedeemInBackgroundResponse>>(result));
} }
/// <summary>
/// Adds keys to redeem using BGR to given bot.
/// </summary>
[HttpPost("{botName:required}/GamesToRedeemInBackground")] [HttpPost("{botName:required}/GamesToRedeemInBackground")]
public async Task<ActionResult<GenericResponse<OrderedDictionary>>> GamesToRedeemInBackgroundPost(string botName, [FromBody] GamesToRedeemInBackgroundRequest request) { public async Task<ActionResult<GenericResponse<OrderedDictionary>>> GamesToRedeemInBackgroundPost(string botName, [FromBody] GamesToRedeemInBackgroundRequest request) {
if (string.IsNullOrEmpty(botName) || (request == null)) { if (string.IsNullOrEmpty(botName) || (request == null)) {
@@ -158,6 +177,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<OrderedDictionary>(result, request.GamesToRedeemInBackground)); return Ok(new GenericResponse<OrderedDictionary>(result, request.GamesToRedeemInBackground));
} }
/// <summary>
/// Pauses given bots.
/// </summary>
[HttpPost("{botNames:required}/Pause")] [HttpPost("{botNames:required}/Pause")]
public async Task<ActionResult<GenericResponse>> PausePost(string botNames, [FromBody] BotPauseRequest request) { public async Task<ActionResult<GenericResponse>> PausePost(string botNames, [FromBody] BotPauseRequest request) {
if (string.IsNullOrEmpty(botNames) || (request == null)) { if (string.IsNullOrEmpty(botNames) || (request == null)) {
@@ -174,6 +196,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse(results.All(result => result.Success), string.Join(Environment.NewLine, results.Select(result => result.Output)))); return Ok(new GenericResponse(results.All(result => result.Success), string.Join(Environment.NewLine, results.Select(result => result.Output))));
} }
/// <summary>
/// Resumes given bots.
/// </summary>
[HttpPost("{botNames:required}/Resume")] [HttpPost("{botNames:required}/Resume")]
public async Task<ActionResult<GenericResponse>> ResumePost(string botNames) { public async Task<ActionResult<GenericResponse>> ResumePost(string botNames) {
if (string.IsNullOrEmpty(botNames)) { if (string.IsNullOrEmpty(botNames)) {
@@ -190,6 +215,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse(results.All(result => result.Success), string.Join(Environment.NewLine, results.Select(result => result.Output)))); return Ok(new GenericResponse(results.All(result => result.Success), string.Join(Environment.NewLine, results.Select(result => result.Output))));
} }
/// <summary>
/// Starts given bots.
/// </summary>
[HttpPost("{botNames:required}/Start")] [HttpPost("{botNames:required}/Start")]
public async Task<ActionResult<GenericResponse>> StartPost(string botNames) { public async Task<ActionResult<GenericResponse>> StartPost(string botNames) {
if (string.IsNullOrEmpty(botNames)) { if (string.IsNullOrEmpty(botNames)) {
@@ -206,6 +234,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse(results.All(result => result.Success), string.Join(Environment.NewLine, results.Select(result => result.Output)))); return Ok(new GenericResponse(results.All(result => result.Success), string.Join(Environment.NewLine, results.Select(result => result.Output))));
} }
/// <summary>
/// Stops given bots.
/// </summary>
[HttpPost("{botNames:required}/Stop")] [HttpPost("{botNames:required}/Stop")]
public async Task<ActionResult<GenericResponse>> StopPost(string botNames) { public async Task<ActionResult<GenericResponse>> StopPost(string botNames) {
if (string.IsNullOrEmpty(botNames)) { if (string.IsNullOrEmpty(botNames)) {

View File

@@ -28,8 +28,16 @@ using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api { namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController] [ApiController]
[Produces("application/json")]
[Route("Api/Command")] [Route("Api/Command")]
public sealed class CommandController : ControllerBase { public sealed class CommandController : ControllerBase {
/// <summary>
/// Executes a command.
/// </summary>
/// <remarks>
/// This API endpoint is supposed to be entirely replaced by ASF actions available under /Api/ASF/{action} and /Api/Bot/{bot}/{action}.
/// You should use "given bot" commands when executing this endpoint, omitting targets of the command will cause the command to be executed on first defined bot
/// </remarks>
[HttpPost("{command:required}")] [HttpPost("{command:required}")]
public async Task<ActionResult<GenericResponse<string>>> CommandPost(string command) { public async Task<ActionResult<GenericResponse<string>>> CommandPost(string command) {
if (string.IsNullOrEmpty(command)) { if (string.IsNullOrEmpty(command)) {

View File

@@ -33,10 +33,17 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Controllers.Api { namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController] [ApiController]
[Produces("application/json")]
[Route("Api/NLog")] [Route("Api/NLog")]
public sealed class NLogController : ControllerBase { public sealed class NLogController : ControllerBase {
private static readonly ConcurrentDictionary<WebSocket, SemaphoreSlim> ActiveLogWebSockets = new ConcurrentDictionary<WebSocket, SemaphoreSlim>(); private static readonly ConcurrentDictionary<WebSocket, SemaphoreSlim> ActiveLogWebSockets = new ConcurrentDictionary<WebSocket, SemaphoreSlim>();
/// <summary>
/// Fetches ASF log in realtime.
/// </summary>
/// <remarks>
/// This API endpoint requires a websocket connection.
/// </remarks>
[HttpGet] [HttpGet]
public async Task<ActionResult> NLogGet() { public async Task<ActionResult> NLogGet() {
if (!HttpContext.WebSockets.IsWebSocketRequest) { if (!HttpContext.WebSockets.IsWebSocketRequest) {

View File

@@ -26,8 +26,15 @@ using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api { namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController] [ApiController]
[Produces("application/json")]
[Route("Api/Structure")] [Route("Api/Structure")]
public sealed class StructureController : ControllerBase { public sealed class StructureController : ControllerBase {
/// <summary>
/// Fetches structure of given type.
/// </summary>
/// <remarks>
/// Structure is defined as a representation of given object in its default state.
/// </remarks>
[HttpGet("{structure:required}")] [HttpGet("{structure:required}")]
public ActionResult<GenericResponse<object>> StructureGet(string structure) { public ActionResult<GenericResponse<object>> StructureGet(string structure) {
if (string.IsNullOrEmpty(structure)) { if (string.IsNullOrEmpty(structure)) {

View File

@@ -30,8 +30,15 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Controllers.Api { namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController] [ApiController]
[Produces("application/json")]
[Route("Api/Type")] [Route("Api/Type")]
public sealed class TypeController : ControllerBase { public sealed class TypeController : ControllerBase {
/// <summary>
/// Fetches type info of given type.
/// </summary>
/// <remarks>
/// Type info is defined as a representation of given object with its fields and properties being assigned to a string value that defines their type.
/// </remarks>
[HttpGet("{type:required}")] [HttpGet("{type:required}")]
public ActionResult<GenericResponse<TypeResponse>> TypeGet(string type) { public ActionResult<GenericResponse<TypeResponse>> TypeGet(string type) {
if (string.IsNullOrEmpty(type)) { if (string.IsNullOrEmpty(type)) {

View File

@@ -31,8 +31,15 @@ using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api { namespace ArchiSteamFarm.IPC.Controllers.Api {
[ApiController] [ApiController]
[Produces("application/json")]
[Route("Api/WWW")] [Route("Api/WWW")]
public sealed class WWWController : ControllerBase { public sealed class WWWController : ControllerBase {
/// <summary>
/// Fetches files in given directory relative to WWW root.
/// </summary>
/// <remarks>
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW as they can disappear and change anytime.
/// </remarks>
[HttpGet("Directory/{directory:required}")] [HttpGet("Directory/{directory:required}")]
public ActionResult<GenericResponse<HashSet<string>>> DirectoryGet(string directory) { public ActionResult<GenericResponse<HashSet<string>>> DirectoryGet(string directory) {
if (string.IsNullOrEmpty(directory)) { if (string.IsNullOrEmpty(directory)) {
@@ -57,6 +64,12 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<HashSet<string>>(result)); return Ok(new GenericResponse<HashSet<string>>(result));
} }
/// <summary>
/// Fetches newest GitHub releases of ASF project.
/// </summary>
/// <remarks>
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW as they can disappear and change anytime.
/// </remarks>
[HttpGet("GitHub/Releases")] [HttpGet("GitHub/Releases")]
public async Task<ActionResult<GenericResponse<IEnumerable<GitHubReleaseResponse>>>> GitHubReleasesGet([FromQuery] byte count = 10) { public async Task<ActionResult<GenericResponse<IEnumerable<GitHubReleaseResponse>>>> GitHubReleasesGet([FromQuery] byte count = 10) {
if (count == 0) { if (count == 0) {
@@ -72,6 +85,12 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<IEnumerable<GitHubReleaseResponse>>(result)); return Ok(new GenericResponse<IEnumerable<GitHubReleaseResponse>>(result));
} }
/// <summary>
/// Fetches specific GitHub release of ASF project.
/// </summary>
/// <remarks>
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW as they can disappear and change anytime.
/// </remarks>
[HttpGet("GitHub/Releases/{version:required}")] [HttpGet("GitHub/Releases/{version:required}")]
public async Task<ActionResult<GenericResponse<GitHubReleaseResponse>>> GitHubReleasesGet(string version) { public async Task<ActionResult<GenericResponse<GitHubReleaseResponse>>> GitHubReleasesGet(string version) {
if (string.IsNullOrEmpty(version)) { if (string.IsNullOrEmpty(version)) {
@@ -86,6 +105,12 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse))); return Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse)));
} }
/// <summary>
/// Sends a HTTPS request through ASF's built-in HttpClient.
/// </summary>
/// <remarks>
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW as they can disappear and change anytime.
/// </remarks>
[HttpPost("Send")] [HttpPost("Send")]
public async Task<ActionResult<GenericResponse<string>>> SendPost([FromBody] WWWSendRequest request) { public async Task<ActionResult<GenericResponse<string>>> SendPost([FromBody] WWWSendRequest request) {
if (request == null) { if (request == null) {

View File

@@ -19,14 +19,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests { namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class ASFRequest { public sealed class ASFRequest {
/// <summary>
/// ASF's global config structure.
/// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
internal readonly GlobalConfig GlobalConfig; [Required]
public readonly GlobalConfig GlobalConfig;
// Deserialized from JSON // Deserialized from JSON
private ASFRequest() { } private ASFRequest() { }

View File

@@ -25,11 +25,17 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests { namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class BotPauseRequest { public sealed class BotPauseRequest {
/// <summary>
/// Specifies if pause is permanent or temporary (default).
/// </summary>
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Permanent; public readonly bool Permanent;
/// <summary>
/// Specifies automatic resume action in given seconds. Default value of 0 disables automatic resume.
/// </summary>
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly ushort ResumeInSeconds; public readonly ushort ResumeInSeconds;
// Deserialized from JSON // Deserialized from JSON
private BotPauseRequest() { } private BotPauseRequest() { }

View File

@@ -19,14 +19,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests { namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class BotRequest { public sealed class BotRequest {
/// <summary>
/// ASF's bot config structure.
/// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
internal readonly BotConfig BotConfig; [Required]
public readonly BotConfig BotConfig;
// Deserialized from JSON // Deserialized from JSON
private BotRequest() { } private BotRequest() { }

View File

@@ -20,14 +20,23 @@
// limitations under the License. // limitations under the License.
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests { namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class GamesToRedeemInBackgroundRequest { public sealed class GamesToRedeemInBackgroundRequest {
/// <summary>
/// A string-string map that maps cd-key to redeem (key) to its name (value).
/// </summary>
/// <remarks>
/// Key in the map must be a valid and unique Steam cd-key.
/// Value in the map must be a non-null and non-empty name of the key (e.g. game's name, but can be anything).
/// </remarks>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
internal readonly OrderedDictionary GamesToRedeemInBackground; [Required]
public readonly OrderedDictionary GamesToRedeemInBackground;
// Deserialized from JSON // Deserialized from JSON
private GamesToRedeemInBackgroundRequest() { } private GamesToRedeemInBackgroundRequest() { }

View File

@@ -19,14 +19,22 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Requests { namespace ArchiSteamFarm.IPC.Requests {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class WWWSendRequest { public sealed class WWWSendRequest {
/// <summary>
/// Full URL of the request to be made.
/// </summary>
/// <remarks>
/// URL must start from https:// scheme.
/// </remarks>
[Required]
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
internal readonly string URL; public readonly string URL;
// Deserialized from JSON // Deserialized from JSON
private WWWSendRequest() { } private WWWSendRequest() { }

View File

@@ -20,24 +20,45 @@
// limitations under the License. // limitations under the License.
using System; using System;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses { namespace ArchiSteamFarm.IPC.Responses {
public sealed class ASFResponse { public sealed class ASFResponse {
[JsonProperty] /// <summary>
private readonly string BuildVariant; /// ASF's build variant.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string BuildVariant;
[JsonProperty] /// <summary>
private readonly GlobalConfig GlobalConfig; /// Currently loaded ASF's global config.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly GlobalConfig GlobalConfig;
[JsonProperty] /// <summary>
private readonly uint MemoryUsage; /// Current amount of managed memory being used by the process, in kilobytes.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly uint MemoryUsage;
[JsonProperty] /// <summary>
private readonly DateTime ProcessStartTime; /// Start date of the process.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly DateTime ProcessStartTime;
[JsonProperty] /// <summary>
private readonly Version Version; /// ASF version of currently running binary.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly Version Version;
internal ASFResponse(string buildVariant, GlobalConfig globalConfig, uint memoryUsage, DateTime processStartTime, 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)) { if (string.IsNullOrEmpty(buildVariant) || (globalConfig == null) || (memoryUsage == 0) || (processStartTime == DateTime.MinValue) || (version == null)) {

View File

@@ -24,11 +24,17 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses { namespace ArchiSteamFarm.IPC.Responses {
public sealed class GamesToRedeemInBackgroundResponse { public sealed class GamesToRedeemInBackgroundResponse {
/// <summary>
/// Keys that were redeemed and not used during the process, if available.
/// </summary>
[JsonProperty] [JsonProperty]
private readonly Dictionary<string, string> UnusedKeys; public readonly Dictionary<string, string> UnusedKeys;
/// <summary>
/// Keys that were redeemed and used during the process, if available.
/// </summary>
[JsonProperty] [JsonProperty]
private readonly Dictionary<string, string> UsedKeys; public readonly Dictionary<string, string> UsedKeys;
internal GamesToRedeemInBackgroundResponse(Dictionary<string, string> unusedKeys = null, Dictionary<string, string> usedKeys = null) { internal GamesToRedeemInBackgroundResponse(Dictionary<string, string> unusedKeys = null, Dictionary<string, string> usedKeys = null) {
UnusedKeys = unusedKeys; UnusedKeys = unusedKeys;

View File

@@ -19,13 +19,20 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
using System.ComponentModel.DataAnnotations;
using ArchiSteamFarm.Localization; using ArchiSteamFarm.Localization;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses { namespace ArchiSteamFarm.IPC.Responses {
public sealed class GenericResponse<T> : GenericResponse where T : class { public sealed class GenericResponse<T> : GenericResponse where T : class {
/// <summary>
/// The actual result of the request, if available.
/// </summary>
/// <remarks>
/// The type of the result depends on the API endpoint that you've called.
/// </remarks>
[JsonProperty] [JsonProperty]
private readonly T Result; public readonly T Result;
internal GenericResponse(T result) : base(result != null) => Result = result; internal GenericResponse(T result) : base(result != null) => Result = result;
internal GenericResponse(bool success, string message) : base(success, message) { } internal GenericResponse(bool success, string message) : base(success, message) { }
@@ -33,11 +40,21 @@ namespace ArchiSteamFarm.IPC.Responses {
} }
public class GenericResponse { public class GenericResponse {
/// <summary>
/// A message that describes what happened with the request, if available.
/// </summary>
/// <remarks>
/// This property will provide exact reason for majority of expected failures.
/// </remarks>
[JsonProperty] [JsonProperty]
private readonly string Message; public readonly string Message;
[JsonProperty] /// <summary>
private readonly bool Success; /// Boolean type that specifies if the request has succeeded.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly bool Success;
internal GenericResponse(bool success, string message = null) { internal GenericResponse(bool success, string message = null) {
Success = success; Success = success;

View File

@@ -20,21 +20,38 @@
// limitations under the License. // limitations under the License.
using System; using System;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses { namespace ArchiSteamFarm.IPC.Responses {
public sealed class GitHubReleaseResponse { public sealed class GitHubReleaseResponse {
[JsonProperty] /// <summary>
private readonly string ChangelogHTML; /// Changelog of the release rendered in HTML.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string ChangelogHTML;
[JsonProperty] /// <summary>
private readonly DateTime ReleasedAt; /// Date of the release.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly DateTime ReleasedAt;
[JsonProperty] /// <summary>
private readonly bool Stable; /// Boolean value that specifies whether the build is stable or not (pre-release).
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly bool Stable;
[JsonProperty] /// <summary>
private readonly string Version; /// Version of the release.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string Version;
internal GitHubReleaseResponse(GitHub.ReleaseResponse releaseResponse) { internal GitHubReleaseResponse(GitHub.ReleaseResponse releaseResponse) {
if (releaseResponse == null) { if (releaseResponse == null) {

View File

@@ -21,15 +21,29 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses { namespace ArchiSteamFarm.IPC.Responses {
public sealed class TypeResponse { public sealed class TypeResponse {
[JsonProperty] /// <summary>
private readonly Dictionary<string, string> Body; /// A string-string map representing a decomposition of given type.
/// </summary>
/// <remarks>
/// The actual structure of this field depends on the type that was requested. You can determine that type based on <see cref="Properties"/> metadata.
/// For enums, keys are friendly names while values are underlying values of those names.
/// For objects, keys are non-private fields and properties, while values are underlying types of those.
/// </remarks>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly Dictionary<string, string> Body;
[JsonProperty] /// <summary>
private readonly TypeProperties Properties; /// Metadata of given type.
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly TypeProperties Properties;
internal TypeResponse(Dictionary<string, string> body, TypeProperties properties) { internal TypeResponse(Dictionary<string, string> body, TypeProperties properties) {
if ((body == null) || (properties == null)) { if ((body == null) || (properties == null)) {
@@ -40,15 +54,33 @@ namespace ArchiSteamFarm.IPC.Responses {
Properties = properties; Properties = properties;
} }
internal sealed class TypeProperties { public sealed class TypeProperties {
/// <summary>
/// Base type of given type, if available.
/// </summary>
/// <remarks>
/// This can be used for determining how <see cref="Body"/> should be interpreted.
/// </remarks>
[JsonProperty] [JsonProperty]
private readonly string BaseType; public readonly string BaseType;
/// <summary>
/// Custom attributes of given type, if available.
/// </summary>
/// <remarks>
/// This can be used for determining main enum type if <see cref="BaseType"/> is <see cref="Enum"/>.
/// </remarks>
[JsonProperty] [JsonProperty]
private readonly HashSet<string> CustomAttributes; public readonly HashSet<string> CustomAttributes;
/// <summary>
/// Underlying type of given type, if available.
/// </summary>
/// <remarks>
/// This can be used for determining underlying enum type if <see cref="BaseType"/> is <see cref="Enum"/>.
/// </remarks>
[JsonProperty] [JsonProperty]
private readonly string UnderlyingType; public readonly string UnderlyingType;
internal TypeProperties(string baseType = null, HashSet<string> customAttributes = null, string underlyingType = null) { internal TypeProperties(string baseType = null, HashSet<string> customAttributes = null, string underlyingType = null) {
BaseType = baseType; BaseType = baseType;

View File

@@ -20,6 +20,7 @@
// limitations under the License. // limitations under the License.
using System; using System;
using System.IO;
using ArchiSteamFarm.IPC.Middleware; using ArchiSteamFarm.IPC.Middleware;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@@ -29,6 +30,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using Swashbuckle.AspNetCore.Swagger;
namespace ArchiSteamFarm.IPC { namespace ArchiSteamFarm.IPC {
internal sealed class Startup { internal sealed class Startup {
@@ -64,6 +66,12 @@ namespace ArchiSteamFarm.IPC {
// We need MVC for /Api // We need MVC for /Api
app.UseMvcWithDefaultRoute(); app.UseMvcWithDefaultRoute();
// Use swagger for automatic API documentation generation
app.UseSwagger();
// Use friendly swagger UI
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/ASF/swagger.json", "ASF API"));
// We're using index for URL routing in our static files so re-execute all non-API calls on / // We're using index for URL routing in our static files so re-execute all non-API calls on /
app.UseWhen(context => !context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseStatusCodePagesWithReExecute("/")); app.UseWhen(context => !context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseStatusCodePagesWithReExecute("/"));
@@ -83,9 +91,26 @@ namespace ArchiSteamFarm.IPC {
// Add support for response compression // Add support for response compression
services.AddResponseCompression(); services.AddResponseCompression();
// Add swagger documentation generation
services.AddSwaggerGen(
c => {
c.DescribeAllEnumsAsStrings();
c.SwaggerDoc("ASF", new Info { Title = "ASF API" });
string xmlDocumentationFile = Path.Combine(AppContext.BaseDirectory, SharedInfo.AssemblyDocumentation);
if (File.Exists(xmlDocumentationFile)) {
c.IncludeXmlComments(xmlDocumentationFile);
}
}
);
// We need MVC for /Api, but we're going to use only a small subset of all available features // We need MVC for /Api, but we're going to use only a small subset of all available features
IMvcCoreBuilder mvc = services.AddMvcCore(); IMvcCoreBuilder mvc = services.AddMvcCore();
// Add API explorer for swagger
mvc.AddApiExplorer();
// Use latest compatibility version for MVC // Use latest compatibility version for MVC
mvc.SetCompatibilityVersion(CompatibilityVersion.Latest); mvc.SetCompatibilityVersion(CompatibilityVersion.Latest);

View File

@@ -29,6 +29,7 @@ namespace ArchiSteamFarm {
internal const ulong ArchiSteamID = 76561198006963719; internal const ulong ArchiSteamID = 76561198006963719;
internal const string ASF = nameof(ASF); internal const string ASF = nameof(ASF);
internal const ulong ASFGroupSteamID = 103582791440160998; internal const ulong ASFGroupSteamID = 103582791440160998;
internal const string AssemblyDocumentation = AssemblyName + ".xml";
internal const string AssemblyName = nameof(ArchiSteamFarm); internal const string AssemblyName = nameof(ArchiSteamFarm);
internal const string ConfigDirectory = "config"; internal const string ConfigDirectory = "config";
internal const string ConfigExtension = ".json"; internal const string ConfigExtension = ".json";