From 3f9833745967e61c34291e190f92aa886534db79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Domeradzki?= Date: Sun, 5 Jan 2025 02:32:05 +0100 Subject: [PATCH] Closes #3343 --- .../ExamplePlugin.cs | 5 +- .../PeriodicGCPlugin.cs | 3 - .../SignInWithSteamPlugin.cs | 3 - ...amFarm.OfficialPlugins.ItemsMatcher.csproj | 1 - .../ItemsMatcherPlugin.cs | 3 - .../MobileAuthenticatorPlugin.cs | 3 - .../MonitoringPlugin.cs | 3 - ...rm.OfficialPlugins.SteamTokenDumper.csproj | 1 - .../SteamTokenDumperController.cs | 4 +- .../SteamTokenDumperPlugin.cs | 3 - ArchiSteamFarm/ArchiSteamFarm.csproj | 5 +- ArchiSteamFarm/IPC/ArchiKestrel.cs | 86 +++++-------------- .../IPC/Controllers/Api/ASFController.cs | 32 ++----- .../IPC/Controllers/Api/ArchiController.cs | 12 ++- .../IPC/Controllers/Api/BotController.cs | 74 ++++------------ .../IPC/Controllers/Api/CommandController.cs | 11 +-- .../IPC/Controllers/Api/GitHubController.cs | 34 ++------ .../Controllers/Api/HealthCheckController.cs | 3 + .../IPC/Controllers/Api/IPCBansController.cs | 13 +-- .../IPC/Controllers/Api/NLogController.cs | 20 ++--- .../IPC/Controllers/Api/PluginsController.cs | 9 +- .../IPC/Controllers/Api/StorageController.cs | 14 +-- .../Controllers/Api/StructureController.cs | 9 +- .../Api/TwoFactorAuthenticationController.cs | 23 ++--- .../IPC/Controllers/Api/TypeController.cs | 9 +- .../CustomAttributesSchemaFilter.cs | 52 ----------- .../Integration/ReadOnlyFixesSchemaFilter.cs | 42 --------- .../IPC/OpenApi/DocumentTransformer.cs | 58 +++++++++++++ .../IPC/OpenApi/OperationTransformer.cs | 52 +++++++++++ .../SchemaTransformer.cs} | 50 ++++++++--- .../IPC/Requests/ASFEncryptRequest.cs | 9 +- ArchiSteamFarm/IPC/Requests/ASFHashRequest.cs | 9 +- ArchiSteamFarm/IPC/Requests/ASFRequest.cs | 5 +- .../IPC/Requests/BotAddLicenseRequest.cs | 9 +- .../BotGamesToRedeemInBackgroundRequest.cs | 9 +- .../IPC/Requests/BotInputRequest.cs | 9 +- .../IPC/Requests/BotPauseRequest.cs | 9 +- .../IPC/Requests/BotRedeemRequest.cs | 5 +- .../IPC/Requests/BotRenameRequest.cs | 5 +- ArchiSteamFarm/IPC/Requests/BotRequest.cs | 5 +- ArchiSteamFarm/IPC/Requests/CommandRequest.cs | 5 +- .../IPC/Requests/PluginUpdateRequest.cs | 13 +-- ...actorAuthenticationConfirmationsRequest.cs | 21 ++--- ArchiSteamFarm/IPC/Requests/UpdateRequest.cs | 9 +- ArchiSteamFarm/IPC/Responses/ASFResponse.cs | 29 ++----- .../IPC/Responses/AddLicenseResult.cs | 2 + .../IPC/Responses/BotAddLicenseResponse.cs | 9 +- .../GamesToRedeemInBackgroundResponse.cs | 9 +- .../IPC/Responses/GenericResponse.cs | 19 +--- .../IPC/Responses/GitHubReleaseResponse.cs | 17 ++-- .../IPC/Responses/HealthCheckResponse.cs | 4 +- ArchiSteamFarm/IPC/Responses/LogResponse.cs | 9 +- .../IPC/Responses/StatusCodeResponse.cs | 9 +- .../IPC/Responses/TypeProperties.cs | 22 +---- ArchiSteamFarm/IPC/Responses/TypeResponse.cs | 14 +-- ArchiSteamFarm/Plugins/Interfaces/IPlugin.cs | 3 - ArchiSteamFarm/SharedInfo.cs | 1 - ArchiSteamFarm/Steam/Bot.cs | 7 -- ArchiSteamFarm/Steam/Cards/CardsFarmer.cs | 3 - ArchiSteamFarm/Steam/Cards/Game.cs | 4 +- ArchiSteamFarm/Steam/Data/LicenseData.cs | 6 ++ Directory.Packages.props | 4 +- 62 files changed, 342 insertions(+), 588 deletions(-) delete mode 100644 ArchiSteamFarm/IPC/Integration/CustomAttributesSchemaFilter.cs delete mode 100644 ArchiSteamFarm/IPC/Integration/ReadOnlyFixesSchemaFilter.cs create mode 100644 ArchiSteamFarm/IPC/OpenApi/DocumentTransformer.cs create mode 100644 ArchiSteamFarm/IPC/OpenApi/OperationTransformer.cs rename ArchiSteamFarm/IPC/{Integration/EnumSchemaFilter.cs => OpenApi/SchemaTransformer.cs} (62%) diff --git a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs index f0386e83b0ea4..d5945d0ece2b8 100644 --- a/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs +++ b/ArchiSteamFarm.CustomPlugins.ExamplePlugin/ExamplePlugin.cs @@ -50,17 +50,16 @@ internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection, // This is used for identification purposes, typically you want to use a friendly name of your plugin here, such as the name of your main class // Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place [JsonInclude] - [Required] public string Name => nameof(ExamplePlugin); // This will be displayed to the user and written in the log file, typically you should point it to the version of your library, but alternatively you can do some more advanced logic if you'd like to // Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place [JsonInclude] - [Required] public Version Version => typeof(ExamplePlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); - // Plugins can expose custom properties for our GET /Api/Plugins API call, simply annotate them with [JsonProperty] (or keep public) + // Plugins can expose custom properties for our GET /Api/Plugins API call, simply annotate them with [JsonInclude] (or keep public) [JsonInclude] + [JsonRequired] [Required] public bool CustomIsEnabledField { get; private init; } = true; diff --git a/ArchiSteamFarm.CustomPlugins.PeriodicGC/PeriodicGCPlugin.cs b/ArchiSteamFarm.CustomPlugins.PeriodicGC/PeriodicGCPlugin.cs index 48a071c0b38e8..2f537d37137a6 100644 --- a/ArchiSteamFarm.CustomPlugins.PeriodicGC/PeriodicGCPlugin.cs +++ b/ArchiSteamFarm.CustomPlugins.PeriodicGC/PeriodicGCPlugin.cs @@ -22,7 +22,6 @@ // limitations under the License. using System; -using System.ComponentModel.DataAnnotations; using System.Composition; using System.Runtime; using System.Text.Json.Serialization; @@ -43,11 +42,9 @@ internal sealed class PeriodicGCPlugin : IPlugin { private static readonly Timer PeriodicGCTimer = new(PerformGC); [JsonInclude] - [Required] public string Name => nameof(PeriodicGCPlugin); [JsonInclude] - [Required] public Version Version => typeof(PeriodicGCPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); public Task OnLoaded() { diff --git a/ArchiSteamFarm.CustomPlugins.SignInWithSteam/SignInWithSteamPlugin.cs b/ArchiSteamFarm.CustomPlugins.SignInWithSteam/SignInWithSteamPlugin.cs index 8ddf027bdd31e..11868c23fa1eb 100644 --- a/ArchiSteamFarm.CustomPlugins.SignInWithSteam/SignInWithSteamPlugin.cs +++ b/ArchiSteamFarm.CustomPlugins.SignInWithSteam/SignInWithSteamPlugin.cs @@ -22,7 +22,6 @@ // limitations under the License. using System; -using System.ComponentModel.DataAnnotations; using System.Composition; using System.Text.Json.Serialization; using System.Threading.Tasks; @@ -36,11 +35,9 @@ namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam; [UsedImplicitly] internal sealed class SignInWithSteamPlugin : IPlugin { [JsonInclude] - [Required] public string Name => nameof(SignInWithSteamPlugin); [JsonInclude] - [Required] public Version Version => typeof(SignInWithSteamPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); public Task OnLoaded() { diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ArchiSteamFarm.OfficialPlugins.ItemsMatcher.csproj b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ArchiSteamFarm.OfficialPlugins.ItemsMatcher.csproj index 193894b6324f1..5b849c8e8d4ea 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ArchiSteamFarm.OfficialPlugins.ItemsMatcher.csproj +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ArchiSteamFarm.OfficialPlugins.ItemsMatcher.csproj @@ -7,7 +7,6 @@ - diff --git a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ItemsMatcherPlugin.cs b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ItemsMatcherPlugin.cs index 680ece5c7fc79..4d9278789cc7b 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ItemsMatcherPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ItemsMatcherPlugin.cs @@ -25,7 +25,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.Composition; using System.Linq; using System.Text.Json; @@ -47,11 +46,9 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, I internal static readonly ConcurrentDictionary RemoteCommunications = new(); [JsonInclude] - [Required] public override string Name => nameof(ItemsMatcherPlugin); [JsonInclude] - [Required] public override Version Version => typeof(ItemsMatcherPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); public async Task OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) { diff --git a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs index d8f720d085d39..d7a11386068b3 100644 --- a/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.MobileAuthenticator/MobileAuthenticatorPlugin.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -42,11 +41,9 @@ namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator; [SuppressMessage("ReSharper", "MemberCanBeFileLocal")] internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, IBotSteamClient { [JsonInclude] - [Required] public override string Name => nameof(MobileAuthenticatorPlugin); [JsonInclude] - [Required] public override Version Version => typeof(MobileAuthenticatorPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); public async Task OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) { diff --git a/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs b/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs index b1943068d6a5e..326de0e2d6783 100644 --- a/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs @@ -25,7 +25,6 @@ using System.Collections.Concurrent; using System.Collections.Frozen; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Metrics; @@ -73,13 +72,11 @@ internal sealed class MonitoringPlugin : OfficialPlugin, IDisposable, IOfficialG private static FrozenSet>? PluginMeasurements; [JsonInclude] - [Required] public override string Name => nameof(MonitoringPlugin); public string RepositoryName => SharedInfo.GithubRepo; [JsonInclude] - [Required] public override Version Version => typeof(MonitoringPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); private readonly ConcurrentDictionary TradeStatistics = new(); diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.csproj b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.csproj index a521691cb580d..14a7e006ffe96 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.csproj +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.csproj @@ -7,7 +7,6 @@ - diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperController.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperController.cs index eaa51ea169fd3..ec4088e22eca0 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperController.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperController.cs @@ -23,8 +23,8 @@ using System.Net; using ArchiSteamFarm.IPC.Controllers.Api; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Swashbuckle.AspNetCore.Annotations; namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper; @@ -32,6 +32,6 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper; public sealed class SteamTokenDumperController : ArchiController { [HttpGet(nameof(GlobalConfigExtension))] [ProducesResponseType((int) HttpStatusCode.OK)] - [SwaggerOperation(Tags = [nameof(GlobalConfigExtension)])] + [Tags(nameof(GlobalConfigExtension))] public ActionResult Get() => Ok(new GlobalConfigExtension()); } diff --git a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs index b0b1ab868c2c7..cc8b67aaedc8a 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs @@ -26,7 +26,6 @@ using System.Collections.Frozen; using System.Collections.Generic; using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.Composition; using System.Linq; using System.Net; @@ -65,11 +64,9 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC private static DateTimeOffset LastUploadAt = DateTimeOffset.MinValue; [JsonInclude] - [Required] public override string Name => nameof(SteamTokenDumperPlugin); [JsonInclude] - [Required] public override Version Version => typeof(SteamTokenDumperPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version)); public Task GetPreferredChangeNumberToStartFrom() => Task.FromResult(GlobalCache?.LastChangeNumber ?? 0); diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj index edacba87b8e2a..95541f9b9c55b 100644 --- a/ArchiSteamFarm/ArchiSteamFarm.csproj +++ b/ArchiSteamFarm/ArchiSteamFarm.csproj @@ -1,7 +1,6 @@ $(DefaultItemExcludes);config/**;debug/**;logs/**;overlay/** - true Exe @@ -11,13 +10,13 @@ + - - + diff --git a/ArchiSteamFarm/IPC/ArchiKestrel.cs b/ArchiSteamFarm/IPC/ArchiKestrel.cs index 807b40bcfe18e..7fb3e2560f010 100644 --- a/ArchiSteamFarm/IPC/ArchiKestrel.cs +++ b/ArchiSteamFarm/IPC/ArchiKestrel.cs @@ -35,6 +35,7 @@ using ArchiSteamFarm.Helpers.Json; using ArchiSteamFarm.IPC.Controllers.Api; using ArchiSteamFarm.IPC.Integration; +using ArchiSteamFarm.IPC.OpenApi; using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; using ArchiSteamFarm.NLog.Targets; @@ -52,7 +53,6 @@ using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; -using Microsoft.OpenApi.Models; using NLog.Web; using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; @@ -118,7 +118,7 @@ internal static async Task Stop() { [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", Justification = "PathString is a primitive, it's unlikely to be trimmed to the best of our knowledge")] [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL3000", Justification = "We don't care about trimmed assemblies, as we need it to work only with the known (used) ones")] - private static void ConfigureApp([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] ConfigurationManager configuration, IApplicationBuilder app) { + private static void ConfigureApp([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] ConfigurationManager configuration, WebApplication app) { ArgumentNullException.ThrowIfNull(configuration); ArgumentNullException.ThrowIfNull(app); @@ -252,10 +252,10 @@ private static void ConfigureApp([SuppressMessage("ReSharper", "SuggestBaseTypeF } // Finally register proper API endpoints once we're done with routing - app.UseEndpoints(static endpoints => endpoints.MapControllers()); + app.MapControllers(); - // Add support for swagger, responsible for automatic API documentation generation, this should be on the end, once we're done with API - app.UseSwagger(); + // Add support for OpenAPI, responsible for automatic API documentation generation, this should be on the end, once we're done with API + app.MapOpenApi("/swagger/{documentName}/swagger.json"); // Add support for swagger UI, this should be after swagger, obviously app.UseSwaggerUI( @@ -331,66 +331,12 @@ private static void ConfigureServices([SuppressMessage("ReSharper", "SuggestBase services.AddCors(static options => options.AddDefaultPolicy(static policyBuilder => policyBuilder.AllowAnyOrigin())); } - // Add support for swagger, responsible for automatic API documentation generation - services.AddSwaggerGen( - static options => { - options.AddSecurityDefinition( - nameof(GlobalConfig.IPCPassword), new OpenApiSecurityScheme { - Description = $"{nameof(GlobalConfig.IPCPassword)} authentication using request headers. Check {SharedInfo.ProjectURL}/wiki/IPC#authentication for more info.", - In = ParameterLocation.Header, - Name = ApiAuthenticationMiddleware.HeadersField, - Type = SecuritySchemeType.ApiKey - } - ); - - options.AddSecurityRequirement( - new OpenApiSecurityRequirement { - { - new OpenApiSecurityScheme { - Reference = new OpenApiReference { - Id = nameof(GlobalConfig.IPCPassword), - Type = ReferenceType.SecurityScheme - } - }, - - [] - } - } - ); - - // We require custom schema IDs due to conflicting type names, choosing the proper one is tricky as there is no good answer and any kind of convention has a potential to create conflict - // FullName and Name both do, ToString() for unknown to me reason doesn't, and I don't have courage to call our WebUtilities.GetUnifiedName() better than what .NET ships with (because it isn't) - // Let's use ToString() until we find a good enough reason to change it, also, the name must pass ^[a-zA-Z0-9.-_]+$ regex - options.CustomSchemaIds(static type => type.ToString().Replace('+', '-')); - - options.EnableAnnotations(true, true); - - options.SchemaFilter(); - options.SchemaFilter(); - options.SchemaFilter(); - - options.SwaggerDoc( - SharedInfo.ASF, new OpenApiInfo { - Contact = new OpenApiContact { - Name = SharedInfo.GithubRepo, - Url = new Uri(SharedInfo.ProjectURL) - }, - - License = new OpenApiLicense { - Name = SharedInfo.LicenseName, - Url = new Uri(SharedInfo.LicenseURL) - }, - - Title = $"{SharedInfo.AssemblyName} API", - Version = SharedInfo.Version.ToString() - } - ); - - string xmlDocumentationFile = Path.Combine(AppContext.BaseDirectory, SharedInfo.AssemblyDocumentation); - - if (File.Exists(xmlDocumentationFile)) { - options.IncludeXmlComments(xmlDocumentationFile); - } + // Add support for OpenAPI, responsible for automatic API documentation generation + services.AddOpenApi( + SharedInfo.ASF, static options => { + options.AddDocumentTransformer(); + options.AddOperationTransformer(); + options.AddSchemaTransformer(); } ); @@ -406,6 +352,16 @@ private static void ConfigureServices([SuppressMessage("ReSharper", "SuggestBase } } + services.ConfigureHttpJsonOptions( + static options => { + JsonSerializerOptions jsonSerializerOptions = Debugging.IsUserDebugging ? JsonUtilities.IndentedJsonSerialierOptions : JsonUtilities.DefaultJsonSerialierOptions; + + options.SerializerOptions.PropertyNamingPolicy = jsonSerializerOptions.PropertyNamingPolicy; + options.SerializerOptions.TypeInfoResolver = jsonSerializerOptions.TypeInfoResolver; + options.SerializerOptions.WriteIndented = jsonSerializerOptions.WriteIndented; + } + ); + // We need MVC for /Api, but we're going to use only a small subset of all available features IMvcBuilder mvc = services.AddControllers(); diff --git a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs index 7474f6abaab55..f3cc4af2f7b80 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs @@ -34,6 +34,7 @@ using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam.Interaction; using ArchiSteamFarm.Storage; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; @@ -51,10 +52,7 @@ public ASFController(IHostApplicationLifetime applicationLifetime) { ApplicationLifetime = applicationLifetime; } - /// - /// Encrypts data with ASF encryption mechanisms using provided details. - /// - [Consumes("application/json")] + [EndpointSummary("Encrypts data with ASF encryption mechanisms using provided details.")] [HttpPost("Encrypt")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -70,9 +68,7 @@ public ActionResult ASFEncryptPost([FromBody] ASFEncryptRequest return Ok(new GenericResponse(encryptedString)); } - /// - /// Fetches common info related to ASF as a whole. - /// + [EndpointSummary("Fetches common info related to ASF as a whole")] [HttpGet] [ProducesResponseType>((int) HttpStatusCode.OK)] public ActionResult> ASFGet() { @@ -87,10 +83,7 @@ public ActionResult> ASFGet() { return Ok(new GenericResponse(result)); } - /// - /// Hashes data with ASF hashing mechanisms using provided details. - /// - [Consumes("application/json")] + [EndpointSummary("Hashes data with ASF hashing mechanisms using provided details")] [HttpPost("Hash")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -106,10 +99,7 @@ public ActionResult ASFHashPost([FromBody] ASFHashRequest reque return Ok(new GenericResponse(hash)); } - /// - /// Updates ASF's global config. - /// - [Consumes("application/json")] + [EndpointSummary("Updates ASF's global config")] [HttpPost] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -163,9 +153,7 @@ public async Task> ASFPost([FromBody] ASFRequest r return Ok(new GenericResponse(result)); } - /// - /// Makes ASF shutdown itself. - /// + [EndpointSummary("Makes ASF shutdown itself")] [HttpPost("Exit")] [ProducesResponseType((int) HttpStatusCode.OK)] public ActionResult ExitPost() { @@ -174,9 +162,7 @@ public ActionResult ExitPost() { return Ok(new GenericResponse(success, message)); } - /// - /// Makes ASF restart itself. - /// + [EndpointSummary("Makes ASF restart itself")] [HttpPost("Restart")] [ProducesResponseType((int) HttpStatusCode.OK)] public ActionResult RestartPost() { @@ -185,9 +171,7 @@ public ActionResult RestartPost() { return Ok(new GenericResponse(success, message)); } - /// - /// Makes ASF update itself. - /// + [EndpointSummary("Makes ASF update itself")] [HttpPost("Update")] [ProducesResponseType>((int) HttpStatusCode.OK)] public async Task>> UpdatePost([FromBody] UpdateRequest request) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/ArchiController.cs b/ArchiSteamFarm/IPC/Controllers/Api/ArchiController.cs index feac5df34cbe6..026db6a5264c8 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/ArchiController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/ArchiController.cs @@ -23,18 +23,16 @@ using System.Net; using ArchiSteamFarm.IPC.Responses; -using ArchiSteamFarm.Storage; using Microsoft.AspNetCore.Mvc; -using Swashbuckle.AspNetCore.Annotations; namespace ArchiSteamFarm.IPC.Controllers.Api; [ApiController] [Produces("application/json")] +[ProducesResponseType((int) HttpStatusCode.BadRequest)] +[ProducesResponseType>((int) HttpStatusCode.Unauthorized)] +[ProducesResponseType>((int) HttpStatusCode.Forbidden)] +[ProducesResponseType((int) HttpStatusCode.InternalServerError)] +[ProducesResponseType((int) HttpStatusCode.ServiceUnavailable)] [Route("Api")] -[SwaggerResponse((int) HttpStatusCode.BadRequest, $"The request has failed, check {nameof(GenericResponse.Message)} from response body for actual reason. Most of the time this is ASF, understanding the request, but refusing to execute it due to provided reason.", typeof(GenericResponse))] -[SwaggerResponse((int) HttpStatusCode.Unauthorized, $"ASF has {nameof(GlobalConfig.IPCPassword)} set, but you've failed to authenticate. See {SharedInfo.ProjectURL}/wiki/IPC#authentication.", typeof(GenericResponse))] -[SwaggerResponse((int) HttpStatusCode.Forbidden, $"ASF lacks {nameof(GlobalConfig.IPCPassword)} and you're not permitted to access the API, or {nameof(GlobalConfig.IPCPassword)} is set and you've failed to authenticate too many times (try again in an hour). See {SharedInfo.ProjectURL}/wiki/IPC#authentication.", typeof(GenericResponse))] -[SwaggerResponse((int) HttpStatusCode.InternalServerError, "ASF has encountered an unexpected error while serving the request. The log may include extra info related to this issue.")] -[SwaggerResponse((int) HttpStatusCode.ServiceUnavailable, "ASF has encountered an error while requesting a third-party resource. Try again later.")] public abstract class ArchiController : ControllerBase; diff --git a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs index 7e9c3b49b860e..eaadd87527b1f 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -34,6 +34,7 @@ using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam; using ArchiSteamFarm.Steam.Storage; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using SteamKit2; using SteamKit2.Internal; @@ -42,10 +43,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/Bot")] public sealed class BotController : ArchiController { - /// - /// Adds (free) licenses on given bots. - /// - [Consumes("application/json")] + [EndpointSummary("Adds (free) licenses on given bots")] [HttpPost("{botNames:required}/AddLicense")] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -74,9 +72,7 @@ public async Task> AddLicensePost(string botNames, return Ok(new GenericResponse>(result)); } - /// - /// Deletes all files related to given bots. - /// + [EndpointSummary("Deletes all files related to given bots")] [HttpDelete("{botNames:required}")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -94,9 +90,7 @@ public async Task> BotDelete(string botNames) { return Ok(new GenericResponse(results.All(static result => result))); } - /// - /// Fetches common info related to given bots. - /// + [EndpointSummary("Fetches common info related to given bots")] [HttpGet("{botNames:required}")] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -112,10 +106,7 @@ public ActionResult BotGet(string botNames) { return Ok(new GenericResponse>(bots.Where(static bot => !string.IsNullOrEmpty(bot.BotName)).ToDictionary(static bot => bot.BotName, static bot => bot, Bot.BotsComparer))); } - /// - /// Updates bot config of given bot. - /// - [Consumes("application/json")] + [EndpointSummary("Updates bot config of given bot")] [HttpPost("{botNames:required}")] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -185,9 +176,7 @@ public async Task> BotPost(string botNames, [FromB return Ok(new GenericResponse>(result.Values.All(static value => value), result)); } - /// - /// Removes BGR output files of given bots. - /// + [EndpointSummary("Removes BGR output files of given bots")] [HttpDelete("{botNames:required}/GamesToRedeemInBackground")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -205,9 +194,7 @@ public async Task> GamesToRedeemInBackgroundDelete return Ok(results.All(static result => result) ? new GenericResponse(true) : new GenericResponse(false, Strings.WarningFailed)); } - /// - /// Fetches BGR output files of given bots. - /// + [EndpointSummary("Fetches BGR output files of given bots")] [HttpGet("{botNames:required}/GamesToRedeemInBackground")] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -232,10 +219,7 @@ public async Task> GamesToRedeemInBackgroundGet(st return Ok(new GenericResponse>(result)); } - /// - /// Adds keys to redeem using BGR to given bot. - /// - [Consumes("application/json")] + [EndpointSummary("Adds keys to redeem using BGR to given bot")] [HttpPost("{botNames:required}/GamesToRedeemInBackground")] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -270,10 +254,7 @@ public async Task> GamesToRedeemInBackgroundPost(s return Ok(new GenericResponse>(result)); } - /// - /// Provides input value to given bot for next usage. - /// - [Consumes("application/json")] + [EndpointSummary("Provides input value to given bot for next usage")] [HttpPost("{botNames:required}/Input")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -296,10 +277,7 @@ public async Task> InputPost(string botNames, [Fro return Ok(results.All(static result => result) ? new GenericResponse(true) : new GenericResponse(false, Strings.WarningFailed)); } - /// - /// Pauses given bots. - /// - [Consumes("application/json")] + [EndpointSummary("Pauses given bots")] [HttpPost("{botNames:required}/Pause")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -318,10 +296,7 @@ public async Task> PausePost(string botNames, [Fro return Ok(new GenericResponse(results.All(static result => result.Success), string.Join(Environment.NewLine, results.Select(static result => result.Message)))); } - /// - /// Redeems points on given bots. - /// - [Consumes("application/json")] + [EndpointSummary("Redeems points on given bots")] [HttpPost("{botNames:required}/RedeemPoints/{definitionID:required}")] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -346,14 +321,8 @@ public async Task> RedeemPointsPost(string botName return Ok(new GenericResponse>(result)); } - /// - /// Redeems cd-keys on given bot. - /// - /// - /// Response contains a map that maps each provided cd-key to its redeem result. - /// Redeem result can be a null value, this means that ASF didn't even attempt to send a request (e.g. because of bot not being connected to Steam network). - /// - [Consumes("application/json")] + [EndpointDescription("Response contains a map that maps each provided cd-key to its redeem result. Redeem result can be a null value, this means that ASF didn't even attempt to send a request (e.g. because of bot not being connected to Steam network)")] + [EndpointSummary("Redeems cd-keys on given bot")] [HttpPost("{botNames:required}/Redeem")] [ProducesResponseType>>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -389,10 +358,7 @@ public async Task> RedeemPost(string botNames, [Fr return Ok(new GenericResponse>>(result.Values.SelectMany(static responses => responses.Values).All(static value => value != null), result)); } - /// - /// Renames given bot along with all its related files. - /// - [Consumes("application/json")] + [EndpointSummary("Renames given bot along with all its related files")] [HttpPost("{botName:required}/Rename")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -417,9 +383,7 @@ public async Task> RenamePost(string botName, [Fro return Ok(new GenericResponse(result)); } - /// - /// Resumes given bots. - /// + [EndpointSummary("Resumes given bots")] [HttpPost("{botNames:required}/Resume")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -437,9 +401,7 @@ public async Task> ResumePost(string botNames) { return Ok(new GenericResponse(results.All(static result => result.Success), string.Join(Environment.NewLine, results.Select(static result => result.Message)))); } - /// - /// Starts given bots. - /// + [EndpointSummary("Starts given bots")] [HttpPost("{botNames:required}/Start")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -457,9 +419,7 @@ public async Task> StartPost(string botNames) { return Ok(new GenericResponse(results.All(static result => result.Success), string.Join(Environment.NewLine, results.Select(static result => result.Message)))); } - /// - /// Stops given bots. - /// + [EndpointSummary("Stops given bots")] [HttpPost("{botNames:required}/Stop")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] diff --git a/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs b/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs index bfec118186550..c425eb796b167 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs @@ -31,6 +31,7 @@ using ArchiSteamFarm.Localization; using ArchiSteamFarm.Steam; using ArchiSteamFarm.Storage; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; @@ -46,14 +47,8 @@ public CommandController(IHostApplicationLifetime applicationLifetime) { ApplicationLifetime = applicationLifetime; } - /// - /// 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 - /// - [Consumes("application/json")] + [EndpointDescription($"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 {nameof(GlobalConfig.DefaultBot)}")] + [EndpointSummary("Executes a command")] [HttpPost] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] diff --git a/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs b/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs index 58a35cf6ec29f..046c4a3fd9d9b 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs @@ -32,18 +32,15 @@ using ArchiSteamFarm.Web; using ArchiSteamFarm.Web.GitHub; using ArchiSteamFarm.Web.GitHub.Data; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/WWW/GitHub")] public sealed class GitHubController : ArchiController { - /// - /// Fetches the most recent 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 endpoints as they can disappear and change anytime. - /// + [EndpointDescription("This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime")] + [EndpointSummary("Fetches the most recent GitHub release of ASF project")] [HttpGet("Release")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.ServiceUnavailable)] @@ -55,12 +52,8 @@ public async Task> GitHubReleaseGet() { return releaseResponse != null ? Ok(new GenericResponse(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries))); } - /// - /// Fetches specific GitHub release of ASF project. Use "latest" for latest stable release. - /// - /// - /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. - /// + [EndpointDescription("This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime")] + [EndpointSummary("Fetches specific GitHub release of ASF project. Use \"latest\" for latest stable release")] [HttpGet("Release/{version:required}")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -90,12 +83,8 @@ public async Task> GitHubReleaseGet(string version return releaseResponse != null ? Ok(new GenericResponse(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries))); } - /// - /// Fetches history of specific GitHub page from ASF project. - /// - /// - /// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. - /// + [EndpointDescription("This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime")] + [EndpointSummary("Fetches history of specific GitHub page from ASF project")] [HttpGet("Wiki/History/{page:required}")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -110,13 +99,8 @@ public async Task> GitHubWikiHistoryGet(string pag return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, Strings.FormatErrorIsInvalid(nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, Strings.FormatErrorRequestFailedTooManyTimes(WebBrowser.MaxTries))); } - /// - /// Fetches specific GitHub page 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 endpoints as they can disappear and change anytime. - /// Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well. - /// + [EndpointDescription("This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime. Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well")] + [EndpointSummary("Fetches specific GitHub page of ASF project")] [HttpGet("Wiki/Page/{page:required}")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] diff --git a/ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs b/ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs index bfbe18bec8291..b9e7919f00c14 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs @@ -26,6 +26,7 @@ using System.Threading; using System.Threading.Tasks; using ArchiSteamFarm.IPC.Responses; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -43,6 +44,8 @@ public HealthCheckController(HealthCheckService healthCheckService) { HealthCheckService = healthCheckService; } + [EndpointDescription("This endpoint can be called in order to easily check whether the program is up")] + [EndpointSummary("Fetches current application's status")] [HttpGet] [ProducesResponseType(typeof(HealthCheckResponse), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(HealthCheckResponse), (int) HttpStatusCode.ServiceUnavailable)] diff --git a/ArchiSteamFarm/IPC/Controllers/Api/IPCBansController.cs b/ArchiSteamFarm/IPC/Controllers/Api/IPCBansController.cs index b5661774545f9..a0cd500e1deb6 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/IPCBansController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/IPCBansController.cs @@ -28,15 +28,14 @@ using ArchiSteamFarm.IPC.Integration; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/IPC/Bans")] public sealed class IPCBansController : ArchiController { - /// - /// Clears the list of all IP addresses currently blocked by ASFs IPC module - /// + [EndpointSummary("Clears the list of all IP addresses currently blocked by ASFs IPC module")] [HttpDelete] [ProducesResponseType((int) HttpStatusCode.OK)] public ActionResult Delete() { @@ -45,9 +44,7 @@ public ActionResult Delete() { return Ok(new GenericResponse(true)); } - /// - /// Removes an IP address from the list of addresses currently blocked by ASFs IPC module - /// + [EndpointSummary("Removes an IP address from the list of addresses currently blocked by ASFs IPC module")] [HttpDelete("{ipAddress:required}")] [ProducesResponseType((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -67,9 +64,7 @@ public ActionResult DeleteSpecific(string ipAddress) { return Ok(new GenericResponse(true)); } - /// - /// Gets all IP addresses currently blocked by ASFs IPC module - /// + [EndpointSummary("Gets all IP addresses currently blocked by ASFs IPC module")] [HttpGet] [ProducesResponseType>>((int) HttpStatusCode.OK)] public ActionResult>> Get() => Ok(new GenericResponse>(ApiAuthenticationMiddleware.GetCurrentlyBannedIPs().Select(static ip => ip.ToString()).ToHashSet())); diff --git a/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs b/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs index 42f7709054494..d13e1474ccdb1 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/NLogController.cs @@ -6,7 +6,7 @@ // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- // | -// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki +// Copyright 2015-2025 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net // | // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +24,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Net; using System.Net.WebSockets; @@ -37,6 +38,7 @@ using ArchiSteamFarm.NLog; using ArchiSteamFarm.NLog.Targets; using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; @@ -54,16 +56,12 @@ public NLogController(IHostApplicationLifetime applicationLifetime) { ApplicationLifetime = applicationLifetime; } - /// - /// Fetches ASF log file, this works on assumption that the log file is in fact generated, as user could disable it through custom configuration. - /// - /// Maximum amount of lines from the log file returned. The respone naturally might have less amount than specified, if you've read whole file already. - /// Ending index, used for pagination. Omit it for the first request, then initialize to TotalLines returned, and on every following request subtract count that you've used in the previous request from it until you hit 0 or less, which means you've read whole file already. + [EndpointSummary("Fetches ASF log file, this works on assumption that the log file is in fact generated, as user could disable it through custom configuration")] [HttpGet("File")] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] [ProducesResponseType((int) HttpStatusCode.ServiceUnavailable)] - public async Task> FileGet(int count = 100, int lastAt = 0) { + public async Task> FileGet([Description("Maximum amount of lines from the log file returned. The respone naturally might have less amount than specified, if you've read whole file already")] int count = 100, [Description("Ending index, used for pagination. Omit it for the first request, then initialize to TotalLines returned, and on every following request subtract count that you've used in the previous request from it until you hit 0 or less, which means you've read whole file already")] int lastAt = 0) { if (count <= 0) { return BadRequest(new GenericResponse(false, Strings.FormatErrorIsInvalid(nameof(count)))); } @@ -91,12 +89,8 @@ public async Task> FileGet(int count = 100, int la return Ok(new GenericResponse(new LogResponse(lines.Length, lines[startFrom..lastAt]))); } - /// - /// Fetches ASF log in realtime. - /// - /// - /// This API endpoint requires a websocket connection. - /// + [EndpointDescription("This API endpoint requires a websocket connection")] + [EndpointSummary("Fetches ASF log in realtime")] [HttpGet] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] diff --git a/ArchiSteamFarm/IPC/Controllers/Api/PluginsController.cs b/ArchiSteamFarm/IPC/Controllers/Api/PluginsController.cs index d7a286dcc70fb..d975a5b329b5a 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/PluginsController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/PluginsController.cs @@ -31,15 +31,14 @@ using ArchiSteamFarm.Plugins; using ArchiSteamFarm.Plugins.Interfaces; using ArchiSteamFarm.Steam.Interaction; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/Plugins")] public sealed class PluginsController : ArchiController { - /// - /// Gets active plugins loaded into the process. - /// + [EndpointSummary("Gets active plugins loaded into the process")] [HttpGet] [ProducesResponseType>>((int) HttpStatusCode.OK)] public ActionResult>> PluginsGet([FromQuery] bool official = true, [FromQuery] bool custom = true) { @@ -60,9 +59,7 @@ public ActionResult>> PluginsGet([F return Ok(new GenericResponse>(result)); } - /// - /// Makes ASF update selected plugins. - /// + [EndpointSummary("Makes ASF update selected plugins")] [HttpPost("Update")] [ProducesResponseType>((int) HttpStatusCode.OK)] public async Task>> UpdatePost([FromBody] PluginUpdateRequest request) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/StorageController.cs b/ArchiSteamFarm/IPC/Controllers/Api/StorageController.cs index a8d842c13cd87..246f942b1bcb9 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/StorageController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/StorageController.cs @@ -26,15 +26,14 @@ using System.Text.Json; using ArchiSteamFarm.Core; using ArchiSteamFarm.IPC.Responses; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/Storage/{key:required}")] public sealed class StorageController : ArchiController { - /// - /// Deletes entry under specified key from ASF's persistent KeyValue JSON storage. - /// + [EndpointSummary("Deletes entry under specified key from ASF's persistent KeyValue JSON storage")] [HttpDelete] [ProducesResponseType((int) HttpStatusCode.OK)] public ActionResult StorageDelete(string key) { @@ -49,9 +48,7 @@ public ActionResult StorageDelete(string key) { return Ok(new GenericResponse(true)); } - /// - /// Loads entry under specified key from ASF's persistent KeyValue JSON storage. - /// + [EndpointSummary("Loads entry under specified key from ASF's persistent KeyValue JSON storage")] [HttpGet] [ProducesResponseType>((int) HttpStatusCode.OK)] public ActionResult StorageGet(string key) { @@ -66,10 +63,7 @@ public ActionResult StorageGet(string key) { return Ok(new GenericResponse(true, value.ValueKind != JsonValueKind.Undefined ? value : null)); } - /// - /// Saves entry under specified key in ASF's persistent KeyValue JSON storage. - /// - [Consumes("application/json")] + [EndpointSummary("Saves entry under specified key in ASF's persistent KeyValue JSON storage")] [HttpPost] [ProducesResponseType((int) HttpStatusCode.OK)] public ActionResult StoragePost(string key, [FromBody] JsonElement value) { diff --git a/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs b/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs index 81bda514ff7bf..39334d4cf4eea 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs @@ -26,18 +26,15 @@ using System.Net; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/Structure")] public sealed class StructureController : ArchiController { - /// - /// Fetches structure of given type. - /// - /// - /// Structure is defined as a representation of given object in its default state. - /// + [EndpointDescription("Structure is defined as a representation of given object in its default state")] + [EndpointSummary("Fetches structure of given type")] [HttpGet("{structure:required}")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] diff --git a/ArchiSteamFarm/IPC/Controllers/Api/TwoFactorAuthenticationController.cs b/ArchiSteamFarm/IPC/Controllers/Api/TwoFactorAuthenticationController.cs index 5320b5e25c037..692d3493d00c9 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/TwoFactorAuthenticationController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/TwoFactorAuthenticationController.cs @@ -33,15 +33,14 @@ using ArchiSteamFarm.Steam; using ArchiSteamFarm.Steam.Data; using ArchiSteamFarm.Steam.Security; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/Bot/{botNames:required}/TwoFactorAuthentication")] public sealed class TwoFactorAuthenticationController : ArchiController { - /// - /// Fetches pending 2FA confirmations of given bots, requires ASF 2FA module to be active on them. - /// + [EndpointSummary("Fetches pending 2FA confirmations of given bots, requires ASF 2FA module to be active on them")] [HttpGet("Confirmations")] [ProducesResponseType>>>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -66,10 +65,7 @@ public async Task> ConfirmationsGet(string botName return Ok(new GenericResponse>>>(result)); } - /// - /// Handles 2FA confirmations of given bots, requires ASF 2FA module to be active on them. - /// - [Consumes("application/json")] + [EndpointSummary("Handles 2FA confirmations of given bots, requires ASF 2FA module to be active on them")] [HttpPost("Confirmations")] [ProducesResponseType>>>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -99,9 +95,7 @@ public async Task> ConfirmationsPost(string botNam return Ok(new GenericResponse>>>(result)); } - /// - /// Deletes the MobileAuthenticator of given bots if an ASF 2FA module is active on them. - /// + [EndpointSummary("Deletes the MobileAuthenticator of given bots if an ASF 2FA module is active on them")] [HttpDelete] [ProducesResponseType>>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -126,10 +120,7 @@ public async Task> Delete(string botNames) { return Ok(new GenericResponse>>(result)); } - /// - /// Imports a MobileAuthenticator into the ASF 2FA module of a given bot. - /// - [Consumes("application/json")] + [EndpointSummary("Imports a MobileAuthenticator into the ASF 2FA module of a given bot")] [HttpPost] [ProducesResponseType>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] @@ -155,9 +146,7 @@ public async Task> Post(string botNames, [FromBody return Ok(new GenericResponse>(result)); } - /// - /// Fetches 2FA tokens of given bots, requires ASF 2FA module to be active on them. - /// + [EndpointSummary("Fetches 2FA tokens of given bots, requires ASF 2FA module to be active on them")] [HttpGet("Token")] [ProducesResponseType>>>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] diff --git a/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs b/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs index d5c7ce34447be..1176ef2a63cda 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs @@ -32,18 +32,15 @@ using ArchiSteamFarm.Core; using ArchiSteamFarm.IPC.Responses; using ArchiSteamFarm.Localization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ArchiSteamFarm.IPC.Controllers.Api; [Route("Api/Type")] public sealed class TypeController : ArchiController { - /// - /// 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. - /// + [EndpointDescription("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")] + [EndpointSummary("Fetches type info of given type")] [HttpGet("{type:required}")] [ProducesResponseType>((int) HttpStatusCode.OK)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] diff --git a/ArchiSteamFarm/IPC/Integration/CustomAttributesSchemaFilter.cs b/ArchiSteamFarm/IPC/Integration/CustomAttributesSchemaFilter.cs deleted file mode 100644 index e5d9978e2d336..0000000000000 --- a/ArchiSteamFarm/IPC/Integration/CustomAttributesSchemaFilter.cs +++ /dev/null @@ -1,52 +0,0 @@ -// ---------------------------------------------------------------------------------------------- -// _ _ _ ____ _ _____ -// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ -// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ -// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | -// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// ---------------------------------------------------------------------------------------------- -// | -// Copyright 2015-2024 Ł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.Reflection; -using JetBrains.Annotations; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace ArchiSteamFarm.IPC.Integration; - -[UsedImplicitly] -internal sealed class CustomAttributesSchemaFilter : ISchemaFilter { - public void Apply(OpenApiSchema schema, SchemaFilterContext context) { - ArgumentNullException.ThrowIfNull(schema); - ArgumentNullException.ThrowIfNull(context); - - ICustomAttributeProvider attributesProvider; - - if (context.MemberInfo != null) { - attributesProvider = context.MemberInfo; - } else if (context.ParameterInfo != null) { - attributesProvider = context.ParameterInfo; - } else { - return; - } - - foreach (CustomSwaggerAttribute customSwaggerAttribute in attributesProvider.GetCustomAttributes(typeof(CustomSwaggerAttribute), true)) { - customSwaggerAttribute.Apply(schema); - } - } -} diff --git a/ArchiSteamFarm/IPC/Integration/ReadOnlyFixesSchemaFilter.cs b/ArchiSteamFarm/IPC/Integration/ReadOnlyFixesSchemaFilter.cs deleted file mode 100644 index f947a5560c352..0000000000000 --- a/ArchiSteamFarm/IPC/Integration/ReadOnlyFixesSchemaFilter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ---------------------------------------------------------------------------------------------- -// _ _ _ ____ _ _____ -// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ -// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ -// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | -// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| -// ---------------------------------------------------------------------------------------------- -// | -// Copyright 2015-2024 Ł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.Reflection; -using JetBrains.Annotations; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace ArchiSteamFarm.IPC.Integration; - -[UsedImplicitly] -internal sealed class ReadOnlyFixesSchemaFilter : ISchemaFilter { - public void Apply(OpenApiSchema schema, SchemaFilterContext context) { - ArgumentNullException.ThrowIfNull(schema); - ArgumentNullException.ThrowIfNull(context); - - if (schema.ReadOnly && context.MemberInfo is PropertyInfo { CanWrite: true }) { - schema.ReadOnly = false; - } - } -} diff --git a/ArchiSteamFarm/IPC/OpenApi/DocumentTransformer.cs b/ArchiSteamFarm/IPC/OpenApi/DocumentTransformer.cs new file mode 100644 index 0000000000000..4685708322cc6 --- /dev/null +++ b/ArchiSteamFarm/IPC/OpenApi/DocumentTransformer.cs @@ -0,0 +1,58 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2022-2024 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using ArchiSteamFarm.IPC.Integration; +using ArchiSteamFarm.Storage; +using JetBrains.Annotations; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi.Models; + +namespace ArchiSteamFarm.IPC.OpenApi; + +#pragma warning disable CA1812 // False positive, the class is used internally +[UsedImplicitly] +internal sealed class DocumentTransformer : IOpenApiDocumentTransformer { + public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(document); + ArgumentNullException.ThrowIfNull(context); + + document.Info ??= new OpenApiInfo(); + document.Info.Title = $"{SharedInfo.AssemblyName} API"; + document.Info.Version = SharedInfo.Version.ToString(); + + document.Info.Contact ??= new OpenApiContact(); + document.Info.Contact.Name = SharedInfo.GithubRepo; + document.Info.Contact.Url = new Uri(SharedInfo.ProjectURL); + + document.Info.License ??= new OpenApiLicense(); + document.Info.License.Name = SharedInfo.LicenseName; + document.Info.License.Url = new Uri(SharedInfo.LicenseURL); + + document.Components ??= new OpenApiComponents(); + document.Components.SecuritySchemes ??= new Dictionary(1); + + document.Components.SecuritySchemes.Add( + nameof(GlobalConfig.IPCPassword), new OpenApiSecurityScheme { + Description = $"{nameof(GlobalConfig.IPCPassword)} authentication using request headers. Check {SharedInfo.ProjectURL}/wiki/IPC#authentication for more info.", + In = ParameterLocation.Header, + Name = ApiAuthenticationMiddleware.HeadersField, + Type = SecuritySchemeType.ApiKey + } + ); + + return Task.CompletedTask; + } +} +#pragma warning restore CA1812 // False positive, the class is used internally diff --git a/ArchiSteamFarm/IPC/OpenApi/OperationTransformer.cs b/ArchiSteamFarm/IPC/OpenApi/OperationTransformer.cs new file mode 100644 index 0000000000000..d76c8ce02861c --- /dev/null +++ b/ArchiSteamFarm/IPC/OpenApi/OperationTransformer.cs @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------------------------- +// _ _ _ ____ _ _____ +// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ +// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ +// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | | +// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| +// ---------------------------------------------------------------------------------------------- +// | +// Copyright 2022-2024 Łukasz "JustArchi" Domeradzki +// Contact: JustArchi@JustArchi.net + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using ArchiSteamFarm.Storage; +using JetBrains.Annotations; +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi.Models; + +namespace ArchiSteamFarm.IPC.OpenApi; + +#pragma warning disable CA1812 // False positive, the class is used internally +[UsedImplicitly] +internal sealed class OperationTransformer : IOpenApiOperationTransformer { + public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(operation); + ArgumentNullException.ThrowIfNull(context); + + if (context.Description.RelativePath?.StartsWith("Api", StringComparison.OrdinalIgnoreCase) == true) { + operation.Security ??= new List(1); + + operation.Security.Add( + new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Id = nameof(GlobalConfig.IPCPassword), + Type = ReferenceType.SecurityScheme + } + }, + + Array.Empty() + } + } + ); + } + + return Task.CompletedTask; + } +} +#pragma warning restore CA1812 // False positive, the class is used internally diff --git a/ArchiSteamFarm/IPC/Integration/EnumSchemaFilter.cs b/ArchiSteamFarm/IPC/OpenApi/SchemaTransformer.cs similarity index 62% rename from ArchiSteamFarm/IPC/Integration/EnumSchemaFilter.cs rename to ArchiSteamFarm/IPC/OpenApi/SchemaTransformer.cs index ed29fb0ae4686..a0eebf4b3649c 100644 --- a/ArchiSteamFarm/IPC/Integration/EnumSchemaFilter.cs +++ b/ArchiSteamFarm/IPC/OpenApi/SchemaTransformer.cs @@ -1,4 +1,4 @@ -// ---------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------- // _ _ _ ____ _ _____ // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___ // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \ @@ -6,7 +6,7 @@ // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_| // ---------------------------------------------------------------------------------------------- // | -// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki +// Copyright 2015-2025 Łukasz "JustArchi" Domeradzki // Contact: JustArchi@JustArchi.net // | // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,36 +23,63 @@ using System; using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using ArchiSteamFarm.IPC.Integration; using JetBrains.Annotations; +using Microsoft.AspNetCore.OpenApi; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -namespace ArchiSteamFarm.IPC.Integration; +namespace ArchiSteamFarm.IPC.OpenApi; +#pragma warning disable CA1812 // False positive, the class is used internally [UsedImplicitly] -internal sealed class EnumSchemaFilter : ISchemaFilter { - public void Apply(OpenApiSchema schema, SchemaFilterContext context) { +internal sealed class SchemaTransformer : IOpenApiSchemaTransformer { + public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(schema); + ArgumentNullException.ThrowIfNull(context); + + ApplyCustomAttributes(schema, context); + ApplyEnumDefinition(schema, context); + + return Task.CompletedTask; + } + + private static void ApplyCustomAttributes(OpenApiSchema schema, OpenApiSchemaTransformerContext context) { + ArgumentNullException.ThrowIfNull(schema); + ArgumentNullException.ThrowIfNull(context); + + if (context.JsonPropertyInfo?.AttributeProvider == null) { + return; + } + + foreach (CustomSwaggerAttribute customSwaggerAttribute in context.JsonPropertyInfo.AttributeProvider.GetCustomAttributes(typeof(CustomSwaggerAttribute), true)) { + customSwaggerAttribute.Apply(schema); + } + } + + private static void ApplyEnumDefinition(OpenApiSchema schema, OpenApiSchemaTransformerContext context) { ArgumentNullException.ThrowIfNull(schema); ArgumentNullException.ThrowIfNull(context); - if (context.Type is not { IsEnum: true }) { + if (context.JsonTypeInfo.Type is not { IsEnum: true }) { return; } - if (context.Type.IsDefined(typeof(FlagsAttribute), false)) { + if (context.JsonTypeInfo.Type.IsDefined(typeof(FlagsAttribute), false)) { schema.Format = "flags"; } OpenApiObject definition = new(); - foreach (object? enumValue in context.Type.GetEnumValues()) { + foreach (object? enumValue in context.JsonTypeInfo.Type.GetEnumValues()) { if (enumValue == null) { throw new InvalidOperationException(nameof(enumValue)); } - string? enumName = Enum.GetName(context.Type, enumValue); + string? enumName = Enum.GetName(context.JsonTypeInfo.Type, enumValue); if (string.IsNullOrEmpty(enumName)) { // Fallback @@ -68,7 +95,7 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context) { continue; } - IOpenApiPrimitive enumObject; + IOpenApiAny enumObject; if (TryCast(enumValue, out int intValue)) { enumObject = new OpenApiInteger(intValue); @@ -105,3 +132,4 @@ private static bool TryCast(object value, out T typedValue) where T : struct } } } +#pragma warning restore CA1812 // False positive, the class is used internally diff --git a/ArchiSteamFarm/IPC/Requests/ASFEncryptRequest.cs b/ArchiSteamFarm/IPC/Requests/ASFEncryptRequest.cs index 7631f2aa13996..a7221dbe16139 100644 --- a/ArchiSteamFarm/IPC/Requests/ASFEncryptRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/ASFEncryptRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -30,17 +31,13 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class ASFEncryptRequest { - /// - /// Encryption method used for encrypting this string. - /// + [Description("Encryption method used for encrypting this string")] [JsonInclude] [JsonRequired] [Required] public ArchiCryptoHelper.ECryptoMethod CryptoMethod { get; private init; } - /// - /// String to encrypt with provided . - /// + [Description($"String to encrypt with provided {nameof(CryptoMethod)}")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/ASFHashRequest.cs b/ArchiSteamFarm/IPC/Requests/ASFHashRequest.cs index b65cab55a42a0..7d107c468ee29 100644 --- a/ArchiSteamFarm/IPC/Requests/ASFHashRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/ASFHashRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -30,17 +31,13 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class ASFHashRequest { - /// - /// Hashing method used for hashing this string. - /// + [Description("Hashing method used for hashing this string")] [JsonInclude] [JsonRequired] [Required] public ArchiCryptoHelper.EHashingMethod HashingMethod { get; private init; } - /// - /// String to hash with provided . - /// + [Description($"String to hash with provided {nameof(HashingMethod)}")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/ASFRequest.cs b/ArchiSteamFarm/IPC/Requests/ASFRequest.cs index 03070a97fe8c6..3d32436b3560a 100644 --- a/ArchiSteamFarm/IPC/Requests/ASFRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/ASFRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -30,9 +31,7 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class ASFRequest { - /// - /// ASF's global config structure. - /// + [Description("ASF's global config structure")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs b/ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs index fade3b3f11ec2..c5bf1debe729c 100644 --- a/ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotAddLicenseRequest.cs @@ -22,6 +22,7 @@ // limitations under the License. using System.Collections.Immutable; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -29,15 +30,11 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotAddLicenseRequest { - /// - /// A collection (set) of apps (appIDs) to ask license for. - /// + [Description("A collection (set) of apps (appIDs) to ask license for")] [JsonInclude] public ImmutableList? Apps { get; private init; } - /// - /// A collection (set) of packages (subIDs) to ask license for. - /// + [Description("A collection (set) of packages (subIDs) to ask license for")] [JsonInclude] public ImmutableList? Packages { get; private init; } diff --git a/ArchiSteamFarm/IPC/Requests/BotGamesToRedeemInBackgroundRequest.cs b/ArchiSteamFarm/IPC/Requests/BotGamesToRedeemInBackgroundRequest.cs index 274157c866b49..cb137bcbd1e58 100644 --- a/ArchiSteamFarm/IPC/Requests/BotGamesToRedeemInBackgroundRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotGamesToRedeemInBackgroundRequest.cs @@ -22,6 +22,7 @@ // limitations under the License. using System.Collections.Specialized; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -30,13 +31,7 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotGamesToRedeemInBackgroundRequest { - /// - /// 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). - /// + [Description("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)")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/BotInputRequest.cs b/ArchiSteamFarm/IPC/Requests/BotInputRequest.cs index 89ad55fe721e6..f1ca8426f7371 100644 --- a/ArchiSteamFarm/IPC/Requests/BotInputRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotInputRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -30,17 +31,13 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotInputRequest { - /// - /// Specifies the type of the input. - /// + [Description("Specifies the type of the input")] [JsonInclude] [JsonRequired] [Required] public ASF.EUserInputType Type { get; private init; } - /// - /// Specifies the value for given input type (declared in ) - /// + [Description($"Specifies the value for given input type (declared in {nameof(Type)})")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs b/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs index 027a0b761e649..a39cef77ab88b 100644 --- a/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotPauseRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -28,15 +29,11 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotPauseRequest { - /// - /// Specifies if pause is permanent or temporary (default). - /// + [Description("Specifies if pause is permanent or temporary (default)")] [JsonInclude] public bool Permanent { get; private init; } - /// - /// Specifies automatic resume action in given seconds. Default value of 0 disables automatic resume. - /// + [Description("Specifies automatic resume action in given seconds. Default value of 0 disables automatic resume")] [JsonInclude] public ushort ResumeInSeconds { get; private init; } diff --git a/ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs b/ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs index b073e3f995d4c..c7536c83efd5f 100644 --- a/ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotRedeemRequest.cs @@ -22,6 +22,7 @@ // limitations under the License. using System.Collections.Immutable; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -30,9 +31,7 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotRedeemRequest { - /// - /// A collection (set) of keys to redeem. - /// + [Description("A collection (set) of keys to redeem")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/BotRenameRequest.cs b/ArchiSteamFarm/IPC/Requests/BotRenameRequest.cs index dffa6b7422da1..ce5a031b8798c 100644 --- a/ArchiSteamFarm/IPC/Requests/BotRenameRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotRenameRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -29,9 +30,7 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotRenameRequest { - /// - /// Specifies the new name for the bot. The new name can't be "ASF", neither the one used by any existing bot. - /// + [Description($"Specifies the new name for the bot. The new name can't be {SharedInfo.ASF}, neither the one used by any existing bot")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/BotRequest.cs b/ArchiSteamFarm/IPC/Requests/BotRequest.cs index 982a2a751514b..65d3d96c195c8 100644 --- a/ArchiSteamFarm/IPC/Requests/BotRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/BotRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -30,9 +31,7 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class BotRequest { - /// - /// ASF's bot config structure. - /// + [Description("ASF's bot config structure")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/CommandRequest.cs b/ArchiSteamFarm/IPC/Requests/CommandRequest.cs index f874c7fbeece2..66891905f8146 100644 --- a/ArchiSteamFarm/IPC/Requests/CommandRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/CommandRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; @@ -29,9 +30,7 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class CommandRequest { - /// - /// Specifies the command that will be executed by ASF. - /// + [Description("Specifies the command that will be executed by ASF")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Requests/PluginUpdateRequest.cs b/ArchiSteamFarm/IPC/Requests/PluginUpdateRequest.cs index 76ad026831067..d943e99d566b4 100644 --- a/ArchiSteamFarm/IPC/Requests/PluginUpdateRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/PluginUpdateRequest.cs @@ -22,6 +22,7 @@ // limitations under the License. using System.Collections.Immutable; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using ArchiSteamFarm.Storage; @@ -30,21 +31,15 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class PluginUpdateRequest { - /// - /// Target update channel. Not required, will default to if not provided. - /// + [Description($"Target update channel. Not required, will default to {nameof(GlobalConfig.UpdateChannel)} if not provided")] [JsonInclude] public GlobalConfig.EUpdateChannel? Channel { get; private init; } - /// - /// Forced update. This allows ASF to potentially downgrade to previous version available on selected , which isn't permitted normally. - /// + [Description($"Forced update. This allows ASF to potentially downgrade to previous version available on selected {nameof(Channel)}, which isn't permitted normally")] [JsonInclude] public bool Forced { get; private init; } - /// - /// Target plugins. Not required, will default to plugin update configuration in if not provided. - /// + [Description($"Target plugins. Not required, will default to plugin update configuration in {nameof(GlobalConfig)} if not provided")] [JsonInclude] public ImmutableHashSet? Plugins { get; private init; } diff --git a/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs b/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs index 19bc9bc5158d8..5954246d309cd 100644 --- a/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/TwoFactorAuthenticationConfirmationsRequest.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -38,30 +39,22 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class TwoFactorAuthenticationConfirmationsRequest { - /// - /// Specifies the target action, whether we should accept the confirmations (true), or decline them (false). - /// + [Description("Specifies the target action, whether we should accept the confirmations (true), or decline them (false)")] [JsonInclude] [JsonRequired] [Required] public bool Accept { get; private init; } - /// - /// Specifies IDs of the confirmations that we're supposed to handle. CreatorID of the confirmation is equal to ID of the object that triggered it - e.g. ID of the trade offer, or ID of the market listing. If not provided, or empty array, all confirmation IDs are considered for an action. - /// + [Description("Specifies IDs of the confirmations that we're supposed to handle. CreatorID of the confirmation is equal to ID of the object that triggered it - e.g. ID of the trade offer, or ID of the market listing. If not provided, or empty array, all confirmation IDs are considered for an action")] [JsonDisallowNull] [JsonInclude] public ImmutableHashSet AcceptedCreatorIDs { get; private init; } = []; - /// - /// Specifies the type of confirmations to handle. If not provided, all confirmation types are considered for an action. - /// + [Description("Specifies the type of confirmations to handle. If not provided, all confirmation types are considered for an action")] [JsonInclude] public Confirmation.EConfirmationType? AcceptedType { get; private init; } - /// - /// A helper property which works the same as but with values written as strings - for javascript compatibility purposes. Use either this one, or , not both. - /// + [Description($"A helper property which works the same as {nameof(AcceptedCreatorIDs)} but with values written as strings - for javascript compatibility purposes. Use either this one, or {nameof(AcceptedCreatorIDs)}, not both")] [JsonDisallowNull] [JsonInclude] [JsonPropertyName($"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(AcceptedCreatorIDs)}")] @@ -87,9 +80,7 @@ private init { } } - /// - /// Specifies whether we should wait for the confirmations to arrive, in case they're not available immediately. This option makes sense only if is specified as well, and in this case ASF will add a few more tries if needed to ensure that all specified IDs are handled. Useful if confirmations are generated with a delay on Steam network side, which happens fairly often. - /// + [Description($"Specifies whether we should wait for the confirmations to arrive, in case they're not available immediately. This option makes sense only if {nameof(AcceptedCreatorIDs)} is specified as well, and in this case ASF will add a few more tries if needed to ensure that all specified IDs are handled. Useful if confirmations are generated with a delay on Steam network side, which happens fairly often")] [JsonInclude] public bool WaitIfNeeded { get; private init; } diff --git a/ArchiSteamFarm/IPC/Requests/UpdateRequest.cs b/ArchiSteamFarm/IPC/Requests/UpdateRequest.cs index 91da0b52d3836..33366a2183fdf 100644 --- a/ArchiSteamFarm/IPC/Requests/UpdateRequest.cs +++ b/ArchiSteamFarm/IPC/Requests/UpdateRequest.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using ArchiSteamFarm.Storage; @@ -29,15 +30,11 @@ namespace ArchiSteamFarm.IPC.Requests; [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] public sealed class UpdateRequest { - /// - /// Target update channel. Not required, will default to UpdateChannel in GlobalConfig if not provided. - /// + [Description("Target update channel. Not required, will default to UpdateChannel in GlobalConfig if not provided")] [JsonInclude] public GlobalConfig.EUpdateChannel? Channel { get; private init; } - /// - /// Forced update. This allows ASF to potentially downgrade to previous version available on selected , which isn't permitted normally. - /// + [Description($"Forced update. This allows ASF to potentially downgrade to previous version available on selected {nameof(Channel)}, which isn't permitted normally")] [JsonInclude] public bool Forced { get; private init; } diff --git a/ArchiSteamFarm/IPC/Responses/ASFResponse.cs b/ArchiSteamFarm/IPC/Responses/ASFResponse.cs index 0d6d29fdd2ea0..c23a0adcc1eda 100644 --- a/ArchiSteamFarm/IPC/Responses/ASFResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/ASFResponse.cs @@ -22,6 +22,7 @@ // limitations under the License. using System; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using ArchiSteamFarm.Storage; @@ -29,57 +30,43 @@ namespace ArchiSteamFarm.IPC.Responses; public sealed class ASFResponse { - /// - /// ASF's build variant. - /// + [Description("ASF's build variant")] [JsonInclude] [JsonRequired] [Required] public string BuildVariant { get; private init; } - /// - /// A value specifying whether this variant of ASF is capable of auto-update. - /// + [Description("A value specifying whether this variant of ASF is capable of auto-update")] [JsonInclude] [JsonRequired] [Required] public bool CanUpdate { get; private init; } - /// - /// Currently loaded ASF's global config. - /// + [Description("Currently loaded ASF's global config")] [JsonInclude] [JsonRequired] [Required] public GlobalConfig GlobalConfig { get; private init; } - /// - /// Current amount of managed memory being used by the process, in kilobytes. - /// + [Description("Current amount of managed memory being used by the process, in kilobytes")] [JsonInclude] [JsonRequired] [Required] public uint MemoryUsage { get; private init; } - /// - /// Start date of the process. - /// + [Description("Start date of the process")] [JsonInclude] [JsonRequired] [Required] public DateTime ProcessStartTime { get; private init; } - /// - /// Boolean value specifying whether ASF has been started with a --service parameter. - /// + [Description("Boolean value specifying whether ASF has been started with a --service parameter")] [JsonInclude] [JsonRequired] [Required] public bool Service { get; private init; } - /// - /// ASF version of currently running binary. - /// + [Description("ASF version of currently running binary")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs b/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs index 2a1993247214b..b43a51860186f 100644 --- a/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs +++ b/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs @@ -30,11 +30,13 @@ namespace ArchiSteamFarm.IPC.Responses; public sealed class AddLicenseResult { + [Description("Additional result of the license request")] [JsonInclude] [JsonRequired] [Required] public EPurchaseResultDetail PurchaseResultDetail { get; private init; } + [Description("Main result of the license request")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs b/ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs index 76a69053ec6d6..68cf2b384c03b 100644 --- a/ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/BotAddLicenseResponse.cs @@ -23,20 +23,17 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Text.Json.Serialization; namespace ArchiSteamFarm.IPC.Responses; public sealed class BotAddLicenseResponse { - /// - /// A collection (set) of apps (appIDs) to ask license for. - /// + [Description("A collection (set) of apps (appIDs) to ask license for")] [JsonInclude] public ImmutableDictionary? Apps { get; private init; } - /// - /// A collection (set) of packages (subIDs) to ask license for. - /// + [Description("A collection (set) of packages (subIDs) to ask license for")] [JsonInclude] public ImmutableDictionary? Packages { get; private init; } diff --git a/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs b/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs index 6dd1050e7f018..0edf60c3e0adc 100644 --- a/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/GamesToRedeemInBackgroundResponse.cs @@ -23,20 +23,17 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Text.Json.Serialization; namespace ArchiSteamFarm.IPC.Responses; public sealed class GamesToRedeemInBackgroundResponse { - /// - /// Keys that were redeemed and not used during the process, if available. - /// + [Description("Keys that were redeemed and not used during the process, if available")] [JsonInclude] public ImmutableDictionary? UnusedKeys { get; private init; } - /// - /// Keys that were redeemed and used during the process, if available. - /// + [Description("Keys that were redeemed and used during the process, if available")] [JsonInclude] public ImmutableDictionary? UsedKeys { get; private init; } diff --git a/ArchiSteamFarm/IPC/Responses/GenericResponse.cs b/ArchiSteamFarm/IPC/Responses/GenericResponse.cs index 409380f6a4c7f..2e2149e7b7c20 100644 --- a/ArchiSteamFarm/IPC/Responses/GenericResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/GenericResponse.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using ArchiSteamFarm.Localization; @@ -28,12 +29,7 @@ namespace ArchiSteamFarm.IPC.Responses; public sealed class GenericResponse : GenericResponse { - /// - /// The actual result of the request, if available. - /// - /// - /// The type of the result depends on the API endpoint that you've called. - /// + [Description("The actual result of the request, if available. The type of the result depends on the API endpoint that you've called")] [JsonInclude] public T? Result { get; private init; } @@ -47,18 +43,11 @@ private GenericResponse() { } } 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. - /// + [Description("A message that describes what happened with the request, if available. This property will provide exact reason for majority of expected failures")] [JsonInclude] public string? Message { get; private init; } - /// - /// Boolean type that specifies if the request has succeeded. - /// + [Description("Boolean type that specifies if the request has succeeded")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs b/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs index 4b8f5f47cd2df..9fe74fac75ea6 100644 --- a/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/GitHubReleaseResponse.cs @@ -22,6 +22,7 @@ // limitations under the License. using System; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using ArchiSteamFarm.Web.GitHub.Data; @@ -29,33 +30,25 @@ namespace ArchiSteamFarm.IPC.Responses; public sealed class GitHubReleaseResponse { - /// - /// Changelog of the release rendered in HTML. - /// + [Description("Changelog of the release rendered in HTML")] [JsonInclude] [JsonRequired] [Required] public string ChangelogHTML { get; private init; } - /// - /// Date of the release. - /// + [Description("Date of the release")] [JsonInclude] [JsonRequired] [Required] public DateTime ReleasedAt { get; private init; } - /// - /// Boolean value that specifies whether the build is stable or not (pre-release). - /// + [Description("Boolean value that specifies whether the build is stable or not (pre-release)")] [JsonInclude] [JsonRequired] [Required] public bool Stable { get; private init; } - /// - /// Version of the release. - /// + [Description("Version of the release")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Responses/HealthCheckResponse.cs b/ArchiSteamFarm/IPC/Responses/HealthCheckResponse.cs index 97c16d09fba2f..865837b3ec311 100644 --- a/ArchiSteamFarm/IPC/Responses/HealthCheckResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/HealthCheckResponse.cs @@ -22,6 +22,7 @@ // limitations under the License. using System; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -29,10 +30,11 @@ namespace ArchiSteamFarm.IPC.Responses; public sealed class HealthCheckResponse { + [Description($"{nameof(Status)} written as text")] [JsonInclude] - [Required] public string StatusText => Status.ToString(); + [Description("Health status of the application")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Responses/LogResponse.cs b/ArchiSteamFarm/IPC/Responses/LogResponse.cs index e621db74fb30a..e09e257afd01f 100644 --- a/ArchiSteamFarm/IPC/Responses/LogResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/LogResponse.cs @@ -24,23 +24,20 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; namespace ArchiSteamFarm.IPC.Responses; public sealed class LogResponse { - /// - /// Content of the log file which consists of lines read from it - in chronological order. - /// + [Description("Content of the log file which consists of lines read from it - in chronological order")] [JsonInclude] [JsonRequired] [Required] public ImmutableList Content { get; private init; } - /// - /// Total number of lines of the log file returned, can be used as an index for future requests. - /// + [Description("Total number of lines of the log file returned, can be used as an index for future requests")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Responses/StatusCodeResponse.cs b/ArchiSteamFarm/IPC/Responses/StatusCodeResponse.cs index 2082b9cf0e794..eb785995d598a 100644 --- a/ArchiSteamFarm/IPC/Responses/StatusCodeResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/StatusCodeResponse.cs @@ -21,6 +21,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Net; using System.Text.Json.Serialization; @@ -28,17 +29,13 @@ namespace ArchiSteamFarm.IPC.Responses; public sealed class StatusCodeResponse { - /// - /// Value indicating whether the status is permanent. If yes, retrying the request with exactly the same payload doesn't make sense due to a permanent problem (e.g. ASF misconfiguration). - /// + [Description("Value indicating whether the status is permanent. If yes, retrying the request with exactly the same payload doesn't make sense due to a permanent problem (e.g. ASF misconfiguration)")] [JsonInclude] [JsonRequired] [Required] public bool Permanent { get; private init; } - /// - /// Status code transmitted in addition to the one in HTTP spec. - /// + [Description("Status code transmitted in addition to the one in HTTP spec")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/IPC/Responses/TypeProperties.cs b/ArchiSteamFarm/IPC/Responses/TypeProperties.cs index 03a10463347c9..f9b6c160e10f3 100644 --- a/ArchiSteamFarm/IPC/Responses/TypeProperties.cs +++ b/ArchiSteamFarm/IPC/Responses/TypeProperties.cs @@ -24,35 +24,21 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Text.Json.Serialization; namespace ArchiSteamFarm.IPC.Responses; public sealed class TypeProperties { - /// - /// Base type of given type, if available. - /// - /// - /// This can be used for determining how the body of the response should be interpreted. - /// + [Description("Base type of given type, if available. This can be used for determining how the body of the response should be interpreted")] [JsonInclude] public string? BaseType { get; private init; } - /// - /// Custom attributes of given type, if available. - /// - /// - /// This can be used for determining main enum type if is . - /// + [Description($"Custom attributes of given type, if available. This can be used for determining main enum type if {nameof(BaseType)} is {nameof(Enum)}")] [JsonInclude] public ImmutableHashSet? CustomAttributes { get; private init; } - /// - /// Underlying type of given type, if available. - /// - /// - /// This can be used for determining underlying enum type if is . - /// + [Description($"Underlying type of given type, if available. This can be used for determining underlying enum type if {nameof(BaseType)} is {nameof(Enum)}")] [JsonInclude] public string? UnderlyingType { get; private init; } diff --git a/ArchiSteamFarm/IPC/Responses/TypeResponse.cs b/ArchiSteamFarm/IPC/Responses/TypeResponse.cs index 70b1f08fb73f4..6c06d7f12c2fe 100644 --- a/ArchiSteamFarm/IPC/Responses/TypeResponse.cs +++ b/ArchiSteamFarm/IPC/Responses/TypeResponse.cs @@ -24,28 +24,20 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; namespace ArchiSteamFarm.IPC.Responses; public sealed class TypeResponse { - /// - /// 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. - /// + [Description($"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 {nameof(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")] [JsonInclude] [JsonRequired] [Required] public ImmutableDictionary Body { get; private init; } - /// - /// Metadata of given type. - /// + [Description("Metadata of given type")] [JsonInclude] [JsonRequired] [Required] diff --git a/ArchiSteamFarm/Plugins/Interfaces/IPlugin.cs b/ArchiSteamFarm/Plugins/Interfaces/IPlugin.cs index ad97b34734208..ce8066dfb15d4 100644 --- a/ArchiSteamFarm/Plugins/Interfaces/IPlugin.cs +++ b/ArchiSteamFarm/Plugins/Interfaces/IPlugin.cs @@ -22,7 +22,6 @@ // limitations under the License. using System; -using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; using System.Threading.Tasks; using JetBrains.Annotations; @@ -39,7 +38,6 @@ public interface IPlugin { /// /// String that will be used as the name of this plugin. [JsonInclude] - [Required] string Name { get; } /// @@ -48,7 +46,6 @@ public interface IPlugin { /// /// Version that will be shown to the user when plugin is loaded. [JsonInclude] - [Required] Version Version { get; } /// diff --git a/ArchiSteamFarm/SharedInfo.cs b/ArchiSteamFarm/SharedInfo.cs index 49ae1bc2b960f..d13280c13610d 100644 --- a/ArchiSteamFarm/SharedInfo.cs +++ b/ArchiSteamFarm/SharedInfo.cs @@ -38,7 +38,6 @@ public static class SharedInfo { internal const string ArchivalLogsDirectory = "logs"; 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 DatabaseExtension = ".db"; internal const string DebugDirectory = "debug"; diff --git a/ArchiSteamFarm/Steam/Bot.cs b/ArchiSteamFarm/Steam/Bot.cs index 125a052a7b7cb..b76e34181bf2d 100644 --- a/ArchiSteamFarm/Steam/Bot.cs +++ b/ArchiSteamFarm/Steam/Bot.cs @@ -106,12 +106,10 @@ public sealed class Bot : IAsyncDisposable, IDisposable { [JsonInclude] [PublicAPI] - [Required] public string BotName { get; } [JsonInclude] [PublicAPI] - [Required] public CardsFarmer CardsFarmer { get; } [JsonIgnore] @@ -120,12 +118,10 @@ public sealed class Bot : IAsyncDisposable, IDisposable { [JsonInclude] [PublicAPI] - [Required] public uint GamesToRedeemInBackgroundCount => BotDatabase.GamesToRedeemInBackgroundCount; [JsonInclude] [PublicAPI] - [Required] public bool HasMobileAuthenticator => BotDatabase.MobileAuthenticator != null; [JsonIgnore] @@ -138,12 +134,10 @@ public sealed class Bot : IAsyncDisposable, IDisposable { [JsonInclude] [PublicAPI] - [Required] public bool IsConnectedAndLoggedOn => SteamClient.SteamID != null; [JsonInclude] [PublicAPI] - [Required] public bool IsPlayingPossible => !PlayingBlocked && !LibraryLocked; [JsonInclude] @@ -153,7 +147,6 @@ public sealed class Bot : IAsyncDisposable, IDisposable { [JsonInclude] [JsonPropertyName($"{SharedInfo.UlongCompatibilityStringPrefix}{nameof(SteamID)}")] [PublicAPI] - [Required] public string SSteamID => SteamID.ToString(CultureInfo.InvariantCulture); [JsonIgnore] diff --git a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs index 00a35bdc07a7b..6cd34ae44f754 100644 --- a/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs +++ b/ArchiSteamFarm/Steam/Cards/CardsFarmer.cs @@ -67,18 +67,15 @@ public sealed class CardsFarmer : IAsyncDisposable, IDisposable { [JsonInclude] [JsonPropertyName(nameof(CurrentGamesFarming))] [PublicAPI] - [Required] public IReadOnlyCollection CurrentGamesFarmingReadOnly => CurrentGamesFarming; [JsonInclude] [JsonPropertyName(nameof(GamesToFarm))] [PublicAPI] - [Required] public IReadOnlyCollection GamesToFarmReadOnly => GamesToFarm; [JsonInclude] [PublicAPI] - [Required] public TimeSpan TimeRemaining { get { if (GamesToFarm.Count == 0) { diff --git a/ArchiSteamFarm/Steam/Cards/Game.cs b/ArchiSteamFarm/Steam/Cards/Game.cs index dbf161efef5bc..bece96464dbb2 100644 --- a/ArchiSteamFarm/Steam/Cards/Game.cs +++ b/ArchiSteamFarm/Steam/Cards/Game.cs @@ -29,20 +29,20 @@ namespace ArchiSteamFarm.Steam.Cards; public sealed class Game : IEquatable { [JsonInclude] - [Required] public uint AppID { get; } [JsonInclude] - [Required] public string GameName { get; } internal readonly byte BadgeLevel; [JsonInclude] + [JsonRequired] [Required] public ushort CardsRemaining { get; internal set; } [JsonInclude] + [JsonRequired] [Required] public float HoursPlayed { get; internal set; } diff --git a/ArchiSteamFarm/Steam/Data/LicenseData.cs b/ArchiSteamFarm/Steam/Data/LicenseData.cs index f7a8a99bcf4e7..826d0e7d277ee 100644 --- a/ArchiSteamFarm/Steam/Data/LicenseData.cs +++ b/ArchiSteamFarm/Steam/Data/LicenseData.cs @@ -22,12 +22,18 @@ // limitations under the License. using System; +using JetBrains.Annotations; using SteamKit2; namespace ArchiSteamFarm.Steam.Data; public sealed record LicenseData { + [PublicAPI] public required uint PackageID { get; init; } + + [PublicAPI] public required EPaymentMethod PaymentMethod { get; init; } + + [PublicAPI] public required DateTime TimeCreated { get; init; } } diff --git a/Directory.Packages.props b/Directory.Packages.props index 267260477c420..e395d29f24601 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,6 +5,7 @@ + @@ -16,8 +17,7 @@ - - +