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 f0386e83b..d5945d0ec 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 48a071c0b..2f537d371 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 8ddf027bd..11868c23f 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 193894b63..5b849c8e8 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 680ece5c7..4d9278789 100644 --- a/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ItemsMatcherPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.ItemsMatcher/ItemsMatcherPlugin.cs @@ -25,7 +25,6 @@ using System; 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 d8f720d08..d7a113860 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 b1943068d..326de0e2d 100644 --- a/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.Monitoring/MonitoringPlugin.cs @@ -25,7 +25,6 @@ using System; 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 a521691cb..14a7e006f 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 eaa51ea16..ec4088e22 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 b0b1ab868..cc8b67aae 100644 --- a/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs +++ b/ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SteamTokenDumperPlugin.cs @@ -26,7 +26,6 @@ using System.Collections.Concurrent; 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 edacba87b..95541f9b9 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 807b40bcf..7fb3e2560 100644 --- a/ArchiSteamFarm/IPC/ArchiKestrel.cs +++ b/ArchiSteamFarm/IPC/ArchiKestrel.cs @@ -35,6 +35,7 @@ using ArchiSteamFarm.Core; 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.DependencyInjection; 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 class ArchiKestrel { [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 @@ internal static class ArchiKestrel { } // 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 @@ internal static class ArchiKestrel { 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 @@ internal static class ArchiKestrel { } } + 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 7474f6aba..f3cc4af2f 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/ASFController.cs @@ -34,6 +34,7 @@ using ArchiSteamFarm.IPC.Responses; 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 sealed class ASFController : ArchiController { 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 sealed class ASFController : ArchiController { 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 sealed class ASFController : ArchiController { 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 sealed class ASFController : ArchiController { 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 sealed class ASFController : ArchiController { 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 sealed class ASFController : ArchiController { 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 sealed class ASFController : ArchiController { 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 feac5df34..026db6a52 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 7e9c3b49b..eaadd8752 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/BotController.cs @@ -34,6 +34,7 @@ using ArchiSteamFarm.IPC.Responses; 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 sealed class BotController : ArchiController { 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 bfec11818..c425eb796 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/CommandController.cs @@ -31,6 +31,7 @@ using ArchiSteamFarm.IPC.Responses; 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 sealed class CommandController : ArchiController { 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 58a35cf6e..046c4a3fd 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/GitHubController.cs @@ -32,18 +32,15 @@ using ArchiSteamFarm.Localization; 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 sealed class GitHubController : ArchiController { 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 sealed class GitHubController : ArchiController { 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 sealed class GitHubController : ArchiController { 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 bfbe18bec..b9e7919f0 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs @@ -26,6 +26,7 @@ using System.Net; 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 sealed class HealthCheckController : ControllerBase { 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 b56617745..a0cd500e1 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/IPCBansController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/IPCBansController.cs @@ -28,15 +28,14 @@ using System.Net; 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 sealed class IPCBansController : ArchiController { 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 sealed class IPCBansController : ArchiController { 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 42f770905..d13e1474c 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.Localization; 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 sealed class NLogController : ArchiController { 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 sealed class NLogController : ArchiController { 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 d7a286dcc..d975a5b32 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/PluginsController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/PluginsController.cs @@ -31,15 +31,14 @@ using ArchiSteamFarm.Localization; 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 sealed class PluginsController : ArchiController { 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 a8d842c13..246f942b1 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/StorageController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/StorageController.cs @@ -26,15 +26,14 @@ using System.Net; 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 sealed class StorageController : ArchiController { 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 sealed class StorageController : ArchiController { 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 81bda514f..39334d4cf 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/StructureController.cs @@ -26,18 +26,15 @@ using System.Diagnostics.CodeAnalysis; 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 5320b5e25..692d3493d 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/TwoFactorAuthenticationController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/TwoFactorAuthenticationController.cs @@ -33,15 +33,14 @@ using ArchiSteamFarm.Localization; 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 sealed class TwoFactorAuthenticationController : ArchiController { 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 sealed class TwoFactorAuthenticationController : ArchiController { 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 sealed class TwoFactorAuthenticationController : ArchiController { 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 sealed class TwoFactorAuthenticationController : ArchiController { 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 d5c7ce344..1176ef2a6 100644 --- a/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs +++ b/ArchiSteamFarm/IPC/Controllers/Api/TypeController.cs @@ -32,18 +32,15 @@ using System.Text.Json.Serialization; 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 e5d9978e2..000000000 --- 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 f947a5560..000000000 --- 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 000000000..468570832 --- /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 000000000..d76c8ce02 --- /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 ed29fb0ae..a0eebf4b3 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); - if (context.Type is not { IsEnum: true }) { + 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; } - if (context.Type.IsDefined(typeof(FlagsAttribute), false)) { + 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.JsonTypeInfo.Type is not { IsEnum: true }) { + return; + } + + 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 @@ internal sealed class EnumSchemaFilter : ISchemaFilter { continue; } - IOpenApiPrimitive enumObject; + IOpenApiAny enumObject; if (TryCast(enumValue, out int intValue)) { enumObject = new OpenApiInteger(intValue); @@ -105,3 +132,4 @@ internal sealed class EnumSchemaFilter : ISchemaFilter { } } } +#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 7631f2aa1..a7221dbe1 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 b65cab55a..7d107c468 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 03070a97f..3d32436b3 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 fade3b3f1..c5bf1debe 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 274157c86..cb137bcbd 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 89ad55fe7..f1ca8426f 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 027a0b761..a39cef77a 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 b073e3f99..c7536c83e 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 dffa6b742..ce5a031b8 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 982a2a751..65d3d96c1 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 f874c7fbe..66891905f 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 76ad02683..d943e99d5 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 19bc9bc51..5954246d3 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 @@ public sealed class TwoFactorAuthenticationConfirmationsRequest { } } - /// - /// 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 91da0b52d..33366a218 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 0d6d29fdd..c23a0adcc 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 @@ using ArchiSteamFarm.Storage; 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 2a1993247..b43a51860 100644 --- a/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs +++ b/ArchiSteamFarm/IPC/Responses/AddLicenseResult.cs @@ -30,11 +30,13 @@ using SteamKit2; 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 76a69053e..68cf2b384 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 6dd1050e7..0edf60c3e 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 409380f6a..2e2149e7b 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 @@ using ArchiSteamFarm.Localization; 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 @@ public sealed class GenericResponse : 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 4b8f5f47c..9fe74fac7 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 @@ using ArchiSteamFarm.Web.GitHub.Data; 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 97c16d09f..865837b3e 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 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; 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 e621db74f..e09e257af 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 2082b9cf0..eb785995d 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 @@ using System.Text.Json.Serialization; 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 03a104633..f9b6c160e 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 70b1f08fb..6c06d7f12 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 ad97b3473..ce8066dfb 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 49ae1bc2b..d13280c13 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 125a052a7..b76e34181 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 00a35bdc0..6cd34ae44 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 dbf161efe..bece96464 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 f7a8a99bc..826d0e7d2 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 267260477..e395d29f2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,6 +5,7 @@ + @@ -16,8 +17,7 @@ - - +