diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj index 6c0842b8d..a1bbd4cfd 100644 --- a/ArchiSteamFarm/ArchiSteamFarm.csproj +++ b/ArchiSteamFarm/ArchiSteamFarm.csproj @@ -11,8 +11,9 @@ ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously. none 3.4.0.3 + true latest - + 1591 Exe https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico http://www.apache.org/licenses/LICENSE-2.0 @@ -54,6 +55,7 @@ + diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index 12d872a87..17c8da449 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -33,7 +33,7 @@ using SteamKit2; namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - internal sealed class BotConfig { + public sealed class BotConfig { private const bool DefaultAcceptGifts = false; private const bool DefaultAutoSteamSaleEvent = false; private const EBotBehaviour DefaultBotBehaviour = EBotBehaviour.None; diff --git a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs index a4f021a58..a1fe2c457 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs @@ -30,8 +30,12 @@ using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api { [ApiController] + [Produces("application/json")] [Route("Api/ASF")] public sealed class ASFController : ControllerBase { + /// + /// Fetches common info related to ASF as a whole. + /// [HttpGet] public ActionResult> ASFGet() { uint memoryUsage = (uint) GC.GetTotalMemory(false) / 1024; @@ -46,6 +50,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse(result)); } + /// + /// Updates ASF's global config. + /// [HttpPost] public async Task> ASFPost([FromBody] ASFRequest request) { if (request == null) { @@ -70,18 +77,27 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse(result)); } + /// + /// Makes ASF shutdown itself. + /// [HttpPost("Exit")] public ActionResult ExitPost() { (bool success, string output) = Actions.Exit(); return Ok(new GenericResponse(success, output)); } + /// + /// Makes ASF restart itself. + /// [HttpPost("Restart")] public ActionResult RestartPost() { (bool success, string output) = Actions.Restart(); return Ok(new GenericResponse(success, output)); } + /// + /// Makes ASF update itself. + /// [HttpPost("Update")] public async Task>> UpdatePost() { (bool success, Version version) = await Actions.Update().ConfigureAwait(false); diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs index f0086e5f1..7c750bbb5 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -32,8 +32,12 @@ using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api { [ApiController] + [Produces("application/json")] [Route("Api/Bot")] public sealed class BotController : ControllerBase { + /// + /// Deletes all files related to given bots. + /// [HttpDelete("{botNames:required}")] public async Task> BotDelete(string botNames) { if (string.IsNullOrEmpty(botNames)) { @@ -50,6 +54,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse(results.All(result => result))); } + /// + /// Fetches common info related to given bots. + /// [HttpGet("{botNames:required}")] public ActionResult>> BotGet(string botNames) { if (string.IsNullOrEmpty(botNames)) { @@ -65,6 +72,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse>(bots)); } + /// + /// Updates bot config of given bot. + /// [HttpPost("{botName:required}")] public async Task> BotPost(string botName, [FromBody] BotRequest request) { if (string.IsNullOrEmpty(botName) || (request == null)) { @@ -99,6 +109,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse(result)); } + /// + /// Removes BGR output files of given bots. + /// [HttpDelete("{botNames:required}/GamesToRedeemInBackground")] public async Task> GamesToRedeemInBackgroundDelete(string 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)); } + /// + /// Fetches BGR output files of given bots. + /// [HttpGet("{botNames:required}/GamesToRedeemInBackground")] public async Task>>> GamesToRedeemInBackgroundGet(string botNames) { if (string.IsNullOrEmpty(botNames)) { @@ -139,6 +155,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse>(result)); } + /// + /// Adds keys to redeem using BGR to given bot. + /// [HttpPost("{botName:required}/GamesToRedeemInBackground")] public async Task>> GamesToRedeemInBackgroundPost(string botName, [FromBody] GamesToRedeemInBackgroundRequest request) { if (string.IsNullOrEmpty(botName) || (request == null)) { @@ -158,6 +177,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse(result, request.GamesToRedeemInBackground)); } + /// + /// Pauses given bots. + /// [HttpPost("{botNames:required}/Pause")] public async Task> PausePost(string botNames, [FromBody] BotPauseRequest request) { 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)))); } + /// + /// Resumes given bots. + /// [HttpPost("{botNames:required}/Resume")] public async Task> ResumePost(string 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)))); } + /// + /// Starts given bots. + /// [HttpPost("{botNames:required}/Start")] public async Task> StartPost(string 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)))); } + /// + /// Stops given bots. + /// [HttpPost("{botNames:required}/Stop")] public async Task> StopPost(string botNames) { if (string.IsNullOrEmpty(botNames)) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs b/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs index e1eb3756e..e2c1915da 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs @@ -28,8 +28,16 @@ using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api { [ApiController] + [Produces("application/json")] [Route("Api/Command")] public sealed class CommandController : ControllerBase { + /// + /// Executes a command. + /// + /// + /// 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 + /// [HttpPost("{command:required}")] public async Task>> CommandPost(string command) { if (string.IsNullOrEmpty(command)) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs b/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs index 240574aaa..a564c9184 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs @@ -33,10 +33,17 @@ using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Controllers.Api { [ApiController] + [Produces("application/json")] [Route("Api/NLog")] public sealed class NLogController : ControllerBase { private static readonly ConcurrentDictionary ActiveLogWebSockets = new ConcurrentDictionary(); + /// + /// Fetches ASF log in realtime. + /// + /// + /// This API endpoint requires a websocket connection. + /// [HttpGet] public async Task NLogGet() { if (!HttpContext.WebSockets.IsWebSocketRequest) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs b/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs index dda5a76ec..1665d9038 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs @@ -26,8 +26,15 @@ using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api { [ApiController] + [Produces("application/json")] [Route("Api/Structure")] public sealed class StructureController : ControllerBase { + /// + /// Fetches structure of given type. + /// + /// + /// Structure is defined as a representation of given object in its default state. + /// [HttpGet("{structure:required}")] public ActionResult> StructureGet(string structure) { if (string.IsNullOrEmpty(structure)) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs b/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs index 9817636dc..f9baf89c4 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs @@ -30,8 +30,15 @@ using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Controllers.Api { [ApiController] + [Produces("application/json")] [Route("Api/Type")] public sealed class TypeController : ControllerBase { + /// + /// Fetches type info of given type. + /// + /// + /// 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. + /// [HttpGet("{type:required}")] public ActionResult> TypeGet(string type) { if (string.IsNullOrEmpty(type)) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/WWWController.cs b/ArchiSteamFarm/IPC/Controllers/Api/WWWController.cs index 629d78c47..b33898862 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/WWWController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/WWWController.cs @@ -31,8 +31,15 @@ using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api { [ApiController] + [Produces("application/json")] [Route("Api/WWW")] public sealed class WWWController : ControllerBase { + /// + /// Fetches files in given directory relative to WWW root. + /// + /// + /// 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. + /// [HttpGet("Directory/{directory:required}")] public ActionResult>> DirectoryGet(string directory) { if (string.IsNullOrEmpty(directory)) { @@ -57,6 +64,12 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse>(result)); } + /// + /// Fetches newest GitHub releases of ASF project. + /// + /// + /// 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. + /// [HttpGet("GitHub/Releases")] public async Task>>> GitHubReleasesGet([FromQuery] byte count = 10) { if (count == 0) { @@ -72,6 +85,12 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse>(result)); } + /// + /// Fetches specific GitHub release of ASF project. + /// + /// + /// 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. + /// [HttpGet("GitHub/Releases/{version:required}")] public async Task>> GitHubReleasesGet(string version) { if (string.IsNullOrEmpty(version)) { @@ -86,6 +105,12 @@ namespace ArchiSteamFarm.IPC.Controllers.Api { return Ok(new GenericResponse(new GitHubReleaseResponse(releaseResponse))); } + /// + /// Sends a HTTPS request through ASF's built-in HttpClient. + /// + /// + /// 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. + /// [HttpPost("Send")] public async Task>> SendPost([FromBody] WWWSendRequest request) { if (request == null) { diff --git a/ArchiSteamFarm/IPC/Requests/ASFRequest.cs b/ArchiSteamFarm/IPC/Requests/ASFRequest.cs index 48590a9ed..bd00e9fee 100644 --- a/ArchiSteamFarm/IPC/Requests/ASFRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/ASFRequest.cs @@ -19,14 +19,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Requests { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class ASFRequest { + /// + /// ASF's global config structure. + /// [JsonProperty(Required = Required.Always)] - internal readonly GlobalConfig GlobalConfig; + [Required] + public readonly GlobalConfig GlobalConfig; // Deserialized from JSON private ASFRequest() { } diff --git a/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs b/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs index 679eb245e..a7c561189 100644 --- a/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs @@ -25,11 +25,17 @@ using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Requests { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotPauseRequest { + /// + /// Specifies if pause is permanent or temporary (default). + /// [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool Permanent; + public readonly bool Permanent; + /// + /// Specifies automatic resume action in given seconds. Default value of 0 disables automatic resume. + /// [JsonProperty(Required = Required.DisallowNull)] - internal readonly ushort ResumeInSeconds; + public readonly ushort ResumeInSeconds; // Deserialized from JSON private BotPauseRequest() { } diff --git a/ArchiSteamFarm/IPC/Requests/BotRequest.cs b/ArchiSteamFarm/IPC/Requests/BotRequest.cs index 0958815d5..e67057fb6 100644 --- a/ArchiSteamFarm/IPC/Requests/BotRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotRequest.cs @@ -19,14 +19,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Requests { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotRequest { + /// + /// ASF's bot config structure. + /// [JsonProperty(Required = Required.Always)] - internal readonly BotConfig BotConfig; + [Required] + public readonly BotConfig BotConfig; // Deserialized from JSON private BotRequest() { } diff --git a/ArchiSteamFarm/IPC/Requests/GamesToRedeemInBackgroundRequest.cs b/ArchiSteamFarm/IPC/Requests/GamesToRedeemInBackgroundRequest.cs index d6034e937..11bb6fffb 100644 --- a/ArchiSteamFarm/IPC/Requests/GamesToRedeemInBackgroundRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/GamesToRedeemInBackgroundRequest.cs @@ -20,14 +20,23 @@ // limitations under the License. using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Requests { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class GamesToRedeemInBackgroundRequest { + /// + /// A string-string map that maps cd-key to redeem (key) to its name (value). + /// + /// + /// 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). + /// [JsonProperty(Required = Required.Always)] - internal readonly OrderedDictionary GamesToRedeemInBackground; + [Required] + public readonly OrderedDictionary GamesToRedeemInBackground; // Deserialized from JSON private GamesToRedeemInBackgroundRequest() { } diff --git a/ArchiSteamFarm/IPC/Requests/WWWSendRequest.cs b/ArchiSteamFarm/IPC/Requests/WWWSendRequest.cs index 416c14aa1..1da581b43 100644 --- a/ArchiSteamFarm/IPC/Requests/WWWSendRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/WWWSendRequest.cs @@ -19,14 +19,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Requests { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class WWWSendRequest { + /// + /// Full URL of the request to be made. + /// + /// + /// URL must start from https:// scheme. + /// + [Required] [JsonProperty(Required = Required.Always)] - internal readonly string URL; + public readonly string URL; // Deserialized from JSON private WWWSendRequest() { } diff --git a/ArchiSteamFarm/IPC/Responses/ASFResponse.cs b/ArchiSteamFarm/IPC/Responses/ASFResponse.cs index de10ead13..9ccd992bd 100644 --- a/ArchiSteamFarm/IPC/Responses/ASFResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/ASFResponse.cs @@ -20,24 +20,45 @@ // limitations under the License. using System; +using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Responses { public sealed class ASFResponse { - [JsonProperty] - private readonly string BuildVariant; + /// + /// ASF's build variant. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly string BuildVariant; - [JsonProperty] - private readonly GlobalConfig GlobalConfig; + /// + /// Currently loaded ASF's global config. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly GlobalConfig GlobalConfig; - [JsonProperty] - private readonly uint MemoryUsage; + /// + /// Current amount of managed memory being used by the process, in kilobytes. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly uint MemoryUsage; - [JsonProperty] - private readonly DateTime ProcessStartTime; + /// + /// Start date of the process. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly DateTime ProcessStartTime; - [JsonProperty] - private readonly Version Version; + /// + /// ASF version of currently running binary. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public 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)) { diff --git a/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs b/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs index 6829c96b1..21f9cd848 100644 --- a/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs @@ -24,11 +24,17 @@ using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Responses { public sealed class GamesToRedeemInBackgroundResponse { + /// + /// Keys that were redeemed and not used during the process, if available. + /// [JsonProperty] - private readonly Dictionary UnusedKeys; + public readonly Dictionary UnusedKeys; + /// + /// Keys that were redeemed and used during the process, if available. + /// [JsonProperty] - private readonly Dictionary UsedKeys; + public readonly Dictionary UsedKeys; internal GamesToRedeemInBackgroundResponse(Dictionary unusedKeys = null, Dictionary usedKeys = null) { UnusedKeys = unusedKeys; diff --git a/ArchiSteamFarm/IPC/Responses/GenericResponse.cs b/ArchiSteamFarm/IPC/Responses/GenericResponse.cs index edf7516f5..8eeddf240 100644 --- a/ArchiSteamFarm/IPC/Responses/GenericResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/GenericResponse.cs @@ -19,13 +19,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel.DataAnnotations; using ArchiSteamFarm.Localization; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Responses { public sealed class GenericResponse : GenericResponse where T : class { + /// + /// The actual result of the request, if available. + /// + /// + /// The type of the result depends on the API endpoint that you've called. + /// [JsonProperty] - private readonly T Result; + public readonly T Result; internal GenericResponse(T result) : base(result != null) => Result = result; internal GenericResponse(bool success, string message) : base(success, message) { } @@ -33,11 +40,21 @@ namespace ArchiSteamFarm.IPC.Responses { } public class GenericResponse { + /// + /// A message that describes what happened with the request, if available. + /// + /// + /// This property will provide exact reason for majority of expected failures. + /// [JsonProperty] - private readonly string Message; + public readonly string Message; - [JsonProperty] - private readonly bool Success; + /// + /// Boolean type that specifies if the request has succeeded. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly bool Success; internal GenericResponse(bool success, string message = null) { Success = success; diff --git a/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs b/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs index 9d29cf78b..9f51ed5a0 100644 --- a/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs @@ -20,21 +20,38 @@ // limitations under the License. using System; +using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Responses { public sealed class GitHubReleaseResponse { - [JsonProperty] - private readonly string ChangelogHTML; + /// + /// Changelog of the release rendered in HTML. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly string ChangelogHTML; - [JsonProperty] - private readonly DateTime ReleasedAt; + /// + /// Date of the release. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly DateTime ReleasedAt; - [JsonProperty] - private readonly bool Stable; + /// + /// Boolean value that specifies whether the build is stable or not (pre-release). + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly bool Stable; - [JsonProperty] - private readonly string Version; + /// + /// Version of the release. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly string Version; internal GitHubReleaseResponse(GitHub.ReleaseResponse releaseResponse) { if (releaseResponse == null) { diff --git a/ArchiSteamFarm/IPC/Responses/TypeResponse.cs b/ArchiSteamFarm/IPC/Responses/TypeResponse.cs index 22f204287..4177237df 100644 --- a/ArchiSteamFarm/IPC/Responses/TypeResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/TypeResponse.cs @@ -21,15 +21,29 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; namespace ArchiSteamFarm.IPC.Responses { public sealed class TypeResponse { - [JsonProperty] - private readonly Dictionary Body; + /// + /// A string-string map representing a decomposition of given type. + /// + /// + /// The actual structure of this field depends on the type that was requested. You can determine that type based on 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. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly Dictionary Body; - [JsonProperty] - private readonly TypeProperties Properties; + /// + /// Metadata of given type. + /// + [JsonProperty(Required = Required.Always)] + [Required] + public readonly TypeProperties Properties; internal TypeResponse(Dictionary body, TypeProperties properties) { if ((body == null) || (properties == null)) { @@ -40,15 +54,33 @@ namespace ArchiSteamFarm.IPC.Responses { Properties = properties; } - internal sealed class TypeProperties { + public sealed class TypeProperties { + /// + /// Base type of given type, if available. + /// + /// + /// This can be used for determining how should be interpreted. + /// [JsonProperty] - private readonly string BaseType; + public readonly string BaseType; + /// + /// Custom attributes of given type, if available. + /// + /// + /// This can be used for determining main enum type if is . + /// [JsonProperty] - private readonly HashSet CustomAttributes; + public readonly HashSet CustomAttributes; + /// + /// Underlying type of given type, if available. + /// + /// + /// This can be used for determining underlying enum type if is . + /// [JsonProperty] - private readonly string UnderlyingType; + public readonly string UnderlyingType; internal TypeProperties(string baseType = null, HashSet customAttributes = null, string underlyingType = null) { BaseType = baseType; diff --git a/ArchiSteamFarm/IPC/Startup.cs b/ArchiSteamFarm/IPC/Startup.cs index db1d7385f..733f18c4f 100644 --- a/ArchiSteamFarm/IPC/Startup.cs +++ b/ArchiSteamFarm/IPC/Startup.cs @@ -20,6 +20,7 @@ // limitations under the License. using System; +using System.IO; using ArchiSteamFarm.IPC.Middleware; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -29,6 +30,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using Swashbuckle.AspNetCore.Swagger; namespace ArchiSteamFarm.IPC { internal sealed class Startup { @@ -64,6 +66,12 @@ namespace ArchiSteamFarm.IPC { // We need MVC for /Api 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 / 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 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 IMvcCoreBuilder mvc = services.AddMvcCore(); + // Add API explorer for swagger + mvc.AddApiExplorer(); + // Use latest compatibility version for MVC mvc.SetCompatibilityVersion(CompatibilityVersion.Latest); diff --git a/ArchiSteamFarm/SharedInfo.cs b/ArchiSteamFarm/SharedInfo.cs index ac9d846c2..84475605b 100644 --- a/ArchiSteamFarm/SharedInfo.cs +++ b/ArchiSteamFarm/SharedInfo.cs @@ -29,6 +29,7 @@ namespace ArchiSteamFarm { internal const ulong ArchiSteamID = 76561198006963719; internal const string ASF = nameof(ASF); internal const ulong ASFGroupSteamID = 103582791440160998; + internal const string AssemblyDocumentation = AssemblyName + ".xml"; internal const string AssemblyName = nameof(ArchiSteamFarm); internal const string ConfigDirectory = "config"; internal const string ConfigExtension = ".json";