diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9db2811..40f83e57 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,6 +5,11 @@ on: branches-ignore: - stable - alpha + pull_request: + types: [opened, synchronize] + branches: + - stable + - alpha jobs: build: diff --git a/Documentation/guides/advanced/Voice/VoiceModule.cs b/Documentation/guides/advanced/Voice/VoiceModule.cs index 3da53139..f49239e2 100644 --- a/Documentation/guides/advanced/Voice/VoiceModule.cs +++ b/Documentation/guides/advanced/Voice/VoiceModule.cs @@ -34,9 +34,6 @@ public async Task PlayAsync(string track) // Connect await voiceClient.StartAsync(); - // Wait for ready - await voiceClient.ReadyAsync; - // Enter speaking state, to be able to send voice await voiceClient.EnterSpeakingStateAsync(SpeakingFlags.Microphone); @@ -124,9 +121,6 @@ public async Task EchoAsync() // Connect await voiceClient.StartAsync(); - // Wait for ready - await voiceClient.ReadyAsync; - // Enter speaking state, to be able to send voice await voiceClient.EnterSpeakingStateAsync(SpeakingFlags.Microphone); diff --git a/Documentation/guides/advanced/sharding.md b/Documentation/guides/advanced/sharding.md index 7d505518..c1bdfdc8 100644 --- a/Documentation/guides/advanced/sharding.md +++ b/Documentation/guides/advanced/sharding.md @@ -10,14 +10,14 @@ Sharding is splitting your bot into multiple @"NetCord.Gateway.GatewayClient"s. ## How to shard? -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) -With hosting, to start sharding, instead of calling @NetCord.Hosting.Gateway.GatewayClientHostBuilderExtensions.UseDiscordGateway(Microsoft.Extensions.Hosting.IHostBuilder), you need to call @NetCord.Hosting.Gateway.ShardedGatewayClientHostBuilderExtensions.UseDiscordShardedGateway(Microsoft.Extensions.Hosting.IHostBuilder). Example: +To start sharding with the generic host, instead of calling @NetCord.Hosting.Gateway.GatewayClientHostBuilderExtensions.UseDiscordGateway(Microsoft.Extensions.Hosting.IHostBuilder), you need to call @NetCord.Hosting.Gateway.ShardedGatewayClientHostBuilderExtensions.UseDiscordShardedGateway(Microsoft.Extensions.Hosting.IHostBuilder). Example: [!code-cs[Program.cs](ShardingHosting/Program.cs)] Also note that you need to use @NetCord.Hosting.Gateway.IShardedGatewayEventHandler or @NetCord.Hosting.Gateway.IShardedGatewayEventHandler`1 instead of @NetCord.Hosting.Gateway.IGatewayEventHandler or @NetCord.Hosting.Gateway.IGatewayEventHandler`1 for event handlers. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerServiceCollectionExtensions.AddShardedGatewayEventHandlers(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Reflection.Assembly) to add event handlers. -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) To start sharding, you need to create an instance of @NetCord.Gateway.ShardedGatewayClient. Its usage is very similar to @NetCord.Gateway.GatewayClient. Example: [!code-cs[Program.cs](Sharding/Program.cs)] diff --git a/Documentation/guides/advanced/voice.md b/Documentation/guides/advanced/voice.md index 6006001b..4c80523f 100644 --- a/Documentation/guides/advanced/voice.md +++ b/Documentation/guides/advanced/voice.md @@ -10,7 +10,7 @@ Follow the [installation guide](installing-native-dependencies.md) to install th > In the following examples streams and @NetCord.Gateway.Voice.VoiceClient instances are not disposed because they should be stored somewhere and disposed later. ### Sending Voice -[!code-cs[VoiceModule.cs](Voice/VoiceModule.cs#L12-L102)] +[!code-cs[VoiceModule.cs](Voice/VoiceModule.cs#L12-L99)] ### Receiving Voice -[!code-cs[VoiceModule.cs](Voice/VoiceModule.cs#L104-L146)] \ No newline at end of file +[!code-cs[VoiceModule.cs](Voice/VoiceModule.cs#L101-L140)] \ No newline at end of file diff --git a/Documentation/guides/events/first-events.md b/Documentation/guides/events/first-events.md index c6ee916a..f658ccee 100644 --- a/Documentation/guides/events/first-events.md +++ b/Documentation/guides/events/first-events.md @@ -1,8 +1,8 @@ # First Events -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) -With hosting, the preferred way to receive events is by implementing @NetCord.Hosting.Gateway.IGatewayEventHandler or @NetCord.Hosting.Gateway.IGatewayEventHandler`1. +The preferred way to receive events with the generic host is by implementing @NetCord.Hosting.Gateway.IGatewayEventHandler or @NetCord.Hosting.Gateway.IGatewayEventHandler`1. First, use @NetCord.Hosting.Gateway.GatewayEventHandlerServiceCollectionExtensions.AddGatewayEventHandlers(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Reflection.Assembly) to add all event handlers in an assembly. You also need to call @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the handlers to the client. [!code-cs[Program.cs](FirstEventsHosting/Program.cs?highlight=18,21)] @@ -28,7 +28,7 @@ Other events work similar to these. You can play with them if you want! > [!NOTE] > When using @NetCord.Gateway.ShardedGatewayClient, you need to implement @NetCord.Hosting.Gateway.IShardedGatewayEventHandler or @NetCord.Hosting.Gateway.IShardedGatewayEventHandler`1 instead. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerServiceCollectionExtensions.AddShardedGatewayEventHandlers(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.Reflection.Assembly) to add event handlers instead. -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) ### MessageCreate Event To listen to the event, add the following lines before `client.StartAsync()`! diff --git a/Documentation/guides/events/intents.md b/Documentation/guides/events/intents.md index 0f60ddd5..058ceeb9 100644 --- a/Documentation/guides/events/intents.md +++ b/Documentation/guides/events/intents.md @@ -12,10 +12,10 @@ Privileged intents are intents that you need to enable in [Discord Developer Por Intents in NetCord are handled by @NetCord.Gateway.GatewayIntents. You specify them like this: -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) [!code-cs[Program.cs](IntentsHosting/Program.cs?highlight=6#L6-L13)] -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) [!code-cs[Program.cs](Intents/Program.cs?highlight=3#L4-L7)] *** diff --git a/Documentation/guides/getting-started/coding.md b/Documentation/guides/getting-started/coding.md index 803fc756..dee3770f 100644 --- a/Documentation/guides/getting-started/coding.md +++ b/Documentation/guides/getting-started/coding.md @@ -10,9 +10,9 @@ Before we start, you need a token of your bot... so you need to go to the [Disco > [!IMPORTANT] > You should never give your token to anybody. -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) -With hosting, you can just use @NetCord.Hosting.Gateway.GatewayClientHostBuilderExtensions.UseDiscordGateway(Microsoft.Extensions.Hosting.IHostBuilder) to add your bot to the host. Quite easy, right? +With the generic host, you can just use @NetCord.Hosting.Gateway.GatewayClientHostBuilderExtensions.UseDiscordGateway(Microsoft.Extensions.Hosting.IHostBuilder) to add your bot to the host. Quite easy, right? [!code-cs[Program.cs](CodingHosting/Program.cs)] Also note that the token needs to be stored in the configuration. You can for example use `appsettings.json` file. It should look like this: @@ -21,7 +21,7 @@ Also note that the token needs to be stored in the configuration. You can for ex Now, when you run the code, your bot should be online! ![](../../images/coding_BotOnline.png) -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) Add the following lines to file `Program.cs`. [!code-cs[Program.cs](Coding/Program.cs#L1-L4)] diff --git a/Documentation/guides/services/application-commands/Introduction/MessageCommandModule.cs b/Documentation/guides/services/application-commands/Introduction/MessageCommandModule.cs index a6e9058d..0a0ac78e 100644 --- a/Documentation/guides/services/application-commands/Introduction/MessageCommandModule.cs +++ b/Documentation/guides/services/application-commands/Introduction/MessageCommandModule.cs @@ -7,4 +7,3 @@ public class MessageCommandModule : ApplicationCommandModule Context.Target.CreatedAt.ToString(); } - diff --git a/Documentation/guides/services/application-commands/Introduction/UserCommandModule.cs b/Documentation/guides/services/application-commands/Introduction/UserCommandModule.cs index 67ca082b..28d2c8a5 100644 --- a/Documentation/guides/services/application-commands/Introduction/UserCommandModule.cs +++ b/Documentation/guides/services/application-commands/Introduction/UserCommandModule.cs @@ -7,4 +7,3 @@ public class UserCommandModule : ApplicationCommandModule [UserCommand("ID")] public string Id() => Context.Target.Id.ToString(); } - diff --git a/Documentation/guides/services/application-commands/introduction.md b/Documentation/guides/services/application-commands/introduction.md index 75c64060..de5a98cd 100644 --- a/Documentation/guides/services/application-commands/introduction.md +++ b/Documentation/guides/services/application-commands/introduction.md @@ -8,12 +8,12 @@ > > **must** be lowercase. -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) -With hosting, adding application commands is very easy. Use @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostBuilderExtensions.UseApplicationCommands``2(Microsoft.Extensions.Hosting.IHostBuilder) to add an application command service to your host builder. Then, use @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostExtensions.AddSlashCommand*, @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostExtensions.AddUserCommand* or @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostExtensions.AddMessageCommand* to add an application command using the ASP.NET Core minimal APIs way and/or use @NetCord.Hosting.Services.ServicesHostExtensions.AddModules(Microsoft.Extensions.Hosting.IHost,System.Reflection.Assembly) to add modules from an assembly. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the service event handlers. +Adding application commands with the generic host is very easy. Use @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostBuilderExtensions.UseApplicationCommands``2(Microsoft.Extensions.Hosting.IHostBuilder) to add an application command service to your host builder. Then, use @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostExtensions.AddSlashCommand*, @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostExtensions.AddUserCommand* or @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceHostExtensions.AddMessageCommand* to add an application command using the ASP.NET Core minimal APIs way and/or use @NetCord.Hosting.Services.ServicesHostExtensions.AddModules(Microsoft.Extensions.Hosting.IHost,System.Reflection.Assembly) to add modules from an assembly. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the service event handlers. [!code-cs[Program.cs](IntroductionHosting/Program.cs?highlight=11-13,16-20)] -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) First, add the following lines to using the section. [!code-cs[Program.cs](Introduction/Program.cs#L4-L5)] diff --git a/Documentation/guides/services/application-commands/localizations.md b/Documentation/guides/services/application-commands/localizations.md index d741fbb2..072f81b6 100644 --- a/Documentation/guides/services/application-commands/localizations.md +++ b/Documentation/guides/services/application-commands/localizations.md @@ -6,10 +6,10 @@ To localize application commands, you need to use @NetCord.Services.ApplicationC The samples below show how to specify the @NetCord.Services.ApplicationCommands.JsonLocalizationsProvider. -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) [!code-cs[Program.cs](LocalizationsHosting/Program.cs?highlight=6#L9-L16)] -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) [!code-cs[Program.cs](Localizations/Program.cs?highlight=3#L12-L15)] *** diff --git a/Documentation/guides/services/component-interactions/introduction.md b/Documentation/guides/services/component-interactions/introduction.md index a0e3ddb9..709df467 100644 --- a/Documentation/guides/services/component-interactions/introduction.md +++ b/Documentation/guides/services/component-interactions/introduction.md @@ -1,11 +1,11 @@ # Introduction -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) -With hosting, adding component interactions is very easy. Use @NetCord.Hosting.Services.ComponentInteractions.ComponentInteractionServiceHostBuilderExtensions.UseComponentInteractions``2(Microsoft.Extensions.Hosting.IHostBuilder) to add a component interaction service to your host builder. Then, use @NetCord.Hosting.Services.ComponentInteractions.ComponentInteractionServiceHostExtensions.AddComponentInteraction* to add a component interaction using the ASP.NET Core minimal APIs way and/or use @NetCord.Hosting.Services.ServicesHostExtensions.AddModules(Microsoft.Extensions.Hosting.IHost,System.Reflection.Assembly) to add modules from an assembly. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the service event handlers. +Adding component interactions with the generic host is very easy. Use @NetCord.Hosting.Services.ComponentInteractions.ComponentInteractionServiceHostBuilderExtensions.UseComponentInteractions``2(Microsoft.Extensions.Hosting.IHostBuilder) to add a component interaction service to your host builder. Then, use @NetCord.Hosting.Services.ComponentInteractions.ComponentInteractionServiceHostExtensions.AddComponentInteraction* to add a component interaction using the ASP.NET Core minimal APIs way and/or use @NetCord.Hosting.Services.ServicesHostExtensions.AddModules(Microsoft.Extensions.Hosting.IHost,System.Reflection.Assembly) to add modules from an assembly. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the service event handlers. [!code-cs[Program.cs](IntroductionHosting/Program.cs?highlight=11-17,20-28)] -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) First, add the following lines to the using section. [!code-cs[Program.cs](Introduction/Program.cs#L4-L5)] diff --git a/Documentation/guides/services/text-commands/introduction.md b/Documentation/guides/services/text-commands/introduction.md index 5e5ee20d..8e34cd55 100644 --- a/Documentation/guides/services/text-commands/introduction.md +++ b/Documentation/guides/services/text-commands/introduction.md @@ -1,8 +1,8 @@ # Introduction -## [Hosting](#tab/hosting) +## [Generic Host](#tab/generic-host) -With hosting, adding commands is very easy. Use @NetCord.Hosting.Services.Commands.CommandServiceHostBuilderExtensions.UseCommands``1(Microsoft.Extensions.Hosting.IHostBuilder) to add a command service to your host builder. Then, use @NetCord.Hosting.Services.Commands.CommandServiceHostExtensions.AddCommand* to add a command using the ASP.NET Core minimal APIs way and/or use @NetCord.Hosting.Services.ServicesHostExtensions.AddModules(Microsoft.Extensions.Hosting.IHost,System.Reflection.Assembly) to add modules from an assembly. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the service event handlers. +Adding commands with the generic host is very easy. Use @NetCord.Hosting.Services.Commands.CommandServiceHostBuilderExtensions.UseCommands``1(Microsoft.Extensions.Hosting.IHostBuilder) to add a command service to your host builder. Then, use @NetCord.Hosting.Services.Commands.CommandServiceHostExtensions.AddCommand* to add a command using the ASP.NET Core minimal APIs way and/or use @NetCord.Hosting.Services.ServicesHostExtensions.AddModules(Microsoft.Extensions.Hosting.IHost,System.Reflection.Assembly) to add modules from an assembly. You also need to use @NetCord.Hosting.Gateway.GatewayEventHandlerHostExtensions.UseGatewayEventHandlers(Microsoft.Extensions.Hosting.IHost) to bind the service event handlers. [!code-cs[Program.cs](IntroductionHosting/Program.cs?highlight=10,13-15)] ### Specifying a prefix @@ -10,7 +10,7 @@ With hosting, adding commands is very easy. Use @NetCord.Hosting.Services.Comman You can specify a prefix in the configuration. You can for example use `appsettings.json` file. It should look like this: [!code-json[appsettings.json](IntroductionHosting/appsettings.json)] -## [Without Hosting](#tab/without-hosting) +## [Bare Bones](#tab/bare-bones) First, add the following lines to the using section. [!code-cs[Program.cs](Introduction/Program.cs#L3-L4)] diff --git a/Documentation/package-lock.json b/Documentation/package-lock.json index 2fe817ac..238e29a7 100644 --- a/Documentation/package-lock.json +++ b/Documentation/package-lock.json @@ -7504,11 +7504,11 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { diff --git a/Documentation/templates-src/NetCord/src/nav.ts b/Documentation/templates-src/NetCord/src/nav.ts index 678f030f..fccc5fab 100644 --- a/Documentation/templates-src/NetCord/src/nav.ts +++ b/Documentation/templates-src/NetCord/src/nav.ts @@ -188,19 +188,33 @@ function inThisArticle(): TemplateResult { function findActiveItem(items: (NavItem | NavItemContainer)[]): NavItem { const url = new URL(window.location.href); + let activeItem: NavItem; + let maxPrefix = 0; for (const item of items.map((i) => ("items" in i ? i.items : i)).flat()) { - const href = item.href; - if (!isExternalHref(href) && commonUrl(url, href)) - return item; + if (isExternalHref(item.href)) { + continue; + } + const prefix = commonUrlPrefix(url, item.href); + if (prefix == maxPrefix) { + activeItem = undefined; + } + else if (prefix > maxPrefix) { + maxPrefix = prefix; + activeItem = item; + } } } -function commonUrl(url: URL, base: URL): boolean { - const urlPath = normalizePath(url.pathname); - const basePath = normalizePath(base.pathname); - return urlPath === basePath; -} - -function normalizePath(url: string): string { - return url.replace(/\/(?:index.html)?$/, "/"); +function commonUrlPrefix(url: URL, base: URL): number { + const urlSegments = url.pathname.split("/"); + const baseSegments = base.pathname.split("/"); + let i = 0; + while ( + i < urlSegments.length && + i < baseSegments.length && + urlSegments[i] === baseSegments[i] + ) { + i++; + } + return i; } diff --git a/Hosting/NetCord.Hosting.AspNetCore/EndpointRouteBuilderExtensions.cs b/Hosting/NetCord.Hosting.AspNetCore/EndpointRouteBuilderExtensions.cs index 899b62e9..99bfce66 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/EndpointRouteBuilderExtensions.cs +++ b/Hosting/NetCord.Hosting.AspNetCore/EndpointRouteBuilderExtensions.cs @@ -75,11 +75,11 @@ private static async Task HandleRequestAsync(HttpContext context, HttpInteractio return null; var response = context.Response; - var interaction = HttpInteractionFactory.Create(timestampAndBody.Span[timestampByteCount..], async (interaction, interactionCallback, properties) => + var interaction = HttpInteractionFactory.Create(timestampAndBody.Span[timestampByteCount..], async (interaction, interactionCallback, properties, cancellationToken) => { using var content = interactionCallback.Serialize(); response.ContentType = content.Headers.ContentType!.ToString(); - await content.CopyToAsync(response.Body).ConfigureAwait(false); + await content.CopyToAsync(response.Body, cancellationToken).ConfigureAwait(false); await response.CompleteAsync().ConfigureAwait(false); }, client); diff --git a/Hosting/NetCord.Hosting.AspNetCore/NetCord.Hosting.AspNetCore.csproj b/Hosting/NetCord.Hosting.AspNetCore/NetCord.Hosting.AspNetCore.csproj index 25b72123..d8d2f25c 100644 --- a/Hosting/NetCord.Hosting.AspNetCore/NetCord.Hosting.AspNetCore.csproj +++ b/Hosting/NetCord.Hosting.AspNetCore/NetCord.Hosting.AspNetCore.csproj @@ -13,7 +13,7 @@ SmallSquare.png MIT $(VersionPrefix) - alpha.72 + alpha.87 The modern and fully customizable C# Discord library. true diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandInteractionHandler.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandInteractionHandler.cs index fa0e2e6b..54d49683 100644 --- a/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandInteractionHandler.cs +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandInteractionHandler.cs @@ -4,7 +4,6 @@ using NetCord.Gateway; using NetCord.Hosting.Gateway; -using NetCord.Services; using NetCord.Services.ApplicationCommands; namespace NetCord.Hosting.Services.ApplicationCommands; @@ -19,7 +18,7 @@ internal unsafe partial class ApplicationCommandInteractionHandler, Interaction, GatewayClient?, ValueTask> _handleAsync; private readonly Func _createContext; - private readonly Func _handleResultAsync; + private readonly IApplicationCommandResultHandler _resultHandler; private readonly GatewayClient? _client; public ApplicationCommandInteractionHandler(IServiceProvider services, @@ -44,7 +43,7 @@ public ApplicationCommandInteractionHandler(IServiceProvider services, _handleAsync = &HandleInteractionAsync; _createContext = optionsValue.CreateContext ?? ContextHelper.CreateContextDelegate(); - _handleResultAsync = optionsValue.HandleResultAsync; + _resultHandler = optionsValue.ResultHandler; _client = client; } @@ -89,7 +88,7 @@ private async ValueTask HandleInteractionAsyncCore(TInteraction interaction, Gat try { - await _handleResultAsync(result, interaction, client, _logger, services).ConfigureAwait(false); + await _resultHandler.HandleResultAsync(result, context, client, _logger, services).ConfigureAwait(false); } catch (Exception exceptionHandlerException) { diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandResultHandler.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandResultHandler.cs new file mode 100644 index 00000000..ee815b7c --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandResultHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Rest; +using NetCord.Services; +using NetCord.Services.ApplicationCommands; + +namespace NetCord.Hosting.Services.ApplicationCommands; + +public class ApplicationCommandResultHandler(MessageFlags? messageFlags = null) : IApplicationCommandResultHandler where TContext : IApplicationCommandContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TContext context, GatewayClient? client, ILogger logger, IServiceProvider services) + { + if (result is not IFailResult failResult) + return default; + + var resultMessage = failResult.Message; + + var interaction = context.Interaction; + + if (failResult is IExceptionResult exceptionResult) + logger.LogError(exceptionResult.Exception, "Execution of an application command of name '{Name}' failed with an exception", interaction.Data.Name); + else + logger.LogDebug("Execution of an application command of name '{Name}' failed with '{Message}'", interaction.Data.Name, resultMessage); + + InteractionMessageProperties message = new() + { + Content = resultMessage, + Flags = messageFlags, + }; + + return new(interaction.SendResponseAsync(InteractionCallback.Message(message))); + } +} diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceHostedService.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceHostedService.cs index 875cb973..31616ef4 100644 --- a/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceHostedService.cs +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceHostedService.cs @@ -23,7 +23,7 @@ public async Task StartAsync(CancellationToken cancellationToken) foreach (var service in services.GetServices()) applicationCommandServiceManager.AddService(service); - var commands = await applicationCommandServiceManager.CreateCommandsAsync(client, token.Id, true).ConfigureAwait(false); + var commands = await applicationCommandServiceManager.CreateCommandsAsync(client, token.Id, true, cancellationToken: cancellationToken).ConfigureAwait(false); logger.LogInformation("{count} application command(s) created", commands.Count); } diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceOptions.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceOptions.cs index 559c37fe..29af31e2 100644 --- a/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceOptions.cs +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/ApplicationCommandServiceOptions.cs @@ -1,8 +1,4 @@ -using Microsoft.Extensions.Logging; - -using NetCord.Gateway; -using NetCord.Rest; -using NetCord.Services; +using NetCord.Gateway; using NetCord.Services.ApplicationCommands; namespace NetCord.Hosting.Services.ApplicationCommands; @@ -15,38 +11,12 @@ public class ApplicationCommandServiceOptions where TInt public Func? CreateContext { get; set; } - public Func HandleResultAsync { get; set; } = (result, interaction, client, logger, services) => - { - if (result is not IFailResult failResult) - return default; - - var message = failResult.Message; - - if (failResult is IExceptionResult exceptionResult) - logger.LogError(exceptionResult.Exception, "Execution of an application command of name '{Name}' failed with an exception", interaction.Data.Name); - else - logger.LogDebug("Execution of an application command of name '{Name}' failed with '{Message}'", interaction.Data.Name, message); - - return new(interaction.SendResponseAsync(InteractionCallback.Message(message))); - }; + public IApplicationCommandResultHandler ResultHandler { get; set; } = new ApplicationCommandResultHandler(); } public class ApplicationCommandServiceOptions : ApplicationCommandServiceOptions where TInteraction : ApplicationCommandInteraction where TContext : IApplicationCommandContext where TAutocompleteContext : IAutocompleteInteractionContext { public Func? CreateAutocompleteContext { get; set; } - public Func HandleAutocompleteResultAsync { get; set; } = (result, interaction, client, logger, services) => - { - if (result is not IFailResult failResult) - return default; - - var commandName = interaction.Data.Name; - - if (failResult is IExceptionResult exceptionResult) - logger.LogError(exceptionResult.Exception, "Execution of an autocomplete for application command of name '{Name}' failed with an exception", commandName); - else - logger.LogDebug("Execution of an autocomplete for application command of name '{Name}' failed with '{Message}'", commandName, failResult.Message); - - return default; - }; + public IAutocompleteInteractionResultHandler AutocompleteResultHandler { get; set; } = new AutocompleteInteractionResultHandler(); } diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/AutocompleteInteractionHandler.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/AutocompleteInteractionHandler.cs index 36a80d1f..4b72bba2 100644 --- a/Hosting/NetCord.Hosting.Services/ApplicationCommands/AutocompleteInteractionHandler.cs +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/AutocompleteInteractionHandler.cs @@ -4,7 +4,6 @@ using NetCord.Gateway; using NetCord.Hosting.Gateway; -using NetCord.Services; using NetCord.Services.ApplicationCommands; namespace NetCord.Hosting.Services.ApplicationCommands; @@ -19,7 +18,7 @@ internal unsafe partial class AutocompleteInteractionHandler, Interaction, GatewayClient?, ValueTask> _handleAsync; private readonly Func _createContext; - private readonly Func _handleResultAsync; + private readonly IAutocompleteInteractionResultHandler _resultHandler; private readonly GatewayClient? _client; public AutocompleteInteractionHandler(IServiceProvider services, @@ -44,7 +43,7 @@ public AutocompleteInteractionHandler(IServiceProvider services, _handleAsync = &HandleInteractionAsync; _createContext = optionsValue.CreateAutocompleteContext ?? ContextHelper.CreateContextDelegate(); - _handleResultAsync = optionsValue.HandleAutocompleteResultAsync; + _resultHandler = optionsValue.AutocompleteResultHandler; _client = client; } @@ -89,7 +88,7 @@ private async ValueTask HandleInteractionAsyncCore(AutocompleteInteraction inter try { - await _handleResultAsync(result, interaction, client, _logger, services).ConfigureAwait(false); + await _resultHandler.HandleResultAsync(result, context, client, _logger, services).ConfigureAwait(false); } catch (Exception exceptionHandlerException) { diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/AutocompleteInteractionResultHandler.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/AutocompleteInteractionResultHandler.cs new file mode 100644 index 00000000..ae436fe7 --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/AutocompleteInteractionResultHandler.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Services; +using NetCord.Services.ApplicationCommands; + +namespace NetCord.Hosting.Services.ApplicationCommands; + +public class AutocompleteInteractionResultHandler : IAutocompleteInteractionResultHandler where TAutocompleteContext : IAutocompleteInteractionContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TAutocompleteContext context, GatewayClient? client, ILogger logger, IServiceProvider services) + { + if (result is not IFailResult failResult) + return default; + + var commandName = context.Interaction.Data.Name; + + if (failResult is IExceptionResult exceptionResult) + logger.LogError(exceptionResult.Exception, "Execution of an autocomplete for application command of name '{Name}' failed with an exception", commandName); + else + logger.LogDebug("Execution of an autocomplete for application command of name '{Name}' failed with '{Message}'", commandName, failResult.Message); + + return default; + } +} diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/IApplicationCommandResultHandler.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/IApplicationCommandResultHandler.cs new file mode 100644 index 00000000..61eaf576 --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/IApplicationCommandResultHandler.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Services; +using NetCord.Services.ApplicationCommands; + +namespace NetCord.Hosting.Services.ApplicationCommands; + +public interface IApplicationCommandResultHandler where TContext : IApplicationCommandContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TContext context, GatewayClient? client, ILogger logger, IServiceProvider services); +} diff --git a/Hosting/NetCord.Hosting.Services/ApplicationCommands/IAutocompleteInteractionResultHandler.cs b/Hosting/NetCord.Hosting.Services/ApplicationCommands/IAutocompleteInteractionResultHandler.cs new file mode 100644 index 00000000..50a0cb7c --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/ApplicationCommands/IAutocompleteInteractionResultHandler.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Services; +using NetCord.Services.ApplicationCommands; + +namespace NetCord.Hosting.Services.ApplicationCommands; + +public interface IAutocompleteInteractionResultHandler where TAutocompleteContext : IAutocompleteInteractionContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TAutocompleteContext context, GatewayClient? client, ILogger logger, IServiceProvider services); +} diff --git a/Hosting/NetCord.Hosting.Services/Commands/CommandHandler.cs b/Hosting/NetCord.Hosting.Services/Commands/CommandHandler.cs index d0577dea..258a6d99 100644 --- a/Hosting/NetCord.Hosting.Services/Commands/CommandHandler.cs +++ b/Hosting/NetCord.Hosting.Services/Commands/CommandHandler.cs @@ -4,7 +4,6 @@ using NetCord.Gateway; using NetCord.Hosting.Gateway; -using NetCord.Services; using NetCord.Services.Commands; namespace NetCord.Hosting.Services.Commands; @@ -20,7 +19,7 @@ internal unsafe partial class CommandHandler : IGatewayEventHandler, Message, GatewayClient, ValueTask> _handleAsync; private readonly Func> _getPrefixLengthAsync; private readonly Func _createContext; - private readonly Func _handleResultAsync; + private readonly ICommandResultHandler _resultHandler; private readonly GatewayClient? _client; public CommandHandler(IServiceProvider services, @@ -46,7 +45,7 @@ public CommandHandler(IServiceProvider services, _getPrefixLengthAsync = GetGetPrefixLengthAsyncDelegate(optionsValue); _createContext = optionsValue.CreateContext ?? ContextHelper.CreateContextDelegate(); - _handleResultAsync = optionsValue.HandleResultAsync; + _resultHandler = optionsValue.ResultHandler; _client = client; } @@ -134,7 +133,7 @@ private async ValueTask HandleMessageAsyncCore(Message message, GatewayClient cl try { - await _handleResultAsync(result, message, client, _logger, services).ConfigureAwait(false); + await _resultHandler.HandleResultAsync(result, context, client, _logger, services).ConfigureAwait(false); } catch (Exception exceptionHandlerException) { diff --git a/Hosting/NetCord.Hosting.Services/Commands/CommandResultHandler.cs b/Hosting/NetCord.Hosting.Services/Commands/CommandResultHandler.cs new file mode 100644 index 00000000..d8d009ba --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/Commands/CommandResultHandler.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Services; +using NetCord.Services.Commands; + +namespace NetCord.Hosting.Services.Commands; + +public class CommandResultHandler(MessageFlags? messageFlags = null) : ICommandResultHandler where TContext : ICommandContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TContext context, GatewayClient client, ILogger logger, IServiceProvider services) + { + if (result is not IFailResult failResult) + return default; + + var resultMessage = failResult.Message; + + var message = context.Message; + + if (failResult is IExceptionResult exceptionResult) + logger.LogError(exceptionResult.Exception, "Execution of a command with content '{Content}' failed with an exception", message.Content); + else + logger.LogDebug("Execution of a command with content '{Content}' failed with '{Message}'", message.Content, resultMessage); + + return new(message.ReplyAsync(new() + { + Content = resultMessage, + FailIfNotExists = false, + Flags = messageFlags, + })); + } +} diff --git a/Hosting/NetCord.Hosting.Services/Commands/CommandServiceOptions.cs b/Hosting/NetCord.Hosting.Services/Commands/CommandServiceOptions.cs index b59f27d2..de132b81 100644 --- a/Hosting/NetCord.Hosting.Services/Commands/CommandServiceOptions.cs +++ b/Hosting/NetCord.Hosting.Services/Commands/CommandServiceOptions.cs @@ -1,7 +1,4 @@ -using Microsoft.Extensions.Logging; - -using NetCord.Gateway; -using NetCord.Services; +using NetCord.Gateway; using NetCord.Services.Commands; namespace NetCord.Hosting.Services.Commands; @@ -20,22 +17,5 @@ public class CommandServiceOptions where TContext : ICommandContext public Func? CreateContext { get; set; } - public Func HandleResultAsync { get; set; } = (result, message, client, logger, services) => - { - if (result is not IFailResult failResult) - return default; - - string resultMessage = failResult.Message; - - if (failResult is IExceptionResult exceptionResult) - logger.LogError(exceptionResult.Exception, "Execution of a command with content '{Content}' failed with an exception", message.Content); - else - logger.LogDebug("Execution of a command with content '{Content}' failed with '{Message}'", message.Content, resultMessage); - - return new(message.ReplyAsync(new() - { - Content = resultMessage, - FailIfNotExists = false, - })); - }; + public ICommandResultHandler ResultHandler { get; set; } = new CommandResultHandler(); } diff --git a/Hosting/NetCord.Hosting.Services/Commands/ICommandResultHandler.cs b/Hosting/NetCord.Hosting.Services/Commands/ICommandResultHandler.cs new file mode 100644 index 00000000..bd3a4133 --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/Commands/ICommandResultHandler.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Services; +using NetCord.Services.Commands; + +namespace NetCord.Hosting.Services.Commands; + +public interface ICommandResultHandler where TContext : ICommandContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TContext context, GatewayClient client, ILogger logger, IServiceProvider services); +} diff --git a/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionHandler.cs b/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionHandler.cs index 12ff83dc..7a841413 100644 --- a/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionHandler.cs +++ b/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionHandler.cs @@ -4,7 +4,6 @@ using NetCord.Gateway; using NetCord.Hosting.Gateway; -using NetCord.Services; using NetCord.Services.ComponentInteractions; namespace NetCord.Hosting.Services.ComponentInteractions; @@ -19,7 +18,7 @@ internal unsafe partial class ComponentInteractionHandler, Interaction, GatewayClient?, ValueTask> _handleAsync; private readonly Func _createContext; - private readonly Func _handleResultAsync; + private readonly IComponentInteractionResultHandler _resultHandler; private readonly GatewayClient? _client; public ComponentInteractionHandler(IServiceProvider services, @@ -44,7 +43,7 @@ public ComponentInteractionHandler(IServiceProvider services, _handleAsync = &HandleInteractionAsync; _createContext = optionsValue.CreateContext ?? ContextHelper.CreateContextDelegate(); - _handleResultAsync = optionsValue.HandleResultAsync; + _resultHandler = optionsValue.ResultHandler; _client = client; } @@ -89,7 +88,7 @@ private async ValueTask HandleInteractionAsyncCore(TInteraction interaction, Gat try { - await _handleResultAsync(result, interaction, client, _logger, services).ConfigureAwait(false); + await _resultHandler.HandleResultAsync(result, context, client, _logger, services).ConfigureAwait(false); } catch (Exception exceptionHandlerException) { diff --git a/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionResultHandler.cs b/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionResultHandler.cs new file mode 100644 index 00000000..e8c8d01c --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionResultHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Rest; +using NetCord.Services; +using NetCord.Services.ComponentInteractions; + +namespace NetCord.Hosting.Services.ComponentInteractions; + +public class ComponentInteractionResultHandler(MessageFlags? messageFlags = null) : IComponentInteractionResultHandler where TContext : IComponentInteractionContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TContext context, GatewayClient? client, ILogger logger, IServiceProvider services) + { + if (result is not IFailResult failResult) + return default; + + var resultMessage = failResult.Message; + + var interaction = context.Interaction; + + if (failResult is IExceptionResult exceptionResult) + logger.LogError(exceptionResult.Exception, "Execution of an interaction of custom ID '{Id}' failed with an exception", interaction.Id); + else + logger.LogDebug("Execution of an interaction of custom ID '{Id}' failed with '{Message}'", interaction.Id, resultMessage); + + InteractionMessageProperties message = new() + { + Content = resultMessage, + Flags = messageFlags, + }; + + return new(interaction.SendResponseAsync(InteractionCallback.Message(message))); + } +} diff --git a/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionServiceOptions.cs b/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionServiceOptions.cs index f77427b3..21d5d51f 100644 --- a/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionServiceOptions.cs +++ b/Hosting/NetCord.Hosting.Services/ComponentInteractions/ComponentInteractionServiceOptions.cs @@ -1,8 +1,4 @@ -using Microsoft.Extensions.Logging; - -using NetCord.Gateway; -using NetCord.Rest; -using NetCord.Services; +using NetCord.Gateway; using NetCord.Services.ComponentInteractions; namespace NetCord.Hosting.Services.ComponentInteractions; @@ -15,18 +11,5 @@ public class ComponentInteractionServiceOptions where TI public Func? CreateContext { get; set; } - public Func HandleResultAsync { get; set; } = (result, interaction, client, logger, services) => - { - if (result is not IFailResult failResult) - return default; - - var message = failResult.Message; - - if (failResult is IExceptionResult exceptionResult) - logger.LogError(exceptionResult.Exception, "Execution of an interaction of custom ID '{Id}' failed with an exception", interaction.Id); - else - logger.LogDebug("Execution of an interaction of custom ID '{Id}' failed with '{Message}'", interaction.Id, message); - - return new(interaction.SendResponseAsync(InteractionCallback.Message(message))); - }; + public IComponentInteractionResultHandler ResultHandler { get; set; } = new ComponentInteractionResultHandler(); } diff --git a/Hosting/NetCord.Hosting.Services/ComponentInteractions/IComponentInteractionResultHandler.cs b/Hosting/NetCord.Hosting.Services/ComponentInteractions/IComponentInteractionResultHandler.cs new file mode 100644 index 00000000..ea5b6a07 --- /dev/null +++ b/Hosting/NetCord.Hosting.Services/ComponentInteractions/IComponentInteractionResultHandler.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Services; +using NetCord.Services.ComponentInteractions; + +namespace NetCord.Hosting.Services.ComponentInteractions; + +public interface IComponentInteractionResultHandler where TContext : IComponentInteractionContext +{ + public ValueTask HandleResultAsync(IExecutionResult result, TContext context, GatewayClient? client, ILogger logger, IServiceProvider services); +} diff --git a/Hosting/NetCord.Hosting.Services/NetCord.Hosting.Services.csproj b/Hosting/NetCord.Hosting.Services/NetCord.Hosting.Services.csproj index 9521fb17..163e050b 100644 --- a/Hosting/NetCord.Hosting.Services/NetCord.Hosting.Services.csproj +++ b/Hosting/NetCord.Hosting.Services/NetCord.Hosting.Services.csproj @@ -14,7 +14,7 @@ SmallSquare.png MIT $(VersionPrefix) - alpha.80 + alpha.95 The modern and fully customizable C# Discord library. true diff --git a/Hosting/NetCord.Hosting/Gateway/GatewayClientHostedService.cs b/Hosting/NetCord.Hosting/Gateway/GatewayClientHostedService.cs index 3a528161..cafcc64e 100644 --- a/Hosting/NetCord.Hosting/Gateway/GatewayClientHostedService.cs +++ b/Hosting/NetCord.Hosting/Gateway/GatewayClientHostedService.cs @@ -20,12 +20,12 @@ public GatewayClientHostedService(GatewayClient client, ILogger l public Task StartAsync(CancellationToken cancellationToken) { - return _client.StartAsync(); + return _client.StartAsync(cancellationToken: cancellationToken); } public Task StopAsync(CancellationToken cancellationToken) { - return _client.CloseAsync(); + return _client.CloseAsync(cancellationToken: cancellationToken); } private ValueTask LogAsync(LogMessage message) diff --git a/Hosting/NetCord.Hosting/NetCord.Hosting.csproj b/Hosting/NetCord.Hosting/NetCord.Hosting.csproj index 1b75ff1e..b0d7f2ac 100644 --- a/Hosting/NetCord.Hosting/NetCord.Hosting.csproj +++ b/Hosting/NetCord.Hosting/NetCord.Hosting.csproj @@ -13,7 +13,7 @@ SmallSquare.png MIT $(VersionPrefix) - alpha.71 + alpha.86 The modern and fully customizable C# Discord library. true diff --git a/NetCord.Services/ApplicationCommands/ApplicationCommandInfo.cs b/NetCord.Services/ApplicationCommands/ApplicationCommandInfo.cs index 05e7c3e0..c86fc5e4 100644 --- a/NetCord.Services/ApplicationCommands/ApplicationCommandInfo.cs +++ b/NetCord.Services/ApplicationCommands/ApplicationCommandInfo.cs @@ -58,5 +58,5 @@ private protected ApplicationCommandInfo(string name, public abstract LocalizationPathSegment LocalizationPathSegment { get; } public abstract ValueTask InvokeAsync(TContext context, ApplicationCommandServiceConfiguration configuration, IServiceProvider? serviceProvider); - public abstract ValueTask GetRawValueAsync(); + public abstract ValueTask GetRawValueAsync(CancellationToken cancellationToken = default); } diff --git a/NetCord.Services/ApplicationCommands/ApplicationCommandModule.cs b/NetCord.Services/ApplicationCommands/ApplicationCommandModule.cs index 156063f0..540958b4 100644 --- a/NetCord.Services/ApplicationCommands/ApplicationCommandModule.cs +++ b/NetCord.Services/ApplicationCommands/ApplicationCommandModule.cs @@ -4,19 +4,19 @@ namespace NetCord.Services.ApplicationCommands; public class ApplicationCommandModule : BaseApplicationCommandModule where TContext : IApplicationCommandContext { - public Task RespondAsync(InteractionCallback callback, RestRequestProperties? properties = null) => Context.Interaction.SendResponseAsync(callback, properties); + public Task RespondAsync(InteractionCallback callback, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.SendResponseAsync(callback, properties, cancellationToken); - public Task GetResponseAsync(RestRequestProperties? properties = null) => Context.Interaction.GetResponseAsync(properties); + public Task GetResponseAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.GetResponseAsync(properties, cancellationToken); - public Task ModifyResponseAsync(Action action, RestRequestProperties? properties = null) => Context.Interaction.ModifyResponseAsync(action, properties); + public Task ModifyResponseAsync(Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.ModifyResponseAsync(action, properties, cancellationToken); - public Task DeleteResponseAsync(RestRequestProperties? properties = null) => Context.Interaction.DeleteResponseAsync(properties); + public Task DeleteResponseAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.DeleteResponseAsync(properties, cancellationToken); - public Task FollowupAsync(InteractionMessageProperties message, RestRequestProperties? properties = null) => Context.Interaction.SendFollowupMessageAsync(message, properties); + public Task FollowupAsync(InteractionMessageProperties message, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.SendFollowupMessageAsync(message, properties, cancellationToken); - public Task GetFollowupAsync(ulong messageId, RestRequestProperties? properties = null) => Context.Interaction.GetFollowupMessageAsync(messageId, properties); + public Task GetFollowupAsync(ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.GetFollowupMessageAsync(messageId, properties, cancellationToken); - public Task ModifyFollowupAsync(ulong messageId, Action action, RestRequestProperties? properties = null) => Context.Interaction.ModifyFollowupMessageAsync(messageId, action, properties); + public Task ModifyFollowupAsync(ulong messageId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.ModifyFollowupMessageAsync(messageId, action, properties, cancellationToken); - public Task DeleteFollowupAsync(ulong messageId, RestRequestProperties? properties = null) => Context.Interaction.DeleteFollowupMessageAsync(messageId, properties); + public Task DeleteFollowupAsync(ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.DeleteFollowupMessageAsync(messageId, properties, cancellationToken); } diff --git a/NetCord.Services/ApplicationCommands/ApplicationCommandService.cs b/NetCord.Services/ApplicationCommands/ApplicationCommandService.cs index a6c2bcf8..d2f6436b 100644 --- a/NetCord.Services/ApplicationCommands/ApplicationCommandService.cs +++ b/NetCord.Services/ApplicationCommands/ApplicationCommandService.cs @@ -189,16 +189,16 @@ private void AddCommandInfo(ApplicationCommandInfo applicationCommandI _globalCommandsToCreate.Add(applicationCommandInfo); } - public async Task> CreateCommandsAsync(RestClient client, ulong applicationId, bool includeGuildCommands = false, RestRequestProperties? properties = null) + public async Task> CreateCommandsAsync(RestClient client, ulong applicationId, bool includeGuildCommands = false, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { var globalCommandsToCreate = _globalCommandsToCreate; int globalCount = globalCommandsToCreate.Count; var globalProperties = new ApplicationCommandProperties[globalCount]; for (int i = 0; i < globalCount; i++) - globalProperties[i] = await globalCommandsToCreate[i].GetRawValueAsync().ConfigureAwait(false); + globalProperties[i] = await globalCommandsToCreate[i].GetRawValueAsync(cancellationToken).ConfigureAwait(false); - var created = await client.BulkOverwriteGlobalApplicationCommandsAsync(applicationId, globalProperties, properties).ConfigureAwait(false); + var created = await client.BulkOverwriteGlobalApplicationCommandsAsync(applicationId, globalProperties, properties, cancellationToken).ConfigureAwait(false); int count = ((IApplicationCommandService)this).GetApproximateCommandsCount(includeGuildCommands); List>> commands = new(count); @@ -206,8 +206,8 @@ public async Task> CreateCommandsAsync(RestCli foreach (var (command, commandInfo) in created.Zip(globalCommandsToCreate)) { - commands.Add(new(command.Key, commandInfo)); - result.Add(command.Value); + commands.Add(new(command.Id, commandInfo)); + result.Add(command); } if (includeGuildCommands) @@ -219,13 +219,13 @@ public async Task> CreateCommandsAsync(RestCli var guildProperties = new ApplicationCommandProperties[guildCount]; for (int i = 0; i < guildCount; i++) - guildProperties[i] = await guildCommands[i].GetRawValueAsync().ConfigureAwait(false); + guildProperties[i] = await guildCommands[i].GetRawValueAsync(cancellationToken).ConfigureAwait(false); - var guildCreated = await client.BulkOverwriteGuildApplicationCommandsAsync(applicationId, guildCommandsPair.Key, guildProperties, properties).ConfigureAwait(false); + var guildCreated = await client.BulkOverwriteGuildApplicationCommandsAsync(applicationId, guildCommandsPair.Key, guildProperties, properties, cancellationToken).ConfigureAwait(false); foreach (var (command, commandInfo) in guildCreated.Zip(guildCommands)) { - commands.Add(new(command.Key, commandInfo)); - result.Add(command.Value); + commands.Add(new(command.Id, commandInfo)); + result.Add(command); } } } diff --git a/NetCord.Services/ApplicationCommands/ApplicationCommandServiceManager.cs b/NetCord.Services/ApplicationCommands/ApplicationCommandServiceManager.cs index f415ddea..3debd479 100644 --- a/NetCord.Services/ApplicationCommands/ApplicationCommandServiceManager.cs +++ b/NetCord.Services/ApplicationCommands/ApplicationCommandServiceManager.cs @@ -11,7 +11,7 @@ public void AddService(IApplicationCommandService service) _services.Add(service); } - public async Task> CreateCommandsAsync(RestClient client, ulong applicationId, bool includeGuildCommands = false, RestRequestProperties? properties = null) + public async Task> CreateCommandsAsync(RestClient client, ulong applicationId, bool includeGuildCommands = false, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { var services = _services.ToArray(); var serviceCommands = services.Select(s => (Service: s, Commands: new List>(s.GetApproximateCommandsCount(includeGuildCommands)))).ToArray(); @@ -22,16 +22,16 @@ public async Task> CreateCommandsAsync(RestCli var globalProperties = new ApplicationCommandProperties[globalLength]; for (int i = 0; i < globalLength; i++) - globalProperties[i] = await globalInfos[i].Command.GetRawValueAsync().ConfigureAwait(false); + globalProperties[i] = await globalInfos[i].Command.GetRawValueAsync(cancellationToken).ConfigureAwait(false); - var created = await client.BulkOverwriteGlobalApplicationCommandsAsync(applicationId, globalProperties, properties).ConfigureAwait(false); + var created = await client.BulkOverwriteGlobalApplicationCommandsAsync(applicationId, globalProperties, properties, cancellationToken).ConfigureAwait(false); List result = new(globalLength); foreach (var (command, (service, commands, commandInfo)) in created.Zip(globalInfos)) { - commands.Add(new(command.Key, commandInfo)); - result.Add(command.Value); + commands.Add(new(command.Id, commandInfo)); + result.Add(command); } if (includeGuildCommands) @@ -44,14 +44,14 @@ public async Task> CreateCommandsAsync(RestCli var guildProperties = new ApplicationCommandProperties[guildLength]; for (int i = 0; i < guildLength; i++) - guildProperties[i] = await guildInfos[i].Command.GetRawValueAsync().ConfigureAwait(false); + guildProperties[i] = await guildInfos[i].Command.GetRawValueAsync(cancellationToken).ConfigureAwait(false); - var guildCreated = await client.BulkOverwriteGuildApplicationCommandsAsync(applicationId, guildCommandsGroup.Key, guildProperties, properties).ConfigureAwait(false); + var guildCreated = await client.BulkOverwriteGuildApplicationCommandsAsync(applicationId, guildCommandsGroup.Key, guildProperties, properties, cancellationToken).ConfigureAwait(false); foreach (var (command, (service, commands, commandInfo)) in guildCreated.Zip(guildInfos)) { - commands.Add(new(command.Key, commandInfo)); - result.Add(command.Value); + commands.Add(new(command.Id, commandInfo)); + result.Add(command); } } } diff --git a/NetCord.Services/ApplicationCommands/IApplicationCommandInfo.cs b/NetCord.Services/ApplicationCommands/IApplicationCommandInfo.cs index 1933ede3..ea3775a5 100644 --- a/NetCord.Services/ApplicationCommands/IApplicationCommandInfo.cs +++ b/NetCord.Services/ApplicationCommands/IApplicationCommandInfo.cs @@ -15,5 +15,5 @@ public interface IApplicationCommandInfo public bool Nsfw { get; } public ulong? GuildId { get; } - public ValueTask GetRawValueAsync(); + public ValueTask GetRawValueAsync(CancellationToken cancellationToken = default); } diff --git a/NetCord.Services/ApplicationCommands/ILocalizationsProvider.cs b/NetCord.Services/ApplicationCommands/ILocalizationsProvider.cs index ff0c3c49..1f66a1c5 100644 --- a/NetCord.Services/ApplicationCommands/ILocalizationsProvider.cs +++ b/NetCord.Services/ApplicationCommands/ILocalizationsProvider.cs @@ -2,5 +2,5 @@ public interface ILocalizationsProvider { - public ValueTask?> GetLocalizationsAsync(IReadOnlyList path); + public ValueTask?> GetLocalizationsAsync(IReadOnlyList path, CancellationToken cancellationToken = default); } diff --git a/NetCord.Services/ApplicationCommands/ISubSlashCommandInfo.cs b/NetCord.Services/ApplicationCommands/ISubSlashCommandInfo.cs index 197095dd..9b3c8023 100644 --- a/NetCord.Services/ApplicationCommands/ISubSlashCommandInfo.cs +++ b/NetCord.Services/ApplicationCommands/ISubSlashCommandInfo.cs @@ -6,5 +6,5 @@ public interface ISubSlashCommandInfo : IAutocompleteInfo where TConte { public ValueTask InvokeAsync(TContext context, IReadOnlyList options, ApplicationCommandServiceConfiguration configuration, IServiceProvider? serviceProvider); - public ValueTask GetRawValueAsync(); + public ValueTask GetRawValueAsync(CancellationToken cancellationToken = default); } diff --git a/NetCord.Services/ApplicationCommands/JsonLocalizationsProvider.cs b/NetCord.Services/ApplicationCommands/JsonLocalizationsProvider.cs index 93174e36..e5722886 100644 --- a/NetCord.Services/ApplicationCommands/JsonLocalizationsProvider.cs +++ b/NetCord.Services/ApplicationCommands/JsonLocalizationsProvider.cs @@ -13,7 +13,7 @@ public partial class JsonLocalizationsProvider(JsonLocalizationsProviderConfigur private List? _localizationsInfo; - private async ValueTask> LoadLocalizationsAsync() + private async ValueTask> LoadLocalizationsAsync(CancellationToken cancellationToken) { var configuration = _configuration; @@ -34,7 +34,7 @@ private async ValueTask> LoadLocalizationsAsync() Localization localization; using (AutoTranscodingStream stream = new(File.OpenRead(file), Encoding.UTF8)) - localization = (await JsonSerializer.DeserializeAsync(stream, LocalizationSerializerContext.Default.Localization).ConfigureAwait(false))!; + localization = (await JsonSerializer.DeserializeAsync(stream, LocalizationSerializerContext.Default.Localization, cancellationToken).ConfigureAwait(false))!; result.Add(new(locale, localization)); } @@ -78,9 +78,9 @@ private static Regex CreateFileNameRegex(string fileNameFormat, bool ignoreCase) return new(pattern, options); } - public async ValueTask?> GetLocalizationsAsync(IReadOnlyList path) + public async ValueTask?> GetLocalizationsAsync(IReadOnlyList path, CancellationToken cancellationToken = default) { - var localizationsInfo = _localizationsInfo ??= await LoadLocalizationsAsync().ConfigureAwait(false); + var localizationsInfo = _localizationsInfo ??= await LoadLocalizationsAsync(cancellationToken).ConfigureAwait(false); int count = path.Count; int max = count - 1; diff --git a/NetCord.Services/ApplicationCommands/MessageCommandInfo.cs b/NetCord.Services/ApplicationCommands/MessageCommandInfo.cs index ff8d93ce..e50efce6 100644 --- a/NetCord.Services/ApplicationCommands/MessageCommandInfo.cs +++ b/NetCord.Services/ApplicationCommands/MessageCommandInfo.cs @@ -71,12 +71,12 @@ public override async ValueTask InvokeAsync(TContext context, return SuccessResult.Instance; } - public override async ValueTask GetRawValueAsync() + public override async ValueTask GetRawValueAsync(CancellationToken cancellationToken = default) { #pragma warning disable CS0618 // Type or member is obsolete return new MessageCommandProperties(Name) { - NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance)).ConfigureAwait(false), + NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), DefaultGuildUserPermissions = DefaultGuildUserPermissions, DMPermission = DMPermission, DefaultPermission = DefaultPermission, diff --git a/NetCord.Services/ApplicationCommands/SlashCommandGroupInfo.cs b/NetCord.Services/ApplicationCommands/SlashCommandGroupInfo.cs index ac14c416..61675a99 100644 --- a/NetCord.Services/ApplicationCommands/SlashCommandGroupInfo.cs +++ b/NetCord.Services/ApplicationCommands/SlashCommandGroupInfo.cs @@ -73,26 +73,26 @@ public override async ValueTask InvokeAsync(TContext context, return await subCommand.InvokeAsync(context, option.Options!, configuration, serviceProvider).ConfigureAwait(false); } - public override async ValueTask GetRawValueAsync() + public override async ValueTask GetRawValueAsync(CancellationToken cancellationToken = default) { var subCommands = SubCommands; var options = new ApplicationCommandOptionProperties[subCommands.Count]; int i = 0; foreach (var subCommand in subCommands.Values) - options[i++] = await subCommand.GetRawValueAsync().ConfigureAwait(false); + options[i++] = await subCommand.GetRawValueAsync(cancellationToken).ConfigureAwait(false); #pragma warning disable CS0618 // Type or member is obsolete return new SlashCommandProperties(Name, Description) { - NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance)).ConfigureAwait(false), + NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), DefaultGuildUserPermissions = DefaultGuildUserPermissions, DMPermission = DMPermission, DefaultPermission = DefaultPermission, IntegrationTypes = IntegrationTypes, Contexts = Contexts, Nsfw = Nsfw, - DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance)).ConfigureAwait(false), + DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), Options = options, }; #pragma warning restore CS0618 // Type or member is obsolete diff --git a/NetCord.Services/ApplicationCommands/SlashCommandInfo.cs b/NetCord.Services/ApplicationCommands/SlashCommandInfo.cs index c3870af4..52a2f609 100644 --- a/NetCord.Services/ApplicationCommands/SlashCommandInfo.cs +++ b/NetCord.Services/ApplicationCommands/SlashCommandInfo.cs @@ -90,26 +90,26 @@ public override async ValueTask InvokeAsync(TContext context, return SuccessResult.Instance; } - public override async ValueTask GetRawValueAsync() + public override async ValueTask GetRawValueAsync(CancellationToken cancellationToken = default) { var parameters = Parameters; var count = parameters.Count; var options = new ApplicationCommandOptionProperties[count]; for (int i = 0; i < count; i++) - options[i] = await parameters[i].GetRawValueAsync().ConfigureAwait(false); + options[i] = await parameters[i].GetRawValueAsync(cancellationToken).ConfigureAwait(false); #pragma warning disable CS0618 // Type or member is obsolete return new SlashCommandProperties(Name, Description) { - NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance)).ConfigureAwait(false), + NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), DefaultGuildUserPermissions = DefaultGuildUserPermissions, DMPermission = DMPermission, DefaultPermission = DefaultPermission, IntegrationTypes = IntegrationTypes, Contexts = Contexts, Nsfw = Nsfw, - DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance)).ConfigureAwait(false), + DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), Options = options, }; #pragma warning restore CS0618 // Type or member is obsolete diff --git a/NetCord.Services/ApplicationCommands/SlashCommandParameter.cs b/NetCord.Services/ApplicationCommands/SlashCommandParameter.cs index 925cb5e9..a50b62b3 100644 --- a/NetCord.Services/ApplicationCommands/SlashCommandParameter.cs +++ b/NetCord.Services/ApplicationCommands/SlashCommandParameter.cs @@ -90,12 +90,12 @@ internal SlashCommandParameter(ParameterInfo parameter, MethodInfo method, Appli Preconditions = PreconditionsHelper.GetParameterPreconditions(attributesIEnumerable, method); } - public async ValueTask GetRawValueAsync() + public async ValueTask GetRawValueAsync(CancellationToken cancellationToken = default) { return new(TypeReader.Type, Name, Description) { - NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance)).ConfigureAwait(false), - DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance)).ConfigureAwait(false), + NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), + DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), MaxValue = MaxValue, MinValue = MinValue, MaxLength = MaxLength, diff --git a/NetCord.Services/ApplicationCommands/SubSlashCommandGroupInfo.cs b/NetCord.Services/ApplicationCommands/SubSlashCommandGroupInfo.cs index 8f86fdd5..2bfb2179 100644 --- a/NetCord.Services/ApplicationCommands/SubSlashCommandGroupInfo.cs +++ b/NetCord.Services/ApplicationCommands/SubSlashCommandGroupInfo.cs @@ -56,7 +56,7 @@ public async ValueTask InvokeAsync(TContext context, IReadOnly return await subCommand.InvokeAsync(context, option.Options!, configuration, serviceProvider).ConfigureAwait(false); } - public async ValueTask GetRawValueAsync() + public async ValueTask GetRawValueAsync(CancellationToken cancellationToken = default) { var subCommands = SubCommands; var count = subCommands.Count; @@ -64,12 +64,12 @@ public async ValueTask GetRawValueAsync() var options = new ApplicationCommandOptionProperties[count]; int i = 0; foreach (var subCommand in subCommands.Values) - options[i++] = await subCommand.GetRawValueAsync().ConfigureAwait(false); + options[i++] = await subCommand.GetRawValueAsync(cancellationToken).ConfigureAwait(false); return new(ApplicationCommandOptionType.SubCommandGroup, Name, Description) { - NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance)).ConfigureAwait(false), - DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance)).ConfigureAwait(false), + NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), + DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(DescriptionLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), Options = options, }; } diff --git a/NetCord.Services/ApplicationCommands/SubSlashCommandInfo.cs b/NetCord.Services/ApplicationCommands/SubSlashCommandInfo.cs index e724c42a..5d87cb6a 100644 --- a/NetCord.Services/ApplicationCommands/SubSlashCommandInfo.cs +++ b/NetCord.Services/ApplicationCommands/SubSlashCommandInfo.cs @@ -60,19 +60,19 @@ public async ValueTask InvokeAsync(TContext context, IReadOnly return SuccessResult.Instance; } - public async ValueTask GetRawValueAsync() + public async ValueTask GetRawValueAsync(CancellationToken cancellationToken = default) { var parameters = Parameters; var count = parameters.Count; var options = new ApplicationCommandOptionProperties[count]; for (int i = 0; i < count; i++) - options[i] = await parameters[i].GetRawValueAsync().ConfigureAwait(false); + options[i] = await parameters[i].GetRawValueAsync(cancellationToken).ConfigureAwait(false); return new(ApplicationCommandOptionType.SubCommand, Name, Description) { - NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationsPath.Add(NameLocalizationPathSegment.Instance)).ConfigureAwait(false), - DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationsPath.Add(DescriptionLocalizationPathSegment.Instance)).ConfigureAwait(false), + NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationsPath.Add(NameLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), + DescriptionLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationsPath.Add(DescriptionLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), Options = options, }; } diff --git a/NetCord.Services/ApplicationCommands/UserCommandInfo.cs b/NetCord.Services/ApplicationCommands/UserCommandInfo.cs index 5a353a94..71ce3192 100644 --- a/NetCord.Services/ApplicationCommands/UserCommandInfo.cs +++ b/NetCord.Services/ApplicationCommands/UserCommandInfo.cs @@ -71,12 +71,12 @@ public override async ValueTask InvokeAsync(TContext context, return SuccessResult.Instance; } - public override async ValueTask GetRawValueAsync() + public override async ValueTask GetRawValueAsync(CancellationToken cancellationToken = default) { #pragma warning disable CS0618 // Type or member is obsolete return new UserCommandProperties(Name) { - NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance)).ConfigureAwait(false), + NameLocalizations = LocalizationsProvider is null ? null : await LocalizationsProvider.GetLocalizationsAsync(LocalizationPath.Add(NameLocalizationPathSegment.Instance), cancellationToken).ConfigureAwait(false), DefaultGuildUserPermissions = DefaultGuildUserPermissions, DMPermission = DMPermission, DefaultPermission = DefaultPermission, diff --git a/NetCord.Services/Commands/CommandModule.cs b/NetCord.Services/Commands/CommandModule.cs index 05eacaa1..c32b4749 100644 --- a/NetCord.Services/Commands/CommandModule.cs +++ b/NetCord.Services/Commands/CommandModule.cs @@ -4,9 +4,9 @@ namespace NetCord.Services.Commands; public abstract class CommandModule : BaseCommandModule where TContext : ICommandContext, IGatewayClientContext { - public Task ReplyAsync(ReplyMessageProperties replyMessage, RestRequestProperties? properties = null) - => Context.Message.ReplyAsync(replyMessage, properties); + public Task ReplyAsync(ReplyMessageProperties replyMessage, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => Context.Message.ReplyAsync(replyMessage, properties, cancellationToken); - public Task SendAsync(MessageProperties message, RestRequestProperties? properties = null) - => Context.Message.SendAsync(message, properties); + public Task SendAsync(MessageProperties message, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => Context.Message.SendAsync(message, properties, cancellationToken); } diff --git a/NetCord.Services/Commands/CommandService.cs b/NetCord.Services/Commands/CommandService.cs index f09d9844..9f39cb8b 100644 --- a/NetCord.Services/Commands/CommandService.cs +++ b/NetCord.Services/Commands/CommandService.cs @@ -108,26 +108,26 @@ private async ValueTask ExecuteAsyncCore(int prefixLength, TCo var index = fullCommand.Span.IndexOfAny(separators); SortedList>? commandInfos; ReadOnlyMemory baseArguments; - if (index == -1) + if (index >= 0) { - var command = fullCommand; + var command = fullCommand[..index]; if (!TryGetCommandInfos(command, out commandInfos)) return new NotFoundResult("Command not found."); - baseArguments = default; + baseArguments = fullCommand[(index + 1)..].TrimStart(separators); } else { - var command = fullCommand[..index]; + var command = fullCommand; if (!TryGetCommandInfos(command, out commandInfos)) return new NotFoundResult("Command not found."); - baseArguments = fullCommand[(index + 1)..]; + baseArguments = default; } - var configuration = _configuration; + var configuration = _configuration; var maxIndex = commandInfos.Count - 1; for (var i = 0; i <= maxIndex; i++) diff --git a/NetCord.Services/ComponentInteractions/ComponentInteractionModule.cs b/NetCord.Services/ComponentInteractions/ComponentInteractionModule.cs index 4387b32a..e9ade4a9 100644 --- a/NetCord.Services/ComponentInteractions/ComponentInteractionModule.cs +++ b/NetCord.Services/ComponentInteractions/ComponentInteractionModule.cs @@ -4,19 +4,19 @@ namespace NetCord.Services.ComponentInteractions; public class ComponentInteractionModule : BaseComponentInteractionModule where TContext : IComponentInteractionContext { - public Task RespondAsync(InteractionCallback callback, RestRequestProperties? properties = null) => Context.Interaction.SendResponseAsync(callback, properties); + public Task RespondAsync(InteractionCallback callback, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.SendResponseAsync(callback, properties, cancellationToken); - public Task GetResponseAsync(RestRequestProperties? properties = null) => Context.Interaction.GetResponseAsync(properties); + public Task GetResponseAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.GetResponseAsync(properties, cancellationToken); - public Task ModifyResponseAsync(Action action, RestRequestProperties? properties = null) => Context.Interaction.ModifyResponseAsync(action, properties); + public Task ModifyResponseAsync(Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.ModifyResponseAsync(action, properties, cancellationToken); - public Task DeleteResponseAsync(RestRequestProperties? properties = null) => Context.Interaction.DeleteResponseAsync(properties); + public Task DeleteResponseAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.DeleteResponseAsync(properties, cancellationToken); - public Task FollowupAsync(InteractionMessageProperties message, RestRequestProperties? properties = null) => Context.Interaction.SendFollowupMessageAsync(message, properties); + public Task FollowupAsync(InteractionMessageProperties message, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.SendFollowupMessageAsync(message, properties, cancellationToken); - public Task GetFollowupAsync(ulong messageId, RestRequestProperties? properties = null) => Context.Interaction.GetFollowupMessageAsync(messageId, properties); + public Task GetFollowupAsync(ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.GetFollowupMessageAsync(messageId, properties, cancellationToken); - public Task ModifyFollowupAsync(ulong messageId, Action action, RestRequestProperties? properties = null) => Context.Interaction.ModifyFollowupMessageAsync(messageId, action, properties); + public Task ModifyFollowupAsync(ulong messageId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.ModifyFollowupMessageAsync(messageId, action, properties, cancellationToken); - public Task DeleteFollowupAsync(ulong messageId, RestRequestProperties? properties = null) => Context.Interaction.DeleteFollowupMessageAsync(messageId, properties); + public Task DeleteFollowupAsync(ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => Context.Interaction.DeleteFollowupMessageAsync(messageId, properties, cancellationToken); } diff --git a/NetCord.Services/Helpers/ServiceProviderHelper.cs b/NetCord.Services/Helpers/ServiceProviderHelper.cs index 78d6d884..d25b3c67 100644 --- a/NetCord.Services/Helpers/ServiceProviderHelper.cs +++ b/NetCord.Services/Helpers/ServiceProviderHelper.cs @@ -12,7 +12,7 @@ public static Expression GetGetServiceExpression(ParameterInfo parameter, Parame return parameter.IsOptional ? GetOptionalGetServiceExpression(parameter, serviceProvider) : GetRequiredGetServiceExpression(parameter, serviceProvider, serviceNotFound); } - private static Expression GetOptionalGetServiceExpression(ParameterInfo parameter, ParameterExpression serviceProvider) + private static BlockExpression GetOptionalGetServiceExpression(ParameterInfo parameter, ParameterExpression serviceProvider) { var parameterValueVariable = Expression.Variable(typeof(object)); var parameterType = parameter.ParameterType; @@ -25,7 +25,7 @@ private static Expression GetOptionalGetServiceExpression(ParameterInfo paramete Expression.Label(argRet, ParameterHelper.GetParameterDefaultValueExpression(parameterType, parameter))); } - private static Expression GetRequiredGetServiceExpression(ParameterInfo parameter, ParameterExpression serviceProvider, Expression serviceNotFound) + private static BlockExpression GetRequiredGetServiceExpression(ParameterInfo parameter, ParameterExpression serviceProvider, Expression serviceNotFound) { var parameterValueVariable = Expression.Variable(typeof(object)); var parameterType = parameter.ParameterType; diff --git a/NetCord.Services/NetCord.Services.csproj b/NetCord.Services/NetCord.Services.csproj index 92a4ad8a..7ce53e0e 100644 --- a/NetCord.Services/NetCord.Services.csproj +++ b/NetCord.Services/NetCord.Services.csproj @@ -14,7 +14,7 @@ SmallSquare.png MIT $(VersionPrefix) - alpha.202 + alpha.217 The modern and fully customizable C# Discord library. true diff --git a/NetCord/Application.cs b/NetCord/Application.cs index 20fea1dc..56b26660 100644 --- a/NetCord/Application.cs +++ b/NetCord/Application.cs @@ -63,4 +63,14 @@ public Application(JsonModels.JsonApplication jsonModel, RestClient client) : ba if (integrationTypesConfiguration is not null) IntegrationTypesConfiguration = integrationTypesConfiguration.ToDictionary(i => i.Key, i => new ApplicationIntegrationTypeConfiguration(i.Value)); } + + public ImageUrl? GetIconUrl(ImageFormat format) => IconHash is string hash ? ImageUrl.ApplicationIcon(Id, hash, format) : null; + + public ImageUrl? GetCoverUrl(ImageFormat format) => CoverImageHash is string hash ? ImageUrl.ApplicationCover(Id, hash, format) : null; + + public ImageUrl? GetAssetUrl(ulong assetId, ImageFormat format) => ImageUrl.ApplicationAsset(Id, assetId, format); + + public ImageUrl? GetAchievementIconUrl(ulong achievementId, string iconHash, ImageFormat format) => ImageUrl.AchievementIcon(Id, achievementId, iconHash, format); + + public ImageUrl? GetStorePageAssetUrl(ulong assetId, ImageFormat format) => ImageUrl.StorePageAsset(Id, assetId, format); } diff --git a/NetCord/ApplicationCommandInteraction.cs b/NetCord/ApplicationCommandInteraction.cs index a06a443d..7823465d 100644 --- a/NetCord/ApplicationCommandInteraction.cs +++ b/NetCord/ApplicationCommandInteraction.cs @@ -5,7 +5,7 @@ namespace NetCord; public abstract class ApplicationCommandInteraction : Interaction { - private protected ApplicationCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) + private protected ApplicationCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) { } diff --git a/NetCord/ApplicationEmoji.cs b/NetCord/ApplicationEmoji.cs new file mode 100644 index 00000000..934c882b --- /dev/null +++ b/NetCord/ApplicationEmoji.cs @@ -0,0 +1,9 @@ +using NetCord.JsonModels; +using NetCord.Rest; + +namespace NetCord; + +public partial class ApplicationEmoji(JsonEmoji jsonModel, ulong applicationId, RestClient client) : CustomEmoji(jsonModel, client) +{ + public ulong ApplicationId { get; } = applicationId; +} diff --git a/NetCord/ApplicationFlags.cs b/NetCord/ApplicationFlags.cs index 535dc217..72a78b0b 100644 --- a/NetCord/ApplicationFlags.cs +++ b/NetCord/ApplicationFlags.cs @@ -5,11 +5,36 @@ namespace NetCord; [Flags] public enum ApplicationFlags : uint { + /// + /// Undocumented. + /// + EmbeddedReleased = 1 << 1, + + /// + /// Undocumented. + /// + ManagedEmoji = 1 << 2, + + /// + /// Undocumented. + /// + EmbeddedIAP = 1 << 3, + + /// + /// Undocumented. + /// + GroupDMCreate = 1 << 4, + /// /// Indicates if an app uses the Auto Moderation API. /// ApplicationAutoModerationRuleCreateBadge = 1 << 6, + /// + /// Undocumented. + /// + RPCHasConnected = 1 << 11, + /// /// Intent required for bots in 100 or more servers to receive events. /// @@ -50,6 +75,11 @@ public enum ApplicationFlags : uint /// GatewayMessageContentLimited = 1 << 19, + /// + /// Undocumented. + /// + EmbeddedFirstParty = 1 << 20, + /// /// Indicates if an app has registered global application commands. /// diff --git a/NetCord/Attachment.cs b/NetCord/Attachment.cs index f25208ba..63157fdc 100644 --- a/NetCord/Attachment.cs +++ b/NetCord/Attachment.cs @@ -14,6 +14,11 @@ public class Attachment(JsonModels.JsonAttachment jsonModel) : Entity, IJsonMode /// public string FileName => _jsonModel.FileName; + /// + /// Title of the attachment. + /// + public string? Title => _jsonModel.Title; + /// /// Description for the attachment (max 1024 characters). /// diff --git a/NetCord/AuditLogEntryInfo.cs b/NetCord/AuditLogEntryInfo.cs index 0768b794..2865526a 100644 --- a/NetCord/AuditLogEntryInfo.cs +++ b/NetCord/AuditLogEntryInfo.cs @@ -7,7 +7,7 @@ public class AuditLogEntryInfo(JsonAuditLogEntryInfo jsonModel) : IJsonModel.JsonModel => jsonModel; /// - /// Id of the app whose permissions were targeted. + /// ID of the app whose permissions were targeted. /// public ulong? ApplicationId => jsonModel.ApplicationId; @@ -37,7 +37,7 @@ public class AuditLogEntryInfo(JsonAuditLogEntryInfo jsonModel) : IJsonModel jsonModel.DeleteGuildUserDays; /// - /// Id of the overwritten entity. + /// ID of the overwritten entity. /// public ulong? Id => jsonModel.Id; @@ -47,7 +47,7 @@ public class AuditLogEntryInfo(JsonAuditLogEntryInfo jsonModel) : IJsonModel jsonModel.GuildUsersRemoved; /// - /// Id of the message that was targeted. + /// ID of the message that was targeted. /// public ulong? MessageId => jsonModel.MessageId; diff --git a/NetCord/AuditLogEvent.cs b/NetCord/AuditLogEvent.cs index 195ec977..90ca94dd 100644 --- a/NetCord/AuditLogEvent.cs +++ b/NetCord/AuditLogEvent.cs @@ -281,4 +281,39 @@ public enum AuditLogEvent /// Creator monetization terms were accepted. /// CreatorMonetizationTermsAccepted = 151, + + /// + /// Guild onboarding question was created. + /// + OnboardingPromptCreate = 163, + + /// + /// Guild onboarding question was updated. + /// + OnboardingPromptUpdate = 164, + + /// + /// Guild onboarding question was deleted. + /// + OnboardingPromptDelete = 165, + + /// + /// Guild onboarding was created. + /// + OnboardingCreate = 166, + + /// + /// Guild onboarding was updated. + /// + OnboardingUpdate = 167, + + /// + /// Server guide was created. + /// + HomeSettingsCreate = 190, + + /// + /// Server guide was updated. + /// + HomeSettingsUpdate = 191, } diff --git a/NetCord/AutoModerationActionType.cs b/NetCord/AutoModerationActionType.cs index 0c4552c9..871e06e4 100644 --- a/NetCord/AutoModerationActionType.cs +++ b/NetCord/AutoModerationActionType.cs @@ -5,4 +5,5 @@ public enum AutoModerationActionType BlockMessage = 1, SendAlertMessage = 2, Timeout = 3, + BlockUserInteraction = 4, } diff --git a/NetCord/AutoModerationRuleEventType.cs b/NetCord/AutoModerationRuleEventType.cs index 19def3fa..53bceaf1 100644 --- a/NetCord/AutoModerationRuleEventType.cs +++ b/NetCord/AutoModerationRuleEventType.cs @@ -3,4 +3,5 @@ public enum AutoModerationRuleEventType { MessageSend = 1, + UserUpdate = 2, } diff --git a/NetCord/AutoModerationRuleTriggerType.cs b/NetCord/AutoModerationRuleTriggerType.cs index f493143a..5e7da728 100644 --- a/NetCord/AutoModerationRuleTriggerType.cs +++ b/NetCord/AutoModerationRuleTriggerType.cs @@ -6,4 +6,5 @@ public enum AutoModerationRuleTriggerType Spam = 3, KeywordPreset = 4, MentionSpam = 5, + UserProfile = 6, } diff --git a/NetCord/AutocompleteInteraction.cs b/NetCord/AutocompleteInteraction.cs index 014beba2..2c0f5ffb 100644 --- a/NetCord/AutocompleteInteraction.cs +++ b/NetCord/AutocompleteInteraction.cs @@ -3,7 +3,7 @@ namespace NetCord; -public class AutocompleteInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : Interaction(jsonModel, guild, sendResponseAsync, client) +public class AutocompleteInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : Interaction(jsonModel, guild, sendResponseAsync, client) { public override AutocompleteInteractionData Data { get; } = new(jsonModel.Data!, jsonModel.GuildId, client); } diff --git a/NetCord/AvatarDecorationData.cs b/NetCord/AvatarDecorationData.cs new file mode 100644 index 00000000..8281b0bd --- /dev/null +++ b/NetCord/AvatarDecorationData.cs @@ -0,0 +1,12 @@ +using NetCord.JsonModels; + +namespace NetCord; + +public class AvatarDecorationData(JsonAvatarDecorationData jsonModel) : IJsonModel +{ + JsonAvatarDecorationData IJsonModel.JsonModel => jsonModel; + + public string Hash => jsonModel.Hash; + + public ulong SkuId => jsonModel.SkuId; +} diff --git a/NetCord/ButtonInteraction.cs b/NetCord/ButtonInteraction.cs index 0408a4c3..997b8631 100644 --- a/NetCord/ButtonInteraction.cs +++ b/NetCord/ButtonInteraction.cs @@ -3,7 +3,7 @@ namespace NetCord; -public class ButtonInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : MessageComponentInteraction(jsonModel, guild, sendResponseAsync, client) +public class ButtonInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : MessageComponentInteraction(jsonModel, guild, sendResponseAsync, client) { public override ButtonInteractionData Data { get; } = new(jsonModel.Data!); } diff --git a/NetCord/ChannelMenuInteraction.cs b/NetCord/ChannelMenuInteraction.cs index eac852b5..fe565578 100644 --- a/NetCord/ChannelMenuInteraction.cs +++ b/NetCord/ChannelMenuInteraction.cs @@ -3,6 +3,6 @@ namespace NetCord; -public class ChannelMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) +public class ChannelMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) { } diff --git a/NetCord/CodeBlock.cs b/NetCord/CodeBlock.cs index ada3f51c..f6b12de1 100644 --- a/NetCord/CodeBlock.cs +++ b/NetCord/CodeBlock.cs @@ -69,7 +69,7 @@ public static bool TryParse(ReadOnlySpan s, bool strictMode, [MaybeNullWhe foreach (var c in formatterSpan) { - if (char.IsAsciiLetterOrDigit(c) || c == '+' || c == '-') + if (char.IsAsciiLetterOrDigit(c) || c is '+' or '-' or '#' or '_') continue; goto Success; diff --git a/NetCord/ComponentInteraction.cs b/NetCord/ComponentInteraction.cs index a316ed4b..2c1122bf 100644 --- a/NetCord/ComponentInteraction.cs +++ b/NetCord/ComponentInteraction.cs @@ -6,7 +6,7 @@ namespace NetCord; public abstract class ComponentInteraction : Interaction { - private protected ComponentInteraction(JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) + private protected ComponentInteraction(JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) { } diff --git a/NetCord/Components/Button.cs b/NetCord/Components/Button.cs index a0785b62..98409dda 100644 --- a/NetCord/Components/Button.cs +++ b/NetCord/Components/Button.cs @@ -1,6 +1,6 @@ namespace NetCord; -public class Button : IButton, IJsonModel +public class Button : ICustomizableButton, IJsonModel { JsonModels.JsonComponent IJsonModel.JsonModel => _jsonModel; private readonly JsonModels.JsonComponent _jsonModel; diff --git a/NetCord/Components/IButton.cs b/NetCord/Components/IButton.cs index ce12a6e2..dc4852ed 100644 --- a/NetCord/Components/IButton.cs +++ b/NetCord/Components/IButton.cs @@ -2,8 +2,6 @@ public interface IButton { - public string? Label { get; } - public EmojiReference? Emoji { get; } public bool Disabled { get; } public static IButton CreateFromJson(JsonModels.JsonComponent jsonModel) @@ -11,6 +9,7 @@ public static IButton CreateFromJson(JsonModels.JsonComponent jsonModel) return jsonModel.Style.GetValueOrDefault() switch { (ButtonStyle)5 => new LinkButton(jsonModel), + (ButtonStyle)6 => new PremiumButton(jsonModel), _ => new Button(jsonModel), }; } diff --git a/NetCord/Components/ICustomizableButton.cs b/NetCord/Components/ICustomizableButton.cs new file mode 100644 index 00000000..0bf263d9 --- /dev/null +++ b/NetCord/Components/ICustomizableButton.cs @@ -0,0 +1,7 @@ +namespace NetCord; + +public interface ICustomizableButton : IButton +{ + public string? Label { get; } + public EmojiReference? Emoji { get; } +} diff --git a/NetCord/Components/IMessageComponent.cs b/NetCord/Components/IMessageComponent.cs index 79087d00..6a258e4c 100644 --- a/NetCord/Components/IMessageComponent.cs +++ b/NetCord/Components/IMessageComponent.cs @@ -4,7 +4,19 @@ public interface IMessageComponent : IComponent { public static IMessageComponent CreateFromJson(JsonModels.JsonComponent jsonModel) { - var firstComponent = jsonModel.Components[0]; - return firstComponent.Type is ComponentType.Button ? new ActionRow(jsonModel) : Menu.CreateFromJson(firstComponent); + return jsonModel.Type switch + { + ComponentType.ActionRow => jsonModel.Components[0].Type switch + { + ComponentType.Button => new ActionRow(jsonModel), + ComponentType.StringMenu => new StringMenu(jsonModel), + ComponentType.UserMenu => new UserMenu(jsonModel), + ComponentType.ChannelMenu => new ChannelMenu(jsonModel), + ComponentType.RoleMenu => new RoleMenu(jsonModel), + ComponentType.MentionableMenu => new MentionableMenu(jsonModel), + _ => new UnknownMessageComponent(jsonModel), + }, + _ => new UnknownMessageComponent(jsonModel), + }; } } diff --git a/NetCord/Components/IUnknownMessageComponent.cs b/NetCord/Components/IUnknownMessageComponent.cs new file mode 100644 index 00000000..00c5c6a5 --- /dev/null +++ b/NetCord/Components/IUnknownMessageComponent.cs @@ -0,0 +1,6 @@ +namespace NetCord; + +public interface IUnknownMessageComponent : IMessageComponent +{ + public ComponentType Type { get; } +} diff --git a/NetCord/Components/LinkButton.cs b/NetCord/Components/LinkButton.cs index 7739e6e7..3825c7ed 100644 --- a/NetCord/Components/LinkButton.cs +++ b/NetCord/Components/LinkButton.cs @@ -2,7 +2,7 @@ namespace NetCord; -public class LinkButton : IButton, IJsonModel +public class LinkButton : ICustomizableButton, IJsonModel { JsonComponent IJsonModel.JsonModel => _jsonModel; private readonly JsonComponent _jsonModel; diff --git a/NetCord/Components/Menu.cs b/NetCord/Components/Menu.cs index 7c7c900e..60066e8a 100644 --- a/NetCord/Components/Menu.cs +++ b/NetCord/Components/Menu.cs @@ -12,17 +12,4 @@ public abstract class Menu(JsonComponent jsonModel) : IMessageComponent, IJsonMo public int? MinValues => _jsonModel.MinValues; public int? MaxValues => _jsonModel.MaxValues; public bool Disabled => _jsonModel.Disabled.GetValueOrDefault(); - - public static Menu CreateFromJson(JsonComponent jsonModel) - { - return jsonModel.Type switch - { - ComponentType.StringMenu => new StringMenu(jsonModel), - ComponentType.UserMenu => new UserMenu(jsonModel), - ComponentType.ChannelMenu => new ChannelMenu(jsonModel), - ComponentType.RoleMenu => new RoleMenu(jsonModel), - ComponentType.MentionableMenu => new MentionableMenu(jsonModel), - _ => throw new NotImplementedException(), - }; - } } diff --git a/NetCord/Components/PremiumButton.cs b/NetCord/Components/PremiumButton.cs new file mode 100644 index 00000000..a89391be --- /dev/null +++ b/NetCord/Components/PremiumButton.cs @@ -0,0 +1,10 @@ +using NetCord.JsonModels; + +namespace NetCord; +public class PremiumButton(JsonComponent jsonModel) : IButton, IJsonModel +{ + JsonComponent IJsonModel.JsonModel => jsonModel; + + public ulong SkuId => jsonModel.SkuId.GetValueOrDefault(); + public bool Disabled => jsonModel.Disabled.GetValueOrDefault(); +} diff --git a/NetCord/Components/UnknownMessageComponent.cs b/NetCord/Components/UnknownMessageComponent.cs new file mode 100644 index 00000000..1b280e6c --- /dev/null +++ b/NetCord/Components/UnknownMessageComponent.cs @@ -0,0 +1,10 @@ +using NetCord.JsonModels; + +namespace NetCord; + +internal class UnknownMessageComponent(JsonComponent jsonModel) : IMessageComponent, IJsonModel +{ + JsonComponent IJsonModel.JsonModel => jsonModel; + + public ComponentType Type => jsonModel.Type; +} diff --git a/NetCord/CustomEmoji.cs b/NetCord/CustomEmoji.cs new file mode 100644 index 00000000..83b2ddc7 --- /dev/null +++ b/NetCord/CustomEmoji.cs @@ -0,0 +1,69 @@ +using NetCord.JsonModels; +using NetCord.Rest; + +namespace NetCord; + +public abstract class CustomEmoji : Emoji, ISpanFormattable +{ + private protected RestClient _client; + + public CustomEmoji(JsonEmoji jsonModel, RestClient client) : base(jsonModel) + { + _client = client; + + var creator = jsonModel.Creator; + if (creator is not null) + Creator = new(creator, client); + } + + public ulong Id => _jsonModel.Id.GetValueOrDefault(); + + public User? Creator { get; } + + public bool? RequireColons => _jsonModel.RequireColons; + + public bool? Managed => _jsonModel.Managed; + + public bool? Available => _jsonModel.Available; + + public ImageUrl GetImageUrl(ImageFormat format) => ImageUrl.CustomEmoji(Id, format); + + public override string ToString() => Animated ? $"" : $"<:{Name}:{Id}>"; + + public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); + + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) + { + var name = Name; + if (Animated) + { + if (destination.Length < 6 + name.Length || !Id.TryFormat(destination[(4 + name.Length)..^1], out int length)) + { + charsWritten = 0; + return false; + } + + "))] +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum EmbedType { [JsonPropertyName("rich")] diff --git a/NetCord/Entitlement.cs b/NetCord/Entitlement.cs index 858d98e6..46fe2ab4 100644 --- a/NetCord/Entitlement.cs +++ b/NetCord/Entitlement.cs @@ -7,17 +7,17 @@ public class Entitlement(JsonModels.JsonEntitlement jsonModel) : Entity, IJsonMo public override ulong Id => jsonModel.Id; /// - /// Id of the SKU. + /// ID of the SKU. /// public ulong SkuId => jsonModel.SkuId; /// - /// Id of the parent application. + /// ID of the parent application. /// public ulong ApplicationId => jsonModel.ApplicationId; /// - /// Id of the user that is granted access to the entitlement's SKU. + /// ID of the user that is granted access to the entitlement's SKU. /// public ulong? UserId => jsonModel.UserId; @@ -42,7 +42,7 @@ public class Entitlement(JsonModels.JsonEntitlement jsonModel) : Entity, IJsonMo public DateTimeOffset? EndsAt => jsonModel.EndsAt; /// - /// Id of the guild that is granted access to the entitlement's SKU. + /// ID of the guild that is granted access to the entitlement's SKU. /// public ulong? GuildId => jsonModel.GuildId; diff --git a/NetCord/EntityMenuInteraction.cs b/NetCord/EntityMenuInteraction.cs index 43f75b33..dbeeea72 100644 --- a/NetCord/EntityMenuInteraction.cs +++ b/NetCord/EntityMenuInteraction.cs @@ -5,7 +5,7 @@ namespace NetCord; public abstract class EntityMenuInteraction : MessageComponentInteraction { - private protected EntityMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) + private protected EntityMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) { Data = new(jsonModel.Data!, jsonModel.GuildId, client); } diff --git a/NetCord/Gateway/AuditLogEntry.cs b/NetCord/Gateway/AuditLogEntry.cs index 115bedf8..549c96be 100644 --- a/NetCord/Gateway/AuditLogEntry.cs +++ b/NetCord/Gateway/AuditLogEntry.cs @@ -12,20 +12,23 @@ public class AuditLogEntry : Entity, IJsonModel JsonAuditLogEntry IJsonModel.JsonModel => _jsonModel; private protected readonly JsonAuditLogEntry _jsonModel; - public AuditLogEntry(JsonAuditLogEntry jsonModel) + public AuditLogEntry(JsonAuditLogEntry jsonModel, ulong guildId) { _jsonModel = jsonModel; + Changes = _jsonModel.Changes.ToDictionaryOrEmpty(c => c.Key, c => new AuditLogChange(c)); var options = _jsonModel.Options; if (options is not null) Options = new(options); + + GuildId = guildId; } public override ulong Id => _jsonModel.Id; /// - /// Id of the affected entity. + /// ID of the affected entity. /// public ulong? TargetId => _jsonModel.TargetId; @@ -35,7 +38,7 @@ public AuditLogEntry(JsonAuditLogEntry jsonModel) public IReadOnlyDictionary Changes { get; } /// - /// Id of user that made the changes. + /// ID of user that made the changes. /// public ulong? UserId => _jsonModel.UserId; @@ -54,6 +57,11 @@ public AuditLogEntry(JsonAuditLogEntry jsonModel) /// public string? Reason => _jsonModel.Reason; + /// + /// The ID of the guild this audit log entry belongs to. + /// + public ulong GuildId { get; } + private bool TryGetChangeModel(Expression> expression, [NotNullWhen(true)] out JsonAuditLogChange model) { var member = GetMemberAccess(expression); diff --git a/NetCord/Gateway/CancellationTokenProvider.cs b/NetCord/Gateway/CancellationTokenProvider.cs index 36e19444..21d7634a 100644 --- a/NetCord/Gateway/CancellationTokenProvider.cs +++ b/NetCord/Gateway/CancellationTokenProvider.cs @@ -4,6 +4,8 @@ internal sealed class CancellationTokenProvider : IDisposable { private readonly CancellationTokenSource _source; + public bool IsCancellationRequested => _source.IsCancellationRequested; + public CancellationToken Token { get; } public CancellationTokenProvider() diff --git a/NetCord/Gateway/EventArgs/GuildInviteDeleteEventArgs.cs b/NetCord/Gateway/EventArgs/GuildInviteDeleteEventArgs.cs deleted file mode 100644 index 630019e6..00000000 --- a/NetCord/Gateway/EventArgs/GuildInviteDeleteEventArgs.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NetCord.Gateway; - -public class GuildInviteDeleteEventArgs(JsonModels.EventArgs.JsonGuildInviteDeleteEventArgs jsonModel) : IJsonModel -{ - JsonModels.EventArgs.JsonGuildInviteDeleteEventArgs IJsonModel.JsonModel => jsonModel; - - public ulong InviteChannelId => jsonModel.InviteChannelId; - - public ulong? GuildId => jsonModel.GuildId; - - public string InviteCode => jsonModel.InviteCode; -} diff --git a/NetCord/Gateway/EventArgs/GuildThreadUsersUpdateEventArgs.cs b/NetCord/Gateway/EventArgs/GuildThreadUsersUpdateEventArgs.cs index 46334ea9..3b1f082e 100644 --- a/NetCord/Gateway/EventArgs/GuildThreadUsersUpdateEventArgs.cs +++ b/NetCord/Gateway/EventArgs/GuildThreadUsersUpdateEventArgs.cs @@ -13,7 +13,7 @@ public GuildThreadUsersUpdateEventArgs(JsonModels.EventArgs.JsonGuildThreadUsers var addedUsers = jsonModel.AddedUsers; if (addedUsers is not null) - AddedUsers = addedUsers.ToDictionary(u => u.UserId, u => new AddedThreadUser(u, GuildId, client)); + AddedUsers = addedUsers.Select(u => new AddedThreadUser(u, GuildId, client)).ToArray(); } public ulong ThreadId => _jsonModel.ThreadId; @@ -22,7 +22,7 @@ public GuildThreadUsersUpdateEventArgs(JsonModels.EventArgs.JsonGuildThreadUsers public int UserCount => _jsonModel.UserCount; - public IReadOnlyDictionary? AddedUsers { get; } + public IReadOnlyList? AddedUsers { get; } public IReadOnlyList RemovedUserIds => _jsonModel.RemovedUserIds; } diff --git a/NetCord/Gateway/EventArgs/GuildUserChunkEventArgs.cs b/NetCord/Gateway/EventArgs/GuildUserChunkEventArgs.cs index d3b5c08a..a63c632d 100644 --- a/NetCord/Gateway/EventArgs/GuildUserChunkEventArgs.cs +++ b/NetCord/Gateway/EventArgs/GuildUserChunkEventArgs.cs @@ -10,16 +10,16 @@ public class GuildUserChunkEventArgs : IJsonModel u.User.Id, u => new GuildUser(u, jsonModel.GuildId, client)); + Users = jsonModel.Users.Select(u => new GuildUser(u, jsonModel.GuildId, client)).ToArray(); var presences = jsonModel.Presences; if (presences is not null) - Presences = presences.ToDictionary(p => p.User.Id, p => new Presence(p, jsonModel.GuildId, client)); + Presences = presences.Select(p => new Presence(p, jsonModel.GuildId, client)).ToArray(); } public ulong GuildId => _jsonModel.GuildId; - public IReadOnlyDictionary Users { get; } + public IReadOnlyList Users { get; } public int ChunkIndex => _jsonModel.ChunkIndex; @@ -27,7 +27,7 @@ public GuildUserChunkEventArgs(JsonModels.EventArgs.JsonGuildUserChunkEventArgs public IReadOnlyList? NotFound => _jsonModel.NotFound; - public IReadOnlyDictionary? Presences { get; } + public IReadOnlyList? Presences { get; } public string? Nonce => _jsonModel.Nonce; } diff --git a/NetCord/Gateway/EventArgs/InviteDeleteEventArgs.cs b/NetCord/Gateway/EventArgs/InviteDeleteEventArgs.cs new file mode 100644 index 00000000..82e7b3e9 --- /dev/null +++ b/NetCord/Gateway/EventArgs/InviteDeleteEventArgs.cs @@ -0,0 +1,12 @@ +namespace NetCord.Gateway; + +public class InviteDeleteEventArgs(JsonModels.EventArgs.JsonInviteDeleteEventArgs jsonModel) : IJsonModel +{ + JsonModels.EventArgs.JsonInviteDeleteEventArgs IJsonModel.JsonModel => jsonModel; + + public ulong InviteChannelId => jsonModel.InviteChannelId; + + public ulong? GuildId => jsonModel.GuildId; + + public string InviteCode => jsonModel.InviteCode; +} diff --git a/NetCord/Gateway/EventArgs/MessageReactionAddEventArgs.cs b/NetCord/Gateway/EventArgs/MessageReactionAddEventArgs.cs index e3b2522a..74793716 100644 --- a/NetCord/Gateway/EventArgs/MessageReactionAddEventArgs.cs +++ b/NetCord/Gateway/EventArgs/MessageReactionAddEventArgs.cs @@ -31,4 +31,10 @@ public MessageReactionAddEventArgs(JsonModels.EventArgs.JsonMessageReactionAddEv public MessageReactionEmoji Emoji { get; } public ulong? MessageAuthorId => _jsonModel.MessageAuthorId; + + public bool Burst => _jsonModel.Burst; + + public IReadOnlyList BurstColors => _jsonModel.BurstColors; + + public ReactionType Type => _jsonModel.Type; } diff --git a/NetCord/Gateway/EventArgs/MessageReactionRemoveEventArgs.cs b/NetCord/Gateway/EventArgs/MessageReactionRemoveEventArgs.cs index e887f8cd..689c958f 100644 --- a/NetCord/Gateway/EventArgs/MessageReactionRemoveEventArgs.cs +++ b/NetCord/Gateway/EventArgs/MessageReactionRemoveEventArgs.cs @@ -13,4 +13,8 @@ public class MessageReactionRemoveEventArgs(JsonModels.EventArgs.JsonMessageReac public ulong? GuildId => jsonModel.GuildId; public MessageReactionEmoji Emoji { get; } = new(jsonModel.Emoji); + + public bool Burst => jsonModel.Burst; + + public ReactionType Type => jsonModel.Type; } diff --git a/NetCord/Gateway/GatewayClient.cs b/NetCord/Gateway/GatewayClient.cs index 111b1c22..63b6f5dc 100644 --- a/NetCord/Gateway/GatewayClient.cs +++ b/NetCord/Gateway/GatewayClient.cs @@ -9,7 +9,7 @@ namespace NetCord.Gateway; /// -/// The WebSocket-based client allows you to receive events from the Discord Gateway and update or request resources, such as updating voice state. +/// The class allows applications to send and receive data from the Discord Gateway, such as events and resource requests. /// public partial class GatewayClient : WebSocketClient, IEntity { @@ -23,572 +23,684 @@ public partial class GatewayClient : WebSocketClient, IEntity /// /// The ready event is dispatched when a client has completed the initial handshake with the Gateway (for new sessions). - /// The ready event contains all the state required for a client to begin interacting with the rest of the platform.
- ///
Required Intents: None - ///
Optional Intents: None + /// The ready event contains all the state required for a client to begin interacting with the rest of the platform. ///
+ /// + /// Required Intents: None
+ /// Optional Intents: None + ///
public event Func? Ready; /// /// Sent when an application command's permissions are updated. - /// The inner payload is an object.
- ///
Required Intents: None - ///
Optional Intents: None + /// The inner payload is an object. ///
+ /// + /// Required Intents: None
+ /// Optional Intents: None + ///
public event Func? ApplicationCommandPermissionsUpdate; /// /// Sent when a rule is created. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? AutoModerationRuleCreate; /// /// Sent when a rule is updated. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? AutoModerationRuleUpdate; /// /// Sent when a rule is deleted. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? AutoModerationRuleDelete; /// /// Sent when a rule is triggered and an action is executed (e.g. when a message is blocked).
+ ///
+ /// ///
Required Intents: ///
Optional Intents: /// - /// - /// - /// - /// - /// - /// For receiving and . - /// - /// + /// + /// + /// + /// + /// + /// For receiving and . + /// + /// /// - /// + ///
public event Func? AutoModerationActionExecution; /// /// Sent when a new guild channel is created, relevant to the bot. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildChannelCreate; /// /// Sent when a channel is updated. This is not sent with new messages, those are tracked by and . This event may reference roles or guild members that no longer exist in the guild. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildChannelUpdate; /// /// Sent when a channel relevant to the bot is deleted. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildChannelDelete; /// /// Sent when a thread is created, relevant to the bot, or when the current user is added to a thread. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildThreadCreate; /// /// Sent when a thread is updated. This is not sent with new messages, those are tracked by . /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildThreadUpdate; /// /// Sent when a thread relevant to the bot is deleted. /// The inner payload is a subset of an object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildThreadDelete; /// /// Sent when the current user gains access to a channel.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildThreadListSync; /// /// Sent when the object for the bot is updated. This event is largely just a signal that you are a member of the thread. /// The inner payload is a object with a set .
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildThreadUserUpdate; /// /// Sent when anyone is added to or removed from a thread.
+ ///
+ /// ///
Required Intents: , * ///
Optional Intents: /// - /// - /// - /// - /// - /// - /// For receiving this event when other users are added / removed, otherwise this event will only fire for the bot's user. - /// - /// + /// + /// + /// + /// + /// + /// For receiving this event when other users are added / removed, otherwise this event will only fire for the bot's user. + /// + /// /// ///

/// *Must also be enabled in the developer portal. - /// + ///
public event Func? GuildThreadUsersUpdate; /// /// Sent when a message is pinned or unpinned in a text channel. This is not sent when a pinned message is deleted.
+ ///
+ /// ///
Required Intents: , ///
Optional Intents: None - /// + ///
public event Func? ChannelPinsUpdate; /// /// Sent when an entitlement is created. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: None ///
Optional Intents: None - /// + ///
public event Func? EntitlementCreate; /// /// Sent when an entitlement is updated. When an entitlement for a subscription is renewed, the field may have an updated value with the new expiration date. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: None ///
Optional Intents: None - /// + ///
public event Func? EntitlementUpdate; /// /// Sent when an entitlement is deleted. Entitlements are not deleted when they expire. /// The inner payload is an object.
+ ///
+ /// ///
Required Intents: None ///
Optional Intents: None - /// + ///
public event Func? EntitlementDelete; /// /// This event can be sent in three different scenarios (During an outage, the object in scenarios 1 and 3 may be marked as unavailable):
/// - /// - /// - /// To lazily load and backfill information for all unavailable guilds sent in the event. Guilds unavailable due to an outage will send a event. - /// - /// - /// - /// - /// When a guild becomes available again to the client. - /// - /// - /// - /// - /// When the current user joins a new guild. - /// - /// + /// + /// To lazily load and backfill information for all unavailable guilds sent in the event. Guilds unavailable due to an outage will send a event. + /// + /// + /// When a guild becomes available again to the client. + /// + /// + /// When the current user joins a new guild. + /// /// + ///
+ /// /// The inner payload can be a object with extra fields, or an unavailable object. If the guild has over 75k users, users and presences returned in this event will only contain your bot and users in voice channels.
///
Required Intents: ///
Optional Intents: /// - /// - /// - /// - /// - /// - /// For receiving users and presences other than the bot's user and users in voice channels (Same as the 75k limit). - /// - /// + /// + /// + /// + /// + /// + /// For receiving users and presences other than the bot's user and users in voice channels (Same as the 75k limit). + /// + /// /// - /// + ///
public event Func? GuildCreate; /// /// Sent when a guild is updated. /// The inner payload is a object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildUpdate; /// /// Sent when a guild becomes or was already unavailable due to an outage, or when the bot leaves / is removed from a guild. + /// + /// /// The inner payload is an unavailable guild object. If the field is not true, the bot was removed from the guild.
///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildDelete; /// /// Sent when a guild audit log entry is created. /// The inner payload is an object. This event is only sent to bots with the permission.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildAuditLogEntryCreate; /// /// Sent when a user is banned from a guild.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildBanAdd; /// /// Sent when a user is unbanned from a guild.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildBanRemove; /// /// Sent when a guild's emojis have been updated.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildEmojisUpdate; /// /// Sent when a guild's stickers have been updated.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildStickersUpdate; /// /// Sent when guild integrations are updated.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildIntegrationsUpdate; /// /// Sent when a new user joins a guild. /// The inner payload is a object with an extra guild_id key.
+ ///
+ /// ///
Required Intents: * ///
Optional Intents: None ///

/// *Must also be enabled in the developer portal. - /// + ///
public event Func? GuildUserAdd; /// /// Sent when a user is removed from a guild (leave/kick/ban).
+ ///
+ /// ///
Required Intents: * ///
Optional Intents: None ///

/// *Must also be enabled in the developer portal. - /// + ///
public event Func? GuildUserRemove; /// /// Sent when a guild user is updated. This will also fire when the object of a guild user changes.
+ ///
+ /// ///
Required Intents: * ///
Optional Intents: None ///

/// *Must also be enabled in the developer portal. - /// + ///
public event Func? GuildUserUpdate; /// - /// Sent in response to . You can use the and to calculate how many chunks are left for your request.
+ /// Sent in response to . You can use the and to calculate how many chunks are left for your request.
+ ///
+ /// ///
Required Intents: None ///
Optional Intents: None - /// + ///
public event Func? GuildUserChunk; /// /// Sent when a guild role is created.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? RoleCreate; /// /// Sent when a guild role is updated.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? RoleUpdate; /// /// Sent when a guild role is deleted.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? RoleDelete; /// /// Sent when a guild scheduled event is created. /// The inner payload is a object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildScheduledEventCreate; /// /// Sent when a guild scheduled event is updated. /// The inner payload is a object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildScheduledEventUpdate; /// /// Sent when a guild scheduled event is deleted. /// The inner payload is a object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildScheduledEventDelete; /// /// Sent when a user has subscribed to a guild scheduled event.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildScheduledEventUserAdd; /// /// Sent when a user has unsubscribed from a guild scheduled event.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildScheduledEventUserRemove; /// /// Sent when an integration is created. /// The inner payload is an integration object with a set .
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildIntegrationCreate; /// /// Sent when an integration is updated. /// The inner payload is an integration object with a set .
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildIntegrationUpdate; /// /// Sent when an integration is deleted.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? GuildIntegrationDelete; /// /// Sent when a new invite to a channel is created. Only sent if the bot has the permission for the relevant channel.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// - public event Func? GuildInviteCreate; + ///
+ public event Func? InviteCreate; /// /// Sent when an invite is deleted. Only sent if the bot has the permission for the relevant channel.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// - public event Func? GuildInviteDelete; + ///
+ public event Func? InviteDelete; /// /// Sent when a message is created. /// The inner payload is a message object with set , and fields.
+ ///
+ /// ///
Required Intents: , * ///
Optional Intents: /// - /// - /// - /// - /// - /// - /// For receiving , , and .
- /// This does not apply to: - /// - /// - /// - /// Content in messages sent by the bot. - /// - /// - /// - /// - /// Content in DMs with the bot. - /// - /// - /// - /// - /// Content in which the bot is mentioned. - /// - /// - /// - /// - /// Content of messages a message context menu command is used on. - /// - /// - /// - ///
- ///
+ /// + /// + /// + /// + /// + /// For receiving , , and .
+ /// This does not apply to: + /// + /// + /// + /// Content in messages sent by the bot. + /// + /// + /// + /// + /// Content in DMs with the bot. + /// + /// + /// + /// + /// Content in which the bot is mentioned. + /// + /// + /// + /// + /// Content of messages a message context menu command is used on. + /// + /// + /// + ///
+ ///
///
///

/// *Ephemeral messages do not use the guild channel. Because of this, they are tied to the intent, and the message object won't include a or . - /// + ///
public event Func? MessageCreate; /// /// Sent when a message is updated. - /// The inner payload is a partial message object, with only the message's ID and Guild ID being guaranteed present, all other fields can be null.
+ /// The inner payload is a message object with set , and fields.
+ ///
+ /// ///
Required Intents: , * ///
Optional Intents: /// - /// - /// - /// - /// - /// - /// For receiving , , and .
- /// This does not apply to: - /// - /// - /// - /// Content in messages sent by the bot. - /// - /// - /// - /// - /// Content in DMs with the bot. - /// - /// - /// - /// - /// Content in which the bot is mentioned. - /// - /// - /// - /// - /// Content of messages a message context menu command is used on. - /// - /// - /// - ///
- ///
+ /// + /// + /// + /// + /// + /// For receiving , , and .
+ /// This does not apply to: + /// + /// + /// + /// Content in messages sent by the bot. + /// + /// + /// + /// + /// Content in DMs with the bot. + /// + /// + /// + /// + /// Content in which the bot is mentioned. + /// + /// + /// + /// + /// Content of messages a message context menu command is used on. + /// + /// + /// + ///
+ ///
///
///

/// *Ephemeral messages do not use the guild channel. Because of this, they are tied to the intent, and the message object won't include a or . - /// - public event Func? MessageUpdate; + ///
+ public event Func? MessageUpdate; /// /// Sent when a message is deleted.
+ ///
+ /// ///
Required Intents: , ///
Optional Intents: None - /// + ///
public event Func? MessageDelete; /// /// Sent when multiple messages are deleted at once.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? MessageDeleteBulk; /// /// Sent when a user adds a reaction to a message.
+ ///
+ /// ///
Required Intents: , ///
Optional Intents: None - /// + ///
public event Func? MessageReactionAdd; /// /// Sent when a user removes a reaction from a message.
+ ///
+ /// ///
Required Intents: , ///
Optional Intents: None - /// + ///
public event Func? MessageReactionRemove; /// /// Sent when a user explicitly removes all reactions from a message.
+ ///
+ /// ///
Required Intents: , ///
Optional Intents: None - /// + ///
public event Func? MessageReactionRemoveAll; /// /// Sent when a user removes all instances of a given emoji from the reactions of a message.
+ ///
+ /// ///
Required Intents: , ///
Optional Intents: None - /// + ///
public event Func? MessageReactionRemoveEmoji; /// /// Sent when a user's presence or info, such as their name or avatar, is updated. Requires the intent. - /// The user object within this event can be partial, with the id being the only required field, everything else is optional. Along with this limitation, no fields are required, and the types of the fields are not validated. You should expect any combination of fields and types within this event.
+ /// The user object within this event can be partial, with the ID being the only required field, everything else is optional. Along with this limitation, no fields are required, and the types of the fields are not validated. You should expect any combination of fields and types within this event.
+ ///
+ /// ///
Required Intents: * ///
Optional Intents: None ///

/// *Must also be enabled in the developer portal. - /// + ///
public event Func? PresenceUpdate; /// /// Sent when a user starts typing in a channel, and fires again every 10 seconds while they continue typing.
+ ///
+ /// ///
Required Intents: , ///
Optional Intents: None - /// + ///
public event Func? TypingStart; /// /// Sent when properties about the current bot's user change. /// Inner payload is a object.
+ ///
+ /// ///
Required Intents: None ///
Optional Intents: None - /// + ///
public event Func? CurrentUserUpdate; /// /// Sent when someone joins/leaves/moves voice channels. /// Inner payload is a object.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? VoiceStateUpdate; /// /// Sent when a guild's voice server is updated. This is sent when initially connecting to voice, and when the current voice instance fails over to a new server.
+ ///
+ /// ///
Required Intents: None ///
Optional Intents: None - /// + ///
public event Func? VoiceServerUpdate; /// /// Sent when a guild channel's webhook is created, updated, or deleted.
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? WebhooksUpdate; public event Func? MessagePollVoteAdd; @@ -598,33 +710,41 @@ public partial class GatewayClient : WebSocketClient, IEntity /// /// Sent when a user uses an interaction. /// Inner payload is an .
+ ///
+ /// ///
Required Intents: None ///
Optional Intents: None - /// + ///
public event Func? InteractionCreate; /// /// Sent when a is created (i.e. the Stage is now 'live'). /// Inner payload is a .
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? StageInstanceCreate; /// /// Sent when a is updated. /// Inner payload is a .
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? StageInstanceUpdate; /// /// Sent when a is deleted (i.e. the Stage has been closed). /// Inner payload is a .
+ ///
+ /// ///
Required Intents: ///
Optional Intents: None - /// + ///
public event Func? StageInstanceDelete; /// @@ -655,7 +775,7 @@ public partial class GatewayClient : WebSocketClient, IEntity public IGatewayClientCache Cache { get; private set; } /// - /// The session id of the . + /// The session ID of the . /// public string? SessionId { get; private set; } @@ -716,7 +836,7 @@ private protected override void OnConnected() _compression.Initialize(); } - private ValueTask SendIdentifyAsync(PresenceProperties? presence = null, CancellationToken cancellationToken = default) + private ValueTask SendIdentifyAsync(ConnectionState connectionState, PresenceProperties? presence = null, CancellationToken cancellationToken = default) { var serializedPayload = new GatewayPayloadProperties(GatewayOpcode.Identify, new(Token.RawToken) { @@ -727,7 +847,7 @@ private ValueTask SendIdentifyAsync(PresenceProperties? presence = null, Cancell Intents = _configuration.Intents, }).Serialize(Serialization.Default.GatewayPayloadPropertiesGatewayIdentifyProperties); _latencyTimer.Start(); - return SendPayloadAsync(serializedPayload, cancellationToken); + return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken); } /// @@ -738,8 +858,8 @@ private ValueTask SendIdentifyAsync(PresenceProperties? presence = null, Cancell /// public async Task StartAsync(PresenceProperties? presence = null, CancellationToken cancellationToken = default) { - await StartAsync(cancellationToken).ConfigureAwait(false); - await SendIdentifyAsync(presence, cancellationToken).ConfigureAwait(false); + var connectionState = await StartAsync(cancellationToken).ConfigureAwait(false); + await SendIdentifyAsync(connectionState, presence, cancellationToken).ConfigureAwait(false); } /// @@ -751,35 +871,35 @@ public async Task StartAsync(PresenceProperties? presence = null, CancellationTo /// public async Task ResumeAsync(string sessionId, int sequenceNumber, CancellationToken cancellationToken = default) { - await ConnectAsync(cancellationToken).ConfigureAwait(false); - await TryResumeAsync(SessionId = sessionId, SequenceNumber = sequenceNumber, cancellationToken).ConfigureAwait(false); + var connectionState = await StartAsync(cancellationToken).ConfigureAwait(false); + await TryResumeAsync(connectionState, SessionId = sessionId, SequenceNumber = sequenceNumber, cancellationToken).ConfigureAwait(false); } private protected override bool Reconnect(WebSocketCloseStatus? status, string? description) => status is not ((WebSocketCloseStatus)4004 or (WebSocketCloseStatus)4010 or (WebSocketCloseStatus)4011 or (WebSocketCloseStatus)4012 or (WebSocketCloseStatus)4013 or (WebSocketCloseStatus)4014); - private protected override ValueTask TryResumeAsync(CancellationToken cancellationToken = default) + private protected override ValueTask TryResumeAsync(ConnectionState connectionState, CancellationToken cancellationToken = default) { - return TryResumeAsync(SessionId!, SequenceNumber, cancellationToken); + return TryResumeAsync(connectionState, SessionId!, SequenceNumber, cancellationToken); } - private ValueTask TryResumeAsync(string sessionId, int sequenceNumber, CancellationToken cancellationToken = default) + private ValueTask TryResumeAsync(ConnectionState connectionState, string sessionId, int sequenceNumber, CancellationToken cancellationToken = default) { var serializedPayload = new GatewayPayloadProperties(GatewayOpcode.Resume, new(Token.RawToken, sessionId, sequenceNumber)).Serialize(Serialization.Default.GatewayPayloadPropertiesGatewayResumeProperties); _latencyTimer.Start(); - return SendPayloadAsync(serializedPayload, cancellationToken); + return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken); } - private protected override ValueTask HeartbeatAsync(CancellationToken cancellationToken = default) + private protected override ValueTask HeartbeatAsync(ConnectionState connectionState, CancellationToken cancellationToken = default) { var serializedPayload = new GatewayPayloadProperties(GatewayOpcode.Heartbeat, SequenceNumber).Serialize(Serialization.Default.GatewayPayloadPropertiesInt32); _latencyTimer.Start(); - return SendPayloadAsync(serializedPayload, cancellationToken); + return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken); } private protected override JsonPayload CreatePayload(ReadOnlyMemory payload) => JsonSerializer.Deserialize(_compression.Decompress(payload).Span, Serialization.Default.JsonPayload)!; - private protected override async Task ProcessPayloadAsync(JsonPayload payload) + private protected override async Task ProcessPayloadAsync(State state, ConnectionState connectionState, JsonPayload payload) { switch ((GatewayOpcode)payload.Opcode) { @@ -787,7 +907,7 @@ private protected override async Task ProcessPayloadAsync(JsonPayload payload) SequenceNumber = payload.SequenceNumber.GetValueOrDefault(); try { - await ProcessEventAsync(payload).ConfigureAwait(false); + await ProcessEventAsync(state, connectionState, payload).ConfigureAwait(false); } catch (Exception ex) { @@ -798,13 +918,13 @@ private protected override async Task ProcessPayloadAsync(JsonPayload payload) break; case GatewayOpcode.Reconnect: InvokeLog(LogMessage.Info("Reconnect request")); - await AbortAndReconnectAsync().ConfigureAwait(false); + await AbortAndReconnectAsync(state, connectionState).ConfigureAwait(false); break; case GatewayOpcode.InvalidSession: InvokeLog(LogMessage.Info("Invalid session")); try { - await SendIdentifyAsync().ConfigureAwait(false); + await SendIdentifyAsync(connectionState).ConfigureAwait(false); } catch (Exception ex) { @@ -812,7 +932,7 @@ private protected override async Task ProcessPayloadAsync(JsonPayload payload) } break; case GatewayOpcode.Hello: - StartHeartbeating(payload.Data.GetValueOrDefault().ToObject(Serialization.Default.JsonHello).HeartbeatInterval); + StartHeartbeating(connectionState, payload.Data.GetValueOrDefault().ToObject(Serialization.Default.JsonHello).HeartbeatInterval); break; case GatewayOpcode.HeartbeatACK: await UpdateLatencyAsync(_latencyTimer.Elapsed).ConfigureAwait(false); @@ -823,33 +943,34 @@ private protected override async Task ProcessPayloadAsync(JsonPayload payload) /// /// Joins, moves, or disconnects the app from a voice channel. /// - public ValueTask UpdateVoiceStateAsync(VoiceStateProperties voiceState, CancellationToken cancellationToken = default) + public ValueTask UpdateVoiceStateAsync(VoiceStateProperties voiceState, WebSocketPayloadProperties? properties = null, CancellationToken cancellationToken = default) { GatewayPayloadProperties payload = new(GatewayOpcode.VoiceStateUpdate, voiceState); - return SendPayloadAsync(payload.Serialize(Serialization.Default.GatewayPayloadPropertiesVoiceStateProperties), cancellationToken); + return SendPayloadAsync(payload.Serialize(Serialization.Default.GatewayPayloadPropertiesVoiceStateProperties), properties, cancellationToken); } /// /// Updates an app's presence. /// /// The presence to set. + /// /// The cancellation token to cancel the operation. - public ValueTask UpdatePresenceAsync(PresenceProperties presence, CancellationToken cancellationToken = default) + public ValueTask UpdatePresenceAsync(PresenceProperties presence, WebSocketPayloadProperties? properties = null, CancellationToken cancellationToken = default) { GatewayPayloadProperties payload = new(GatewayOpcode.PresenceUpdate, presence); - return SendPayloadAsync(payload.Serialize(Serialization.Default.GatewayPayloadPropertiesPresenceProperties), cancellationToken); + return SendPayloadAsync(payload.Serialize(Serialization.Default.GatewayPayloadPropertiesPresenceProperties), properties, cancellationToken); } /// - /// Requests user for a guild. + /// Requests users for a guild. /// - public ValueTask RequestGuildUsersAsync(GuildUsersRequestProperties requestProperties, CancellationToken cancellationToken = default) + public ValueTask RequestGuildUsersAsync(GuildUsersRequestProperties requestProperties, WebSocketPayloadProperties? properties = null, CancellationToken cancellationToken = default) { GatewayPayloadProperties payload = new(GatewayOpcode.RequestGuildUsers, requestProperties); - return SendPayloadAsync(payload.Serialize(Serialization.Default.GatewayPayloadPropertiesGuildUsersRequestProperties), cancellationToken); + return SendPayloadAsync(payload.Serialize(Serialization.Default.GatewayPayloadPropertiesGuildUsersRequestProperties), properties, cancellationToken); } - private async Task ProcessEventAsync(JsonPayload payload) + private async Task ProcessEventAsync(State state, ConnectionState connectionState, JsonPayload payload) { var data = payload.Data.GetValueOrDefault(); var name = payload.Event!; @@ -871,7 +992,7 @@ await InvokeEventAsync(Ready, args, data => SessionId = args.SessionId; ApplicationFlags = args.ApplicationFlags; - _readyCompletionSource.TrySetResult(); + state.IndicateReady(connectionState); }).ConfigureAwait(false); await updateLatencyTask.ConfigureAwait(false); } @@ -883,7 +1004,7 @@ await InvokeEventAsync(Ready, args, data => var updateLatencyTask = UpdateLatencyAsync(latency); var resumeTask = InvokeResumeEventAsync(); - _readyCompletionSource.TrySetResult(); + state.IndicateReady(connectionState); await updateLatencyTask.ConfigureAwait(false); await resumeTask.ConfigureAwait(false); @@ -1007,7 +1128,7 @@ await InvokeEventAsync(Ready, args, data => break; case "GUILD_AUDIT_LOG_ENTRY_CREATE": { - await InvokeEventAsync(GuildAuditLogEntryCreate, () => new(data.ToObject(Serialization.Default.JsonAuditLogEntry))).ConfigureAwait(false); + await InvokeEventAsync(GuildAuditLogEntryCreate, () => new(data.ToObject(Serialization.Default.JsonAuditLogEntry), GetGuildId())).ConfigureAwait(false); } break; case "GUILD_BAN_ADD": @@ -1137,12 +1258,12 @@ await InvokeEventAsync(Ready, args, data => break; case "INVITE_CREATE": { - await InvokeEventAsync(GuildInviteCreate, () => new(data.ToObject(Serialization.Default.JsonGuildInvite), Rest)).ConfigureAwait(false); + await InvokeEventAsync(InviteCreate, () => new(data.ToObject(Serialization.Default.JsonInvite), Rest)).ConfigureAwait(false); } break; case "INVITE_DELETE": { - await InvokeEventAsync(GuildInviteDelete, () => new(data.ToObject(Serialization.Default.JsonGuildInviteDeleteEventArgs))).ConfigureAwait(false); + await InvokeEventAsync(InviteDelete, () => new(data.ToObject(Serialization.Default.JsonInviteDeleteEventArgs))).ConfigureAwait(false); } break; case "MESSAGE_CREATE": @@ -1167,7 +1288,7 @@ await InvokeEventAsync( await InvokeEventAsync( MessageUpdate, () => data.ToObject(Serialization.Default.JsonMessage), - json => IPartialMessage.CreateFromJson(json, Cache, Rest), + json => Message.CreateFromJson(json, Cache, Rest), json => _configuration.CacheDMChannels && !json.GuildId.HasValue && !json.Flags.GetValueOrDefault().HasFlag(MessageFlags.Ephemeral), json => { diff --git a/NetCord/Gateway/GatewayClientCache.cs b/NetCord/Gateway/GatewayClientCache.cs index 3955cfb2..0fd8c32e 100644 --- a/NetCord/Gateway/GatewayClientCache.cs +++ b/NetCord/Gateway/GatewayClientCache.cs @@ -19,8 +19,8 @@ public GatewayClientCache(JsonGatewayClientCache jsonModel, ulong clientId, Rest var userModel = jsonModel.User; if (userModel is not null) _user = new(userModel, client); - _DMChannels = jsonModel.DMChannels.ToImmutableDictionary(c => DMChannel.CreateFromJson(c, client)); - _guilds = jsonModel.Guilds.ToImmutableDictionary(g => new Guild(g, clientId, client)); + _DMChannels = jsonModel.DMChannels.ToImmutableDictionary(c => c.Id, c => DMChannel.CreateFromJson(c, client)); + _guilds = jsonModel.Guilds.ToImmutableDictionary(g => g.Id, g => new Guild(g, clientId, client)); } public CurrentUser? User => _user; @@ -38,8 +38,8 @@ public JsonGatewayClientCache ToJsonModel() return new() { User = _user is null ? null : ((IJsonModel)_user).JsonModel, - DMChannels = _DMChannels.ToDictionary(p => p.Key, p => ((IJsonModel)p.Value).JsonModel), - Guilds = _guilds.ToDictionary(p => p.Key, p => ((IJsonModel)p.Value).JsonModel), + DMChannels = _DMChannels.Select(p => ((IJsonModel)p.Value).JsonModel).ToArray(), + Guilds = _guilds.Select(p => ((IJsonModel)p.Value).JsonModel).ToArray(), }; } @@ -74,28 +74,28 @@ public IGatewayClientCache CacheGuildUser(GuildUser user) return this; } - public IGatewayClientCache CacheGuildUsers(ulong guildId, IEnumerable> users) + public IGatewayClientCache CacheGuildUsers(ulong guildId, IEnumerable users) { var guilds = _guilds; if (guilds.TryGetValue(guildId, out var guild)) { return this with { - _guilds = guilds.SetItem(guildId, guild.With(g => g.Users = g.Users.SetItems(users))), + _guilds = guilds.SetItem(guildId, guild.With(g => g.Users = g.Users.SetItems(users.Select(u => new KeyValuePair(u.Id, u))))), }; } return this; } - public IGatewayClientCache CachePresences(ulong guildId, IEnumerable> presences) + public IGatewayClientCache CachePresences(ulong guildId, IEnumerable presences) { var guilds = _guilds; if (guilds.TryGetValue(guildId, out var guild)) { return this with { - _guilds = guilds.SetItem(guildId, guild.With(g => g.Presences = g.Presences.SetItems(presences))), + _guilds = guilds.SetItem(guildId, guild.With(g => g.Presences = g.Presences.SetItems(presences.Select(p => new KeyValuePair(p.User.Id, p))))), }; } diff --git a/NetCord/Gateway/GatewayClientConfiguration.cs b/NetCord/Gateway/GatewayClientConfiguration.cs index 2621c882..b9535e00 100644 --- a/NetCord/Gateway/GatewayClientConfiguration.cs +++ b/NetCord/Gateway/GatewayClientConfiguration.cs @@ -7,7 +7,9 @@ namespace NetCord.Gateway; public class GatewayClientConfiguration : IWebSocketClientConfiguration { - public IWebSocket? WebSocket { get; init; } + public IWebSocketConnectionProvider? WebSocketConnectionProvider { get; init; } + public IRateLimiterProvider? RateLimiterProvider { get; init; } + public WebSocketPayloadProperties? DefaultPayloadProperties { get; init; } public IReconnectStrategy? ReconnectStrategy { get; init; } public ILatencyTimer? LatencyTimer { get; init; } public ApiVersion Version { get; init; } = ApiVersion.V10; @@ -21,4 +23,6 @@ public class GatewayClientConfiguration : IWebSocketClientConfiguration public Shard? Shard { get; init; } public bool CacheDMChannels { get; init; } = true; public Rest.RestClientConfiguration? RestClientConfiguration { get; init; } + + IRateLimiterProvider? IWebSocketClientConfiguration.RateLimiterProvider => RateLimiterProvider is { } rateLimiter ? rateLimiter : new GatewayRateLimiterProvider(120, 60_000); } diff --git a/NetCord/Gateway/GatewayIntents.cs b/NetCord/Gateway/GatewayIntents.cs index 76946531..8eec41b5 100644 --- a/NetCord/Gateway/GatewayIntents.cs +++ b/NetCord/Gateway/GatewayIntents.cs @@ -9,36 +9,36 @@ public enum GatewayIntents : uint /// /// Associated with the following events: /// - /// - /// - /// Guild Events: , ,
- ///
- ///
- /// - /// - /// Role Events: , ,
- ///
- ///
- /// - /// - /// Channel Events: , , ,
- ///
- ///
- /// - /// - /// Thread Events: , , ,
- ///
- ///
- /// - /// - /// Thread User Events: ,
- ///
- ///
- /// - /// - /// Stage Events: , , - /// - /// + /// + /// + /// Guild Events: , ,
+ ///
+ ///
+ /// + /// + /// Role Events: , ,
+ ///
+ ///
+ /// + /// + /// Channel Events: , , ,
+ ///
+ ///
+ /// + /// + /// Thread Events: , , ,
+ ///
+ ///
+ /// + /// + /// Thread User Events: ,
+ ///
+ ///
+ /// + /// + /// Stage Events: , , + /// + /// ///
///
Guilds = 1 << 0, @@ -75,7 +75,7 @@ public enum GatewayIntents : uint /// /// Associated with the following events:
- /// , + /// , ///
GuildInvites = 1 << 6, diff --git a/NetCord/Gateway/GatewayRateLimiterProvider.cs b/NetCord/Gateway/GatewayRateLimiterProvider.cs new file mode 100644 index 00000000..bbea17dd --- /dev/null +++ b/NetCord/Gateway/GatewayRateLimiterProvider.cs @@ -0,0 +1,59 @@ +namespace NetCord.Gateway; + +public class GatewayRateLimiterProvider(int limit, long duration) : IRateLimiterProvider +{ + public IRateLimiter CreateRateLimiter() => new GatewayRateLimiter(limit, duration); + + private sealed class GatewayRateLimiter(int limit, long duration) : IRateLimiter + { + private readonly object _lock = new(); + private readonly int _limit = limit; + private int _remaining = limit; + private long _reset; + + public ValueTask TryAcquireAsync(CancellationToken cancellationToken = default) + { + var timestamp = Environment.TickCount64; + lock (_lock) + { + var diff = _reset - timestamp; + if (diff <= 0) + { + _remaining = _limit - 1; + _reset = timestamp + duration; + } + else + { + if (_remaining == 0) + return new(RateLimitAcquisitionResult.RateLimit((int)diff)); + else + _remaining--; + } + } + + return new(RateLimitAcquisitionResult.NoRateLimit); + } + + public ValueTask CancelAcquireAsync(long acquisitionTimestamp, CancellationToken cancellationToken = default) + { + var currentTimestamp = Environment.TickCount64; + lock (_lock) + { + var reset = _reset; + var start = reset - duration; + if (acquisitionTimestamp <= reset + && acquisitionTimestamp >= start + && currentTimestamp <= reset + && currentTimestamp >= start + && _remaining < _limit) + _remaining++; + } + + return default; + } + + public void Dispose() + { + } + } +} diff --git a/NetCord/Gateway/Guild.cs b/NetCord/Gateway/Guild.cs index 37a0698c..6ca36877 100644 --- a/NetCord/Gateway/Guild.cs +++ b/NetCord/Gateway/Guild.cs @@ -6,22 +6,11 @@ namespace NetCord.Gateway; +/// +/// Contains additional information about the guild's current state. +/// public class Guild : RestGuild { - public ImmutableDictionary VoiceStates { get; set; } - public ImmutableDictionary Users { get; set; } - public ImmutableDictionary Channels { get; set; } - public ImmutableDictionary ActiveThreads { get; set; } - public ImmutableDictionary StageInstances { get; set; } - public ImmutableDictionary Presences { get; set; } - public ImmutableDictionary ScheduledEvents { get; set; } - - public override bool IsOwner { get; } - public DateTimeOffset JoinedAt => _jsonModel.JoinedAt; - public bool IsLarge => _jsonModel.IsLarge; - public bool IsUnavailable => _jsonModel.IsUnavailable; - public int UserCount => _jsonModel.UserCount; - public Guild(JsonGuild jsonModel, ulong clientId, RestClient client) : base(jsonModel, client) { var guildId = jsonModel.Id; @@ -69,4 +58,62 @@ public Guild With(Action action) action(cloned); return cloned; } + + /// + /// When the current user joined the . + /// + public DateTimeOffset JoinedAt => _jsonModel.JoinedAt; + + /// + /// Whether the 's member count is over the large threshold. + /// + public bool IsLarge => _jsonModel.IsLarge; + + /// + /// Whether the is unavailable due to an outage. + /// + public bool IsUnavailable => _jsonModel.IsUnavailable; + + /// + /// The total number of s in the . + /// + public int UserCount => _jsonModel.UserCount; + + /// + /// A dictionary of objects, representing the states of s currently in voice channels. + /// + public ImmutableDictionary VoiceStates { get; set; } + + /// + /// A dictionary of objects, representing users in the . + /// + public ImmutableDictionary Users { get; set; } + + /// + /// A dictionary of objects, representing channels present in the . + /// + public ImmutableDictionary Channels { get; set; } + + /// + /// An array of objects, representing all active threads in the that current user has permission to view. + /// + public ImmutableDictionary ActiveThreads { get; set; } + + /// + /// A dictionary of objects, will only include offline users if is . + /// + public ImmutableDictionary Presences { get; set; } + + /// + /// A dictionary of objects, representing active stage instances in the . + /// + public ImmutableDictionary StageInstances { get; set; } + + /// + /// A dictionary of objects, representing currently scheduled events in the . + /// + public ImmutableDictionary ScheduledEvents { get; set; } + + /// + public override bool IsOwner { get; } } diff --git a/NetCord/Gateway/GuildJoinRequestFormResponseFieldType.cs b/NetCord/Gateway/GuildJoinRequestFormResponseFieldType.cs index 0cf645ae..308a0f19 100644 --- a/NetCord/Gateway/GuildJoinRequestFormResponseFieldType.cs +++ b/NetCord/Gateway/GuildJoinRequestFormResponseFieldType.cs @@ -2,7 +2,7 @@ namespace NetCord.Gateway; -[JsonConverter(typeof(JsonConverters.StringEnumConverterWithErrorHandling))] +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum GuildJoinRequestFormResponseFieldType { [JsonPropertyName("TERMS")] diff --git a/NetCord/Gateway/GuildJoinRequestStatus.cs b/NetCord/Gateway/GuildJoinRequestStatus.cs index 39e8e3bf..f62f2941 100644 --- a/NetCord/Gateway/GuildJoinRequestStatus.cs +++ b/NetCord/Gateway/GuildJoinRequestStatus.cs @@ -2,7 +2,7 @@ namespace NetCord.Gateway; -[JsonConverter(typeof(JsonConverters.StringEnumConverterWithErrorHandling))] +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum GuildJoinRequestStatus { [JsonPropertyName("STARTED")] diff --git a/NetCord/Gateway/IGatewayClientCache.cs b/NetCord/Gateway/IGatewayClientCache.cs index 83f0a404..4b866ff3 100644 --- a/NetCord/Gateway/IGatewayClientCache.cs +++ b/NetCord/Gateway/IGatewayClientCache.cs @@ -11,8 +11,8 @@ public interface IGatewayClientCache : IDisposable public IGatewayClientCache CacheDMChannel(DMChannel dMChannel); public IGatewayClientCache CacheGuild(Guild guild); public IGatewayClientCache CacheGuildUser(GuildUser user); - public IGatewayClientCache CacheGuildUsers(ulong guildId, IEnumerable> users); - public IGatewayClientCache CachePresences(ulong guildId, IEnumerable> presences); + public IGatewayClientCache CacheGuildUsers(ulong guildId, IEnumerable users); + public IGatewayClientCache CachePresences(ulong guildId, IEnumerable presences); public IGatewayClientCache CacheRole(Role role); public IGatewayClientCache CacheGuildScheduledEvent(GuildScheduledEvent scheduledEvent); public IGatewayClientCache CacheGuildEmojis(ulong guildId, ImmutableDictionary emojis); diff --git a/NetCord/Gateway/IPartialMessage.cs b/NetCord/Gateway/IPartialMessage.cs deleted file mode 100644 index 6ce5f008..00000000 --- a/NetCord/Gateway/IPartialMessage.cs +++ /dev/null @@ -1,296 +0,0 @@ -using NetCord.JsonModels; -using NetCord.Rest; - -namespace NetCord.Gateway; - -public partial interface IPartialMessage : IEntity -{ - public static IPartialMessage CreateFromJson(JsonMessage jsonModel, IGatewayClientCache cache, RestClient client) - { - if (jsonModel.Content is null || jsonModel.Author is null) - { - var (guild, channel) = GetCacheData(jsonModel, cache); - return new PartialMessage(jsonModel, guild, channel, client); - } - - return Message.CreateFromJson(jsonModel, cache, client); - } - - internal static (Guild?, TextChannel?) GetCacheData(JsonMessage jsonModel, IGatewayClientCache cache) - { - Guild? guild; - TextChannel? channel; - var guildId = jsonModel.GuildId; - if (guildId.HasValue) - { - if (cache.Guilds.TryGetValue(guildId.GetValueOrDefault(), out guild)) - { - var channelId = jsonModel.ChannelId; - if (guild.Channels.TryGetValue(channelId, out var guildChannel)) - channel = (TextChannel)guildChannel; - else if (guild.ActiveThreads.TryGetValue(channelId, out var thread)) - channel = thread; - else - channel = null; - } - else - channel = null; - } - else - { - guild = null; - channel = cache.DMChannels.GetValueOrDefault(jsonModel.ChannelId); - } - - return (guild, channel); - } - - public ulong? GuildId { get; } - - public Guild? Guild { get; } - - public TextChannel? Channel { get; } - - public ulong ChannelId { get; } - - public User? Author { get; } - - public string? Content { get; } - - public DateTimeOffset? EditedAt { get; } - - public bool? IsTts { get; } - - public bool? MentionEveryone { get; } - - public IReadOnlyDictionary? MentionedUsers { get; } - - public IReadOnlyList? MentionedRoleIds { get; } - - public IReadOnlyDictionary? MentionedChannels { get; } - - public IReadOnlyDictionary? Attachments { get; } - - public IReadOnlyList? Embeds { get; } - - public IReadOnlyList? Reactions { get; } - - public string? Nonce { get; } - - public bool? IsPinned { get; } - - public ulong? WebhookId { get; } - - public MessageType? Type { get; } - - public MessageActivity? Activity { get; } - - public Application? Application { get; } - - public ulong? ApplicationId { get; } - - public MessageReference? MessageReference { get; } - - public MessageFlags? Flags { get; } - - public RestMessage? ReferencedMessage { get; } - - public MessageInteractionMetadata? InteractionMetadata { get; } - - [Obsolete($"Replaced by '{nameof(InteractionMetadata)}'")] - public MessageInteraction? Interaction { get; } - - public GuildThread? StartedThread { get; } - - public IReadOnlyList? Components { get; } - - public IReadOnlyDictionary? Stickers { get; } - - public int? Position { get; } - - public RoleSubscriptionData? RoleSubscriptionData { get; } - - public InteractionResolvedData? ResolvedData { get; } - - public MessagePoll? Poll { get; } - - public Task ReplyAsync(ReplyMessageProperties replyMessage, RestRequestProperties? properties = null); -} - -internal partial class PartialMessage : ClientEntity, IPartialMessage, IJsonModel -{ - private readonly JsonMessage _jsonModel; - JsonMessage IJsonModel.JsonModel => _jsonModel; - - public PartialMessage(JsonMessage jsonModel, Guild? guild, TextChannel? channel, RestClient client) : base(client) - { - _jsonModel = jsonModel; - - Guild = guild; - Channel = channel; - - var author = jsonModel.Author; - if (author is not null) - { - var guildUser = jsonModel.GuildUser; - if (guildUser is null) - Author = new(jsonModel.Author!, client); - else - { - guildUser.User = jsonModel.Author!; - Author = new GuildUser(guildUser, jsonModel.GuildId.GetValueOrDefault(), client); - } - } - - var mentionedUsers = jsonModel.MentionedUsers; - if (mentionedUsers is not null) - MentionedUsers = mentionedUsers.ToDictionary(u => u.Id, u => - { - var guildUser = u.GuildUser; - if (guildUser is null) - return new User(u, client); - - guildUser.User = u; - return new GuildUser(guildUser, jsonModel.GuildId.GetValueOrDefault(), client); - }); - - var mentionedChannels = jsonModel.MentionedChannels; - if (mentionedChannels is not null) - MentionedChannels = mentionedChannels.ToDictionary(c => c.Id, c => new GuildChannelMention(c)); - - var attachments = jsonModel.Attachments; - if (attachments is not null) - Attachments = attachments.ToDictionary(a => a.Id, Attachment.CreateFromJson); - - var embeds = jsonModel.Embeds; - if (embeds is not null) - Embeds = embeds.Select(e => new Embed(e)).ToArray(); - - var reactions = jsonModel.Reactions; - if (reactions is not null) - Reactions = reactions.Select(r => new MessageReaction(r)).ToArray(); - - var activity = jsonModel.Activity; - if (activity is not null) - Activity = new(activity); - - var application = jsonModel.Application; - if (application is not null) - Application = new(application, client); - - var messageReference = jsonModel.MessageReference; - if (messageReference is not null) - MessageReference = new(messageReference); - - var referencedMessage = jsonModel.ReferencedMessage; - if (referencedMessage is not null) - ReferencedMessage = new(referencedMessage, client); - - var interactionMetadata = jsonModel.InteractionMetadata; - if (interactionMetadata is not null) - InteractionMetadata = new(interactionMetadata, client); - -#pragma warning disable CS0618 // Type or member is obsolete - var interaction = jsonModel.Interaction; - if (interaction is not null) - Interaction = new(interaction, client); -#pragma warning restore CS0618 // Type or member is obsolete - - var startedThread = jsonModel.StartedThread; - if (startedThread is not null) - StartedThread = GuildThread.CreateFromJson(startedThread, client); - - var components = jsonModel.Components; - if (components is not null) - Components = components.Select(IMessageComponent.CreateFromJson).ToArray(); - - var stickers = jsonModel.Stickers; - if (stickers is not null) - Stickers = stickers.ToDictionary(s => s.Id, s => new MessageSticker(s, client)); - - var roleSubscriptionData = jsonModel.RoleSubscriptionData; - if (roleSubscriptionData is not null) - RoleSubscriptionData = new(roleSubscriptionData); - - var resolvedData = jsonModel.ResolvedData; - if (resolvedData is not null) - ResolvedData = new(resolvedData, jsonModel.GuildId, client); - - var poll = jsonModel.Poll; - if (poll is not null) - Poll = new(poll); - } - - public override ulong Id => _jsonModel.Id; - - public ulong? GuildId => _jsonModel.GuildId; - - public Guild? Guild { get; } - - public ulong ChannelId => _jsonModel.ChannelId; - - public TextChannel? Channel { get; } - - public User? Author { get; } - - public string? Content => _jsonModel.Content; - - public DateTimeOffset? EditedAt => _jsonModel.EditedAt; - - public bool? IsTts => _jsonModel.IsTts; - - public bool? MentionEveryone => _jsonModel.MentionEveryone; - - public IReadOnlyDictionary? MentionedUsers { get; } - - public IReadOnlyList? MentionedRoleIds => _jsonModel.MentionedRoleIds; - - public IReadOnlyDictionary? MentionedChannels { get; } - - public IReadOnlyDictionary? Attachments { get; } - - public IReadOnlyList? Embeds { get; } - - public IReadOnlyList? Reactions { get; } - - public string? Nonce => _jsonModel.Nonce; - - public bool? IsPinned => _jsonModel.IsPinned; - - public ulong? WebhookId => _jsonModel.WebhookId; - - public MessageType? Type => _jsonModel.Type; - - public MessageActivity? Activity { get; } - - public Application? Application { get; } - - public ulong? ApplicationId => _jsonModel.ApplicationId; - - public MessageReference? MessageReference { get; } - - public MessageFlags? Flags => _jsonModel.Flags; - - public RestMessage? ReferencedMessage { get; } - - public MessageInteractionMetadata? InteractionMetadata { get; } - - public MessageInteraction? Interaction { get; } - - public GuildThread? StartedThread { get; } - - public IReadOnlyList? Components { get; } - - public IReadOnlyDictionary? Stickers { get; } - - public int? Position => _jsonModel.Position; - - public RoleSubscriptionData? RoleSubscriptionData { get; } - - public InteractionResolvedData? ResolvedData { get; } - - public MessagePoll? Poll { get; } - - public Task ReplyAsync(ReplyMessageProperties replyMessage, RestRequestProperties? properties = null) - => SendAsync(replyMessage.ToMessageProperties(Id), properties); -} diff --git a/NetCord/Gateway/IRateLimiter.cs b/NetCord/Gateway/IRateLimiter.cs new file mode 100644 index 00000000..cbd1c335 --- /dev/null +++ b/NetCord/Gateway/IRateLimiter.cs @@ -0,0 +1,8 @@ +namespace NetCord.Gateway; + +public interface IRateLimiter : IDisposable +{ + public ValueTask TryAcquireAsync(CancellationToken cancellationToken = default); + + public ValueTask CancelAcquireAsync(long acquisitionTimestamp, CancellationToken cancellationToken = default); +} diff --git a/NetCord/Gateway/IRateLimiterProvider.cs b/NetCord/Gateway/IRateLimiterProvider.cs new file mode 100644 index 00000000..885da5e7 --- /dev/null +++ b/NetCord/Gateway/IRateLimiterProvider.cs @@ -0,0 +1,6 @@ +namespace NetCord.Gateway; + +public interface IRateLimiterProvider +{ + public IRateLimiter CreateRateLimiter(); +} diff --git a/NetCord/Gateway/IWebSocketClientConfiguration.cs b/NetCord/Gateway/IWebSocketClientConfiguration.cs index 10b12b1a..392912f0 100644 --- a/NetCord/Gateway/IWebSocketClientConfiguration.cs +++ b/NetCord/Gateway/IWebSocketClientConfiguration.cs @@ -6,7 +6,9 @@ namespace NetCord.Gateway; internal interface IWebSocketClientConfiguration { - public IWebSocket? WebSocket { get; } + public IWebSocketConnectionProvider? WebSocketConnectionProvider { get; } public IReconnectStrategy? ReconnectStrategy { get; } public ILatencyTimer? LatencyTimer { get; } + public IRateLimiterProvider? RateLimiterProvider { get; } + public WebSocketPayloadProperties? DefaultPayloadProperties { get; } } diff --git a/NetCord/Gateway/GuildInvite.cs b/NetCord/Gateway/Invite.cs similarity index 61% rename from NetCord/Gateway/GuildInvite.cs rename to NetCord/Gateway/Invite.cs index eb3b553b..c3da9d67 100644 --- a/NetCord/Gateway/GuildInvite.cs +++ b/NetCord/Gateway/Invite.cs @@ -2,12 +2,12 @@ namespace NetCord.Gateway; -public class GuildInvite : IGuildInvite, IJsonModel +public class Invite : IInvite, IJsonModel { - JsonModels.JsonGuildInvite IJsonModel.JsonModel => _jsonModel; - private readonly JsonModels.JsonGuildInvite _jsonModel; + JsonModels.JsonInvite IJsonModel.JsonModel => _jsonModel; + private readonly JsonModels.JsonInvite _jsonModel; - public GuildInvite(JsonModels.JsonGuildInvite jsonModel, RestClient client) + public Invite(JsonModels.JsonInvite jsonModel, RestClient client) { _jsonModel = jsonModel; @@ -24,6 +24,8 @@ public GuildInvite(JsonModels.JsonGuildInvite jsonModel, RestClient client) TargetApplication = new(targetApplication, client); } + public InviteType Type => _jsonModel.Type; + public ulong ChannelId => _jsonModel.ChannelId; public string Code => _jsonModel.Code; @@ -38,7 +40,7 @@ public GuildInvite(JsonModels.JsonGuildInvite jsonModel, RestClient client) public int MaxUses => _jsonModel.MaxUses; - public GuildInviteTargetType? TargetType => _jsonModel.TargetType; + public InviteTargetType? TargetType => _jsonModel.TargetType; public User? TargetUser { get; } @@ -48,15 +50,15 @@ public GuildInvite(JsonModels.JsonGuildInvite jsonModel, RestClient client) public int Uses => _jsonModel.Uses; - ulong? IGuildInvite.ChannelId => ChannelId; + ulong? IInvite.ChannelId => ChannelId; - int? IGuildInvite.MaxAge => MaxAge; + int? IInvite.MaxAge => MaxAge; - int? IGuildInvite.MaxUses => MaxUses; + int? IInvite.MaxUses => MaxUses; - bool? IGuildInvite.Temporary => Temporary; + bool? IInvite.Temporary => Temporary; - int? IGuildInvite.Uses => Uses; + int? IInvite.Uses => Uses; - DateTimeOffset? IGuildInvite.CreatedAt => CreatedAt; + DateTimeOffset? IInvite.CreatedAt => CreatedAt; } diff --git a/NetCord/Gateway/JsonModels/EventArgs/JsonGuildInviteDeleteEventArgs.cs b/NetCord/Gateway/JsonModels/EventArgs/JsonInviteDeleteEventArgs.cs similarity index 88% rename from NetCord/Gateway/JsonModels/EventArgs/JsonGuildInviteDeleteEventArgs.cs rename to NetCord/Gateway/JsonModels/EventArgs/JsonInviteDeleteEventArgs.cs index 5a762d33..d3c6b4a7 100644 --- a/NetCord/Gateway/JsonModels/EventArgs/JsonGuildInviteDeleteEventArgs.cs +++ b/NetCord/Gateway/JsonModels/EventArgs/JsonInviteDeleteEventArgs.cs @@ -2,7 +2,7 @@ namespace NetCord.Gateway.JsonModels.EventArgs; -public class JsonGuildInviteDeleteEventArgs +public class JsonInviteDeleteEventArgs { [JsonPropertyName("channel_id")] public ulong InviteChannelId { get; set; } diff --git a/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionAddEventArgs.cs b/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionAddEventArgs.cs index 6f4ca1b8..204c3f57 100644 --- a/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionAddEventArgs.cs +++ b/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionAddEventArgs.cs @@ -26,4 +26,13 @@ public class JsonMessageReactionAddEventArgs [JsonPropertyName("message_author_id")] public ulong? MessageAuthorId { get; set; } + + [JsonPropertyName("burst")] + public bool Burst { get; set; } + + [JsonPropertyName("burst_colors")] + public Color[] BurstColors { get; set; } + + [JsonPropertyName("type")] + public ReactionType Type { get; set; } } diff --git a/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionRemoveEventArgs.cs b/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionRemoveEventArgs.cs index a5693081..9351a1bc 100644 --- a/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionRemoveEventArgs.cs +++ b/NetCord/Gateway/JsonModels/EventArgs/JsonMessageReactionRemoveEventArgs.cs @@ -20,4 +20,10 @@ public class JsonMessageReactionRemoveEventArgs [JsonPropertyName("emoji")] public JsonEmoji Emoji { get; set; } + + [JsonPropertyName("burst")] + public bool Burst { get; set; } + + [JsonPropertyName("type")] + public ReactionType Type { get; set; } } diff --git a/NetCord/Gateway/JsonModels/JsonGatewayClientCache.cs b/NetCord/Gateway/JsonModels/JsonGatewayClientCache.cs index 5d8416b9..b3a16cbf 100644 --- a/NetCord/Gateway/JsonModels/JsonGatewayClientCache.cs +++ b/NetCord/Gateway/JsonModels/JsonGatewayClientCache.cs @@ -10,8 +10,8 @@ public class JsonGatewayClientCache public JsonUser? User { get; set; } [JsonPropertyName("dm_channels")] - public IReadOnlyDictionary DMChannels { get; set; } + public IReadOnlyList DMChannels { get; set; } [JsonPropertyName("guilds")] - public IReadOnlyDictionary Guilds { get; set; } + public IReadOnlyList Guilds { get; set; } } diff --git a/NetCord/Gateway/JsonModels/JsonGuildInvite.cs b/NetCord/Gateway/JsonModels/JsonInvite.cs similarity index 87% rename from NetCord/Gateway/JsonModels/JsonGuildInvite.cs rename to NetCord/Gateway/JsonModels/JsonInvite.cs index b6baaad0..ec00d630 100644 --- a/NetCord/Gateway/JsonModels/JsonGuildInvite.cs +++ b/NetCord/Gateway/JsonModels/JsonInvite.cs @@ -4,8 +4,11 @@ namespace NetCord.Gateway.JsonModels; -public class JsonGuildInvite +public class JsonInvite { + [JsonPropertyName("type")] + public InviteType Type { get; set; } + [JsonPropertyName("channel_id")] public ulong ChannelId { get; set; } @@ -28,7 +31,7 @@ public class JsonGuildInvite public int MaxUses { get; set; } [JsonPropertyName("target_type")] - public GuildInviteTargetType? TargetType { get; set; } + public InviteTargetType? TargetType { get; set; } [JsonPropertyName("target_user")] public JsonUser? TargetUser { get; set; } diff --git a/NetCord/Gateway/Message.cs b/NetCord/Gateway/Message.cs index b60e5c17..6006a3a7 100644 --- a/NetCord/Gateway/Message.cs +++ b/NetCord/Gateway/Message.cs @@ -3,27 +3,52 @@ namespace NetCord.Gateway; -public class Message(JsonMessage jsonModel, Guild? guild, TextChannel? channel, RestClient client) : RestMessage(jsonModel, client), IPartialMessage +/// +/// Represents a complete object, with all required fields present. +/// +public class Message(JsonMessage jsonModel, Guild? guild, TextChannel? channel, RestClient client) : RestMessage(jsonModel, client) { public static Message CreateFromJson(JsonMessage jsonModel, IGatewayClientCache cache, RestClient client) { - var (guild, channel) = IPartialMessage.GetCacheData(jsonModel, cache); + var (guild, channel) = GetCacheData(jsonModel, cache); return new(jsonModel, guild, channel, client); } + internal static (Guild?, TextChannel?) GetCacheData(JsonMessage jsonModel, IGatewayClientCache cache) + { + Guild? guild; + TextChannel? channel; + var guildId = jsonModel.GuildId; + if (guildId.HasValue) + { + if (cache.Guilds.TryGetValue(guildId.GetValueOrDefault(), out guild)) + { + var channelId = jsonModel.ChannelId; + if (guild.Channels.TryGetValue(channelId, out var guildChannel)) + channel = (TextChannel)guildChannel; + else if (guild.ActiveThreads.TryGetValue(channelId, out var thread)) + channel = thread; + else + channel = null; + } + else + channel = null; + } + else + { + guild = null; + channel = cache.DMChannels.GetValueOrDefault(jsonModel.ChannelId); + } + + return (guild, channel); + } + + /// public ulong? GuildId => _jsonModel.GuildId; + /// public Guild? Guild { get; } = guild; + /// public TextChannel? Channel { get; } = channel; - - bool? IPartialMessage.IsTts => IsTts; - - bool? IPartialMessage.MentionEveryone => MentionEveryone; - - bool? IPartialMessage.IsPinned => IsPinned; - - MessageType? IPartialMessage.Type => Type; - - MessageFlags? IPartialMessage.Flags => Flags; } diff --git a/NetCord/Gateway/NullRateLimiterProvider.cs b/NetCord/Gateway/NullRateLimiterProvider.cs new file mode 100644 index 00000000..56f54048 --- /dev/null +++ b/NetCord/Gateway/NullRateLimiterProvider.cs @@ -0,0 +1,25 @@ +namespace NetCord.Gateway; + +public class NullRateLimiterProvider : IRateLimiterProvider +{ + public static NullRateLimiterProvider Instance { get; } = new(); + + public IRateLimiter CreateRateLimiter() => NullRateLimiter.Instance; + + private sealed class NullRateLimiter : IRateLimiter + { + public static NullRateLimiter Instance { get; } = new(); + + private NullRateLimiter() + { + } + + public ValueTask TryAcquireAsync(CancellationToken cancellationToken = default) => new(RateLimitAcquisitionResult.NoRateLimit); + + public ValueTask CancelAcquireAsync(long acquisitionTimestamp, CancellationToken cancellationToken = default) => default; + + public void Dispose() + { + } + } +} diff --git a/NetCord/Gateway/Platform.cs b/NetCord/Gateway/Platform.cs index d3d5dd97..13aef7c1 100644 --- a/NetCord/Gateway/Platform.cs +++ b/NetCord/Gateway/Platform.cs @@ -2,7 +2,7 @@ namespace NetCord.Gateway; -[JsonConverter(typeof(JsonConverters.StringEnumConverterWithErrorHandling))] +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum Platform { [JsonPropertyName("desktop")] diff --git a/NetCord/Gateway/RateLimitAcquisitionResult.cs b/NetCord/Gateway/RateLimitAcquisitionResult.cs new file mode 100644 index 00000000..d87e286a --- /dev/null +++ b/NetCord/Gateway/RateLimitAcquisitionResult.cs @@ -0,0 +1,18 @@ +namespace NetCord.Gateway; + +public readonly struct RateLimitAcquisitionResult +{ + private RateLimitAcquisitionResult(int resetAfter, bool rateLimited) + { + ResetAfter = resetAfter; + RateLimited = rateLimited; + } + + public static RateLimitAcquisitionResult NoRateLimit { get; } = new(0, false); + + public static RateLimitAcquisitionResult RateLimit(int resetAfter) => new(resetAfter, true); + + public int ResetAfter { get; } + + public bool RateLimited { get; } +} diff --git a/NetCord/Gateway/RentedArrayBufferWriter.cs b/NetCord/Gateway/RentedArrayBufferWriter.cs index eb4ceddf..964ce239 100644 --- a/NetCord/Gateway/RentedArrayBufferWriter.cs +++ b/NetCord/Gateway/RentedArrayBufferWriter.cs @@ -57,7 +57,7 @@ private void ResizeBuffer(int sizeHint) { var pool = ArrayPool.Shared; var newBuffer = pool.Rent(sum); - Array.Copy(buffer, newBuffer, index); + buffer.AsSpan(0, index).CopyTo(newBuffer); _buffer = newBuffer; pool.Return(buffer); } diff --git a/NetCord/Gateway/ShardedGatewayClient.cs b/NetCord/Gateway/ShardedGatewayClient.cs index 4e848107..087c3244 100644 --- a/NetCord/Gateway/ShardedGatewayClient.cs +++ b/NetCord/Gateway/ShardedGatewayClient.cs @@ -31,7 +31,7 @@ private static ShardedGatewayClientConfiguration CreateConfiguration(ShardedGate { return new() { - WebSocketFactory = _ => null, + WebSocketConnectionProviderFactory = _ => null, ReconnectStrategyFactory = _ => null, LatencyTimerFactory = _ => null, VersionFactory = _ => ApiVersion.V10, @@ -49,7 +49,7 @@ private static ShardedGatewayClientConfiguration CreateConfiguration(ShardedGate return new() { - WebSocketFactory = configuration.WebSocketFactory ?? (_ => null), + WebSocketConnectionProviderFactory = configuration.WebSocketConnectionProviderFactory ?? (_ => null), ReconnectStrategyFactory = configuration.ReconnectStrategyFactory ?? (_ => null), LatencyTimerFactory = configuration.LatencyTimerFactory ?? (_ => null), VersionFactory = configuration.VersionFactory ?? (_ => ApiVersion.V10), @@ -219,7 +219,9 @@ private GatewayClientConfiguration GetGatewayClientConfiguration(Shard shard) var configuration = _configuration; return new() { - WebSocket = configuration.WebSocketFactory!(shard), + WebSocketConnectionProvider = configuration.WebSocketConnectionProviderFactory!(shard), + RateLimiterProvider = configuration.RateLimiterProviderFactory!(shard), + DefaultPayloadProperties = configuration.DefaultPayloadPropertiesFactory!(shard), ReconnectStrategy = configuration.ReconnectStrategyFactory!(shard), LatencyTimer = configuration.LatencyTimerFactory!(shard), Version = configuration.VersionFactory!(shard), @@ -303,8 +305,8 @@ private void HookEvents(GatewayClient client) HookEvent(client, _guildIntegrationCreateLock, ref _guildIntegrationCreate, a => _guildIntegrationCreate!(client, a), (c, e) => c.GuildIntegrationCreate += e); HookEvent(client, _guildIntegrationUpdateLock, ref _guildIntegrationUpdate, a => _guildIntegrationUpdate!(client, a), (c, e) => c.GuildIntegrationUpdate += e); HookEvent(client, _guildIntegrationDeleteLock, ref _guildIntegrationDelete, a => _guildIntegrationDelete!(client, a), (c, e) => c.GuildIntegrationDelete += e); - HookEvent(client, _guildInviteCreateLock, ref _guildInviteCreate, a => _guildInviteCreate!(client, a), (c, e) => c.GuildInviteCreate += e); - HookEvent(client, _guildInviteDeleteLock, ref _guildInviteDelete, a => _guildInviteDelete!(client, a), (c, e) => c.GuildInviteDelete += e); + HookEvent(client, _inviteCreateLock, ref _inviteCreate, a => _inviteCreate!(client, a), (c, e) => c.InviteCreate += e); + HookEvent(client, _inviteDeleteLock, ref _inviteDelete, a => _inviteDelete!(client, a), (c, e) => c.InviteDelete += e); HookEvent(client, _messageCreateLock, ref _messageCreate, a => _messageCreate!(client, a), (c, e) => c.MessageCreate += e); HookEvent(client, _messageUpdateLock, ref _messageUpdate, a => _messageUpdate!(client, a), (c, e) => c.MessageUpdate += e); HookEvent(client, _messageDeleteLock, ref _messageDelete, a => _messageDelete!(client, a), (c, e) => c.MessageDelete += e); @@ -1033,35 +1035,35 @@ public event Func? Gu private Func? _guildIntegrationDelete; private readonly object _guildIntegrationDeleteLock = new(); - /// - public event Func? GuildInviteCreate + /// + public event Func? InviteCreate { add { - HookEvent(_guildInviteCreateLock, value, ref _guildInviteCreate, client => a => _guildInviteCreate!(client, a), (c, e) => c.GuildInviteCreate += e); + HookEvent(_inviteCreateLock, value, ref _inviteCreate, client => a => _inviteCreate!(client, a), (c, e) => c.InviteCreate += e); } remove { - UnhookEvent(_guildInviteCreateLock, value, ref _guildInviteCreate, (c, e) => c.GuildInviteCreate -= e); + UnhookEvent(_inviteCreateLock, value, ref _inviteCreate, (c, e) => c.InviteCreate -= e); } } - private Func? _guildInviteCreate; - private readonly object _guildInviteCreateLock = new(); + private Func? _inviteCreate; + private readonly object _inviteCreateLock = new(); - /// - public event Func? GuildInviteDelete + /// + public event Func? InviteDelete { add { - HookEvent(_guildInviteDeleteLock, value, ref _guildInviteDelete, client => a => _guildInviteDelete!(client, a), (c, e) => c.GuildInviteDelete += e); + HookEvent(_inviteDeleteLock, value, ref _inviteDelete, client => a => _inviteDelete!(client, a), (c, e) => c.InviteDelete += e); } remove { - UnhookEvent(_guildInviteDeleteLock, value, ref _guildInviteDelete, (c, e) => c.GuildInviteDelete -= e); + UnhookEvent(_inviteDeleteLock, value, ref _inviteDelete, (c, e) => c.InviteDelete -= e); } } - private Func? _guildInviteDelete; - private readonly object _guildInviteDeleteLock = new(); + private Func? _inviteDelete; + private readonly object _inviteDeleteLock = new(); /// public event Func? MessageCreate @@ -1079,7 +1081,7 @@ public event Func? MessageCreate private readonly object _messageCreateLock = new(); /// - public event Func? MessageUpdate + public event Func? MessageUpdate { add { @@ -1090,7 +1092,7 @@ public event Func? MessageUpdate UnhookEvent(_messageUpdateLock, value, ref _messageUpdate, (c, e) => c.MessageUpdate -= e); } } - private Func? _messageUpdate; + private Func? _messageUpdate; private readonly object _messageUpdateLock = new(); /// diff --git a/NetCord/Gateway/ShardedGatewayClientConfiguration.cs b/NetCord/Gateway/ShardedGatewayClientConfiguration.cs index 0cce73dd..d6a70c26 100644 --- a/NetCord/Gateway/ShardedGatewayClientConfiguration.cs +++ b/NetCord/Gateway/ShardedGatewayClientConfiguration.cs @@ -7,7 +7,9 @@ namespace NetCord.Gateway; public class ShardedGatewayClientConfiguration { - public Func? WebSocketFactory { get; init; } + public Func? WebSocketConnectionProviderFactory { get; init; } + public Func? RateLimiterProviderFactory { get; init; } + public Func? DefaultPayloadPropertiesFactory { get; init; } public Func? ReconnectStrategyFactory { get; init; } public Func? LatencyTimerFactory { get; init; } public Func? VersionFactory { get; init; } diff --git a/NetCord/Gateway/Voice/GatewayClientExtensions.cs b/NetCord/Gateway/Voice/GatewayClientExtensions.cs index 48b40f84..e03bf521 100644 --- a/NetCord/Gateway/Voice/GatewayClientExtensions.cs +++ b/NetCord/Gateway/Voice/GatewayClientExtensions.cs @@ -6,8 +6,8 @@ public static class GatewayClientExtensions /// Joins a voice channel. ///
/// The instance. - /// The id of the guild containing the channel. - /// The id of the voice channel to join. + /// The ID of the guild containing the channel. + /// The ID of the voice channel to join. /// Configuration settings for the . /// Cancellation token for the operation. If not cancellable, the default timeout of 2 seconds is used. /// This method is not thread safe and should not be used concurrently for the same . @@ -27,7 +27,7 @@ public static async Task JoinVoiceChannelAsync(this GatewayClient c try { - await client.UpdateVoiceStateAsync(new(guildId, channelId)).ConfigureAwait(false); + await client.UpdateVoiceStateAsync(new(guildId, channelId), cancellationToken: cancellationToken).ConfigureAwait(false); } catch { diff --git a/NetCord/Gateway/Voice/VoiceClient.cs b/NetCord/Gateway/Voice/VoiceClient.cs index 06a58acf..1320c981 100644 --- a/NetCord/Gateway/Voice/VoiceClient.cs +++ b/NetCord/Gateway/Voice/VoiceClient.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Net.Sockets; +using System.Text; using NetCord.Gateway.JsonModels; using NetCord.Gateway.Voice.Encryption; @@ -52,11 +53,11 @@ public class VoiceClient : WebSocketClient RedirectInputStreams = configuration.RedirectInputStreams; } - private ValueTask SendIdentifyAsync(CancellationToken cancellationToken = default) + private ValueTask SendIdentifyAsync(ConnectionState connectionState, CancellationToken cancellationToken = default) { var serializedPayload = new VoicePayloadProperties(VoiceOpcode.Identify, new(GuildId, UserId, SessionId, Token)).Serialize(Serialization.Default.VoicePayloadPropertiesVoiceIdentifyProperties); _latencyTimer.Start(); - return SendPayloadAsync(serializedPayload, cancellationToken); + return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken); } /// @@ -65,8 +66,8 @@ private ValueTask SendIdentifyAsync(CancellationToken cancellationToken = defaul /// public async new Task StartAsync(CancellationToken cancellationToken = default) { - await base.StartAsync(cancellationToken).ConfigureAwait(false); - await SendIdentifyAsync(cancellationToken).ConfigureAwait(false); + var connectionState = await base.StartAsync(cancellationToken).ConfigureAwait(false); + await SendIdentifyAsync(connectionState, cancellationToken).ConfigureAwait(false); } /// @@ -75,40 +76,41 @@ private ValueTask SendIdentifyAsync(CancellationToken cancellationToken = defaul /// public async Task ResumeAsync(CancellationToken cancellationToken = default) { - await ConnectAsync(cancellationToken).ConfigureAwait(false); - await TryResumeAsync(cancellationToken).ConfigureAwait(false); + var connectionState = await base.StartAsync(cancellationToken).ConfigureAwait(false); + await TryResumeAsync(connectionState, cancellationToken).ConfigureAwait(false); } private protected override bool Reconnect(WebSocketCloseStatus? status, string? description) => status is not ((WebSocketCloseStatus)4004 or (WebSocketCloseStatus)4006 or (WebSocketCloseStatus)4009 or (WebSocketCloseStatus)4014); - private protected override ValueTask TryResumeAsync(CancellationToken cancellationToken = default) + private protected override ValueTask TryResumeAsync(ConnectionState connectionState, CancellationToken cancellationToken = default) { var serializedPayload = new VoicePayloadProperties(VoiceOpcode.Resume, new(GuildId, SessionId, Token)).Serialize(Serialization.Default.VoicePayloadPropertiesVoiceResumeProperties); _latencyTimer.Start(); - return SendPayloadAsync(serializedPayload, cancellationToken); + return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken); } - private protected override ValueTask HeartbeatAsync(CancellationToken cancellationToken = default) + private protected override ValueTask HeartbeatAsync(ConnectionState connectionState, CancellationToken cancellationToken = default) { var serializedPayload = new VoicePayloadProperties(VoiceOpcode.Heartbeat, Environment.TickCount).Serialize(Serialization.Default.VoicePayloadPropertiesInt32); _latencyTimer.Start(); - return SendPayloadAsync(serializedPayload, cancellationToken); + return SendConnectionPayloadAsync(connectionState, serializedPayload, _internalPayloadProperties, cancellationToken); } - private protected override async Task ProcessPayloadAsync(JsonPayload payload) + private protected override async Task ProcessPayloadAsync(State state, ConnectionState connectionState, JsonPayload payload) { switch ((VoiceOpcode)payload.Opcode) { case VoiceOpcode.Ready: { var latency = _latencyTimer.Elapsed; - await UpdateLatencyAsync(latency).ConfigureAwait(false); + var updateLatencyTask = UpdateLatencyAsync(latency).ConfigureAwait(false); var ready = payload.Data.GetValueOrDefault().ToObject(Serialization.Default.JsonReady); var ssrc = ready.Ssrc; Cache = Cache.CacheCurrentSsrc(ssrc); _udpSocket.Connect(ready.Ip, ready.Port); + if (RedirectInputStreams) { TaskCompletionSource result = new(); @@ -125,7 +127,7 @@ private protected override async Task ProcessPayloadAsync(JsonPayload payload) _udpSocket.DatagramReceive += HandleDatagramReceive; VoicePayloadProperties protocolPayload = new(VoiceOpcode.SelectProtocol, new("udp", new(ip, port, _encryption.Name))); - await SendPayloadAsync(protocolPayload.Serialize(Serialization.Default.VoicePayloadPropertiesProtocolProperties)).ConfigureAwait(false); + await SendConnectionPayloadAsync(connectionState, protocolPayload.Serialize(Serialization.Default.VoicePayloadPropertiesProtocolProperties), _internalPayloadProperties).ConfigureAwait(false); ReadOnlyMemory CreateDatagram() { @@ -145,15 +147,17 @@ void HandleDatagramReceiveOnce(UdpReceiveResult datagram) void GetIpAndPort(out string ip, out ushort port) { Span span = new(datagram); - ip = System.Text.Encoding.UTF8.GetString(span[8..72].TrimEnd((byte)0)); + ip = Encoding.UTF8.GetString(span[8..72].TrimEnd((byte)0)); port = BinaryPrimitives.ReadUInt16BigEndian(span[72..]); } } else { VoicePayloadProperties protocolPayload = new(VoiceOpcode.SelectProtocol, new("udp", new(ready.Ip, ready.Port, _encryption.Name))); - await SendPayloadAsync(protocolPayload.Serialize(Serialization.Default.VoicePayloadPropertiesProtocolProperties)).ConfigureAwait(false); + await SendConnectionPayloadAsync(connectionState, protocolPayload.Serialize(Serialization.Default.VoicePayloadPropertiesProtocolProperties), _internalPayloadProperties).ConfigureAwait(false); } + + await updateLatencyTask; } break; case VoiceOpcode.SessionDescription: @@ -163,7 +167,7 @@ void GetIpAndPort(out string ip, out ushort port) InvokeLog(LogMessage.Info("Ready")); var readyTask = InvokeEventAsync(Ready); - _readyCompletionSource.TrySetResult(); + state.IndicateReady(connectionState); await readyTask.ConfigureAwait(false); } @@ -177,9 +181,11 @@ void GetIpAndPort(out string ip, out ushort port) VoiceInStream voiceInStream = new(this, ssrc, userId); DecryptStream decryptStream = new(voiceInStream, _encryption); - if (_inputStreams.Remove(ssrc, out var stream)) + + var inputStreams = _inputStreams; + if (inputStreams.Remove(ssrc, out var stream)) stream.Dispose(); - _inputStreams[ssrc] = decryptStream; + inputStreams[ssrc] = decryptStream; } break; case VoiceOpcode.HeartbeatACK: @@ -189,7 +195,7 @@ void GetIpAndPort(out string ip, out ushort port) break; case VoiceOpcode.Hello: { - StartHeartbeating(payload.Data.GetValueOrDefault().ToObject(Serialization.Default.JsonHello).HeartbeatInterval); + StartHeartbeating(connectionState, payload.Data.GetValueOrDefault().ToObject(Serialization.Default.JsonHello).HeartbeatInterval); } break; case VoiceOpcode.Resumed: @@ -240,10 +246,10 @@ private async void HandleDatagramReceive(UdpReceiveResult obj) } } - public ValueTask EnterSpeakingStateAsync(SpeakingFlags flags, int delay = 0, CancellationToken cancellationToken = default) + public ValueTask EnterSpeakingStateAsync(SpeakingFlags flags, int delay = 0, WebSocketPayloadProperties? properties = null, CancellationToken cancellationToken = default) { VoicePayloadProperties payload = new(VoiceOpcode.Speaking, new(flags, delay, Cache.Ssrc)); - return SendPayloadAsync(payload.Serialize(Serialization.Default.VoicePayloadPropertiesSpeakingProperties), cancellationToken); + return SendPayloadAsync(payload.Serialize(Serialization.Default.VoicePayloadPropertiesSpeakingProperties), properties, cancellationToken); } /// diff --git a/NetCord/Gateway/Voice/VoiceClientConfiguration.cs b/NetCord/Gateway/Voice/VoiceClientConfiguration.cs index fa8c8ecc..67868134 100644 --- a/NetCord/Gateway/Voice/VoiceClientConfiguration.cs +++ b/NetCord/Gateway/Voice/VoiceClientConfiguration.cs @@ -8,7 +8,9 @@ namespace NetCord.Gateway.Voice; public class VoiceClientConfiguration : IWebSocketClientConfiguration { - public IWebSocket? WebSocket { get; init; } + public IWebSocketConnectionProvider? WebSocketConnectionProvider { get; init; } + public IRateLimiterProvider? RateLimiterProvider { get; init; } + public WebSocketPayloadProperties? DefaultPayloadProperties { get; init; } public IUdpSocket? UdpSocket { get; init; } public IReconnectStrategy? ReconnectStrategy { get; init; } public ILatencyTimer? LatencyTimer { get; init; } diff --git a/NetCord/Gateway/WebSocketClient.cs b/NetCord/Gateway/WebSocketClient.cs index 6e64c07c..7e045f7d 100644 --- a/NetCord/Gateway/WebSocketClient.cs +++ b/NetCord/Gateway/WebSocketClient.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Text.Json; using NetCord.Gateway.JsonModels; @@ -12,35 +13,180 @@ namespace NetCord.Gateway; public abstract class WebSocketClient : IDisposable { - private protected WebSocketClient(IWebSocketClientConfiguration configuration) + private protected record ConnectionStateResult { - var webSocket = configuration.WebSocket ?? new WebSocket(); + public record Success(ConnectionState ConnectionState) : ConnectionStateResult; + + public record Retry : ConnectionStateResult + { + private Retry() + { + } + + public static Retry Instance { get; } = new(); + } + } + + private protected sealed class ConnectionState(IWebSocketConnection connection, IRateLimiter rateLimiter) : IDisposable + { + public IWebSocketConnection Connection => connection; + + public IRateLimiter RateLimiter => rateLimiter; + + public CancellationTokenProvider DisconnectedTokenProvider { get; } = new(); + + private int _state; + + public bool TryIndicateDisconnecting() + { + var disconnecting = Interlocked.Exchange(ref _state, 1) is 0; + + if (disconnecting) + DisconnectedTokenProvider.Cancel(); + + return disconnecting; + } + + public void Dispose() + { + DisconnectedTokenProvider.Dispose(); + RateLimiter.Dispose(); + Connection.Dispose(); + } + } + + private protected sealed class State : IDisposable + { + private ConnectionState? _connectionState; + + public CancellationTokenProvider ClosedTokenProvider { get; } = new(); + + public Task ReadyTask => _readyCompletionSource.Task; + + private TaskCompletionSource _readyCompletionSource = new(); + + public void IndicateReady(ConnectionState connectionState) + { + lock (ClosedTokenProvider) + { + if (_connectionState != connectionState) + return; + + _readyCompletionSource.TrySetResult(new ConnectionStateResult.Success(connectionState)); + } + } + + public ConnectingResult TryIndicateConnecting(ConnectionState connectionState) + { + lock (ClosedTokenProvider) + { + if (ClosedTokenProvider.IsCancellationRequested) + return ConnectingResult.Closed; + + if (_connectionState is not null) + return ConnectingResult.AlreadyStarted; + + _connectionState = connectionState; + } + + return ConnectingResult.Success; + } + + public enum ConnectingResult : byte + { + Success, + AlreadyStarted, + Closed, + } + + public void IndicateConnectingFailed(ConnectionState connectionState) + { + lock (ClosedTokenProvider) + { + _ = connectionState.TryIndicateDisconnecting(); + + if (_connectionState != connectionState) + return; - webSocket.Connecting += HandleConnecting; - webSocket.Connected += HandleConnected; - webSocket.Disconnected += HandleDisconnected; - webSocket.Closed += HandleClosed; - webSocket.MessageReceived += HandleMessageReceived; + _connectionState = null; + } + } + + public bool TryIndicateClosing([MaybeNullWhen(false)] out ConnectionState connectionState) + { + lock (ClosedTokenProvider) + { + ClosedTokenProvider.Cancel(); + + _readyCompletionSource.TrySetCanceled(); + + var previousState = _connectionState; + if (previousState is null || !previousState.TryIndicateDisconnecting()) + { + connectionState = null; + return false; + } + + _connectionState = null; + connectionState = previousState; + } + + return true; + } + + public bool TryIndicateDisconnecting(ConnectionState connectionState) + { + lock (ClosedTokenProvider) + { + if (_connectionState != connectionState || !connectionState.TryIndicateDisconnecting()) + return false; - _webSocket = webSocket; + _connectionState = null; + + _readyCompletionSource.TrySetResult(ConnectionStateResult.Retry.Instance); + _readyCompletionSource = new(); + } + + return true; + } + + public void Dispose() + { + _readyCompletionSource.TrySetCanceled(); + _connectionState?.Dispose(); + ClosedTokenProvider.Dispose(); + } + } + + private const int DefaultBufferSize = 8192; + + private protected WebSocketClient(IWebSocketClientConfiguration configuration) + { + _connectionProvider = configuration.WebSocketConnectionProvider ?? new WebSocketConnectionProvider(); _reconnectStrategy = configuration.ReconnectStrategy ?? new ReconnectStrategy(); _latencyTimer = configuration.LatencyTimer ?? new LatencyTimer(); + _rateLimiterProvider = configuration.RateLimiterProvider ?? NullRateLimiterProvider.Instance; + _defaultPayloadProperties = configuration.DefaultPayloadProperties is { } defaultPayloadProperties ? defaultPayloadProperties with { } : new(); } + private protected static readonly WebSocketPayloadProperties _internalPayloadProperties = new() + { + MessageFlags = WebSocketMessageFlags.EndOfMessage, + RetryHandling = WebSocketRetryHandling.RetryRateLimit, + }; + private readonly object _eventsLock = new(); - private readonly IWebSocket _webSocket; + private readonly IWebSocketConnectionProvider _connectionProvider; private readonly IReconnectStrategy _reconnectStrategy; + private readonly IRateLimiterProvider _rateLimiterProvider; + private readonly WebSocketPayloadProperties _defaultPayloadProperties; private protected readonly ILatencyTimer _latencyTimer; - private protected readonly TaskCompletionSource _readyCompletionSource = new(); - private CancellationTokenProvider? _disconnectedTokenProvider; - private CancellationTokenProvider? _closedTokenProvider; + private State? _state; private protected abstract Uri Uri { get; } - public Task ReadyAsync => _readyCompletionSource.Task; - public TimeSpan Latency { get @@ -68,79 +214,117 @@ private async void HandleConnecting() private async void HandleConnected() { - Interlocked.Exchange(ref _disconnectedTokenProvider, new())?.Cancel(); - OnConnected(); InvokeLog(LogMessage.Info("Connected")); await InvokeEventAsync(Connect).ConfigureAwait(false); } - private async void HandleDisconnected(WebSocketCloseStatus? closeStatus, string? description) + private async void HandleDisconnected(State state, ConnectionState connectionState) { - Interlocked.Exchange(ref _disconnectedTokenProvider, null)?.Cancel(); + var connection = connectionState.Connection; + + var description = connection.CloseStatusDescription; + InvokeLog(LogMessage.Info("Disconnected", string.IsNullOrEmpty(description) ? null : (description.EndsWith('.') ? description[..^1] : description))); + + var reconnect = Reconnect((WebSocketCloseStatus?)connection.CloseStatus, description); - InvokeLog(string.IsNullOrEmpty(description) ? LogMessage.Info("Disconnected") : LogMessage.Info("Disconnected", description.EndsWith('.') ? description[..^1] : description)); - var reconnect = Reconnect(closeStatus, description); var disconnectTask = InvokeEventAsync(Disconnect, reconnect); + if (reconnect) - await ReconnectAsync().ConfigureAwait(false); + { + connectionState.Dispose(); + await ReconnectAsync(state).ConfigureAwait(false); + } else - _readyCompletionSource.TrySetCanceled(); + { + Interlocked.CompareExchange(ref _state, null, state); + connectionState.Dispose(); + state.Dispose(); + } await disconnectTask.ConfigureAwait(false); } private async void HandleClosed() { - Interlocked.Exchange(ref _disconnectedTokenProvider, null)?.Cancel(); - InvokeLog(LogMessage.Info("Closed")); - var closeTask = InvokeEventAsync(Close).ConfigureAwait(false); - - _readyCompletionSource.TrySetCanceled(); - - await closeTask; + await InvokeEventAsync(Close).ConfigureAwait(false); } - private async void HandleMessageReceived(ReadOnlyMemory data) + private async void HandleMessageReceived(State state, ConnectionState connectionState, ReadOnlyMemory data) { try { - JsonPayload payload; - try - { - payload = CreatePayload(data); - } - catch (Exception ex) - { - InvokeLog(LogMessage.Error(ex)); - await AbortAndReconnectAsync().ConfigureAwait(false); - return; - } - - await ProcessPayloadAsync(payload).ConfigureAwait(false); + var payload = CreatePayload(data); + await ProcessPayloadAsync(state, connectionState, payload).ConfigureAwait(false); } catch (Exception ex) { InvokeLog(LogMessage.Error(ex)); + await AbortAndReconnectAsync(state, connectionState).ConfigureAwait(false); } } - private protected Task StartAsync(CancellationToken cancellationToken = default) + private protected async Task StartAsync(CancellationToken cancellationToken = default) { - CancellationTokenProvider newTokenProvider = new(); - if (Interlocked.CompareExchange(ref _closedTokenProvider, newTokenProvider, null) is not null) + State state = new(); + if (Interlocked.CompareExchange(ref _state, state, null) is not null) + { + state.Dispose(); + ThrowConnectionAlreadyStarted(); + } + + ConnectionState connectionState; + try { - newTokenProvider.Dispose(); - throw new InvalidOperationException("Connection already started."); + connectionState = await ConnectAsync(state, cancellationToken).ConfigureAwait(false); + } + catch + { + Interlocked.CompareExchange(ref _state, null, state); + state.Dispose(); + throw; } - return ConnectAsync(cancellationToken); + return connectionState; } - private protected Task ConnectAsync(CancellationToken cancellationToken = default) + private protected async Task ConnectAsync(State state, CancellationToken cancellationToken = default) { - return _webSocket.ConnectAsync(Uri, cancellationToken); + var connection = _connectionProvider.CreateConnection(); + var rateLimiter = _rateLimiterProvider.CreateRateLimiter(); + ConnectionState connectionState = new(connection, rateLimiter); + + switch (state.TryIndicateConnecting(connectionState)) + { + case State.ConnectingResult.Success: + break; + case State.ConnectingResult.AlreadyStarted: + connectionState.Dispose(); + ThrowConnectionAlreadyStarted(); + break; + case State.ConnectingResult.Closed: + connectionState.Dispose(); + ThrowConnectionNotStarted(); + break; + } + + try + { + HandleConnecting(); + await connection.OpenAsync(Uri, cancellationToken).ConfigureAwait(false); + HandleConnected(); + } + catch (Exception) + { + state.IndicateConnectingFailed(connectionState); + connectionState.Dispose(); + throw; + } + + _ = ReadAsync(state, connectionState); + + return connectionState; } /// @@ -152,47 +336,263 @@ private protected Task ConnectAsync(CancellationToken cancellationToken = defaul /// public async Task CloseAsync(WebSocketCloseStatus status = WebSocketCloseStatus.NormalClosure, string? statusDescription = null, CancellationToken cancellationToken = default) { - var closedTokenProvider = Interlocked.Exchange(ref _closedTokenProvider, null) ?? throw new InvalidOperationException("Connection not started."); + var state = Interlocked.Exchange(ref _state, null); + + if (state is null) + ThrowConnectionNotStarted(); + + if (!state.TryIndicateClosing(out var connectionState)) + return; + + var connection = connectionState.Connection; + try + { + await connection.CloseAsync((int)status, statusDescription, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (ex is not ArgumentException) + { + InvokeLog(LogMessage.Error(ex)); + try + { + connection.Abort(); + } + catch (Exception abortEx) + { + InvokeLog(LogMessage.Error(abortEx)); + } + } - closedTokenProvider.Cancel(); + connectionState.Dispose(); + state.Dispose(); + HandleClosed(); + } + private async Task ReadAsync(State state, ConnectionState connectionState) + { + var connection = connectionState.Connection; try { - await _webSocket.CloseAsync(status, statusDescription, cancellationToken).ConfigureAwait(false); + using RentedArrayBufferWriter writer = new(DefaultBufferSize); + while (true) + { + var result = await connection.ReceiveAsync(writer.GetMemory()).ConfigureAwait(false); + + if (result.EndOfMessage) + { + if (result.MessageType is WebSocketMessageType.Close) + break; + + writer.Advance(result.Count); + HandleMessageReceived(state, connectionState, writer.WrittenMemory); + writer.Clear(); + } + else + writer.Advance(result.Count); + } } catch { } + + if (state.TryIndicateDisconnecting(connectionState)) + HandleDisconnected(state, connectionState); + } + + public void Abort() + { + var state = Interlocked.Exchange(ref _state, null); + + if (state is null) + return; + + if (!state.TryIndicateClosing(out var connectionState)) + return; + + try + { + connectionState.Connection.Abort(); + } + catch (Exception ex) + { + InvokeLog(LogMessage.Error(ex)); + } + + connectionState.Dispose(); + state.Dispose(); + HandleClosed(); } private protected virtual void OnConnected() { } - private protected ValueTask AbortAndReconnectAsync() + private protected ValueTask AbortAndReconnectAsync(State state, ConnectionState connectionState) { + if (!state.TryIndicateDisconnecting(connectionState)) + return default; + try { - _webSocket.Abort(); + connectionState.Connection.Abort(); } catch (Exception ex) { InvokeLog(LogMessage.Error(ex)); } - return ReconnectAsync(); + connectionState.Dispose(); + HandleClosed(); + + return ReconnectAsync(state); } - public ValueTask SendPayloadAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - => _webSocket.SendAsync(buffer, cancellationToken); + public async ValueTask SendPayloadAsync(ReadOnlyMemory buffer, WebSocketPayloadProperties? properties = null, CancellationToken cancellationToken = default) + { + properties ??= _defaultPayloadProperties; - private protected abstract bool Reconnect(WebSocketCloseStatus? status, string? description); + while (true) + { + var state = _state; + + if (state is null) + ThrowConnectionNotStarted(); - private protected async ValueTask ReconnectAsync() + var task = state.ReadyTask; + + ConnectionState connectionState; + if (task.IsCompleted) + { + if (task.Result is ConnectionStateResult.Success successResult) + connectionState = successResult.ConnectionState; + else + { + ThrowConnectionNotStarted(); + return; + } + } + else + { + if (properties.RetryHandling.HasFlag(WebSocketRetryHandling.RetryReconnect)) + { + var result = await task.WaitAsync(cancellationToken).ConfigureAwait(false); + if (result is ConnectionStateResult.Success successResult) + connectionState = successResult.ConnectionState; + else + continue; + } + else + { + ThrowConnectionNotStarted(); + return; + } + } + + var exception = await TrySendConnectionPayloadAsync(connectionState, buffer, properties, cancellationToken).ConfigureAwait(false); + + if (exception is null) + return; + + if (!properties.RetryHandling.HasFlag(WebSocketRetryHandling.RetryReconnect)) + ThrowConnectionNotStarted(); + } + } + + private protected static async ValueTask SendConnectionPayloadAsync(ConnectionState connectionState, ReadOnlyMemory buffer, WebSocketPayloadProperties properties, CancellationToken cancellationToken = default) { - if (_closedTokenProvider is not { Token: var cancellationToken }) + var exception = await TrySendConnectionPayloadAsync(connectionState, buffer, properties, cancellationToken).ConfigureAwait(false); + if (exception is null) return; + ThrowConnectionNotStarted(exception); + } + + private static async ValueTask TrySendConnectionPayloadAsync(ConnectionState connectionState, ReadOnlyMemory buffer, WebSocketPayloadProperties properties, CancellationToken cancellationToken = default) + { + var rateLimiter = connectionState.RateLimiter; + + var disconnectedToken = connectionState.DisconnectedTokenProvider.Token; + + using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(disconnectedToken, cancellationToken); + var linkedToken = linkedTokenSource.Token; + + while (true) + { + RateLimitAcquisitionResult result; + try + { + result = await rateLimiter.TryAcquireAsync(linkedToken).ConfigureAwait(false); + } + catch (Exception ex) + { + cancellationToken.ThrowIfCancellationRequested(); + + return ex; + } + + if (result.RateLimited) + { + if (properties.RetryHandling.HasFlag(WebSocketRetryHandling.RetryRateLimit)) + { + try + { + await Task.Delay(result.ResetAfter, linkedToken).ConfigureAwait(false); + } + catch (TaskCanceledException ex) + { + cancellationToken.ThrowIfCancellationRequested(); + + return ex; + } + + continue; + } + + ThrowRateLimitTriggered(result.ResetAfter); + } + + var timestamp = Environment.TickCount64; + + try + { + await connectionState.Connection.SendAsync(buffer, properties.MessageType, properties.MessageFlags, linkedToken).ConfigureAwait(false); + } + catch (ArgumentException) + { + try + { + await rateLimiter.CancelAcquireAsync(timestamp, default).ConfigureAwait(false); + } + catch + { + } + + throw; + } + catch (Exception ex) + { + try + { + await rateLimiter.CancelAcquireAsync(timestamp, default).ConfigureAwait(false); + } + catch + { + } + + cancellationToken.ThrowIfCancellationRequested(); + + return ex; + } + + return null; + } + } + + private protected abstract bool Reconnect(WebSocketCloseStatus? status, string? description); + + private protected async ValueTask ReconnectAsync(State state) + { + var cancellationToken = state.ClosedTokenProvider.Token; + foreach (var delay in _reconnectStrategy.GetDelays()) { try @@ -204,9 +604,10 @@ private protected async ValueTask ReconnectAsync() return; } + ConnectionState connectionState; try { - await ConnectAsync(cancellationToken).ConfigureAwait(false); + connectionState = await ConnectAsync(state, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -216,7 +617,7 @@ private protected async ValueTask ReconnectAsync() try { - await TryResumeAsync(cancellationToken).ConfigureAwait(false); + await TryResumeAsync(connectionState, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -227,12 +628,11 @@ private protected async ValueTask ReconnectAsync() } } - private protected abstract ValueTask TryResumeAsync(CancellationToken cancellationToken = default); + private protected abstract ValueTask TryResumeAsync(ConnectionState connectionState, CancellationToken cancellationToken = default); - private protected async void StartHeartbeating(double interval) + private protected async void StartHeartbeating(ConnectionState connectionState, double interval) { - if (_disconnectedTokenProvider is not { Token: var cancellationToken }) - return; + var cancellationToken = connectionState.DisconnectedTokenProvider.Token; PeriodicTimer timer; @@ -253,7 +653,7 @@ private protected async void StartHeartbeating(double interval) try { await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false); - await HeartbeatAsync(cancellationToken).ConfigureAwait(false); + await HeartbeatAsync(connectionState, cancellationToken).ConfigureAwait(false); } catch { @@ -263,11 +663,11 @@ private protected async void StartHeartbeating(double interval) } } - private protected abstract ValueTask HeartbeatAsync(CancellationToken cancellationToken = default); + private protected abstract ValueTask HeartbeatAsync(ConnectionState connectionState, CancellationToken cancellationToken = default); private protected virtual JsonPayload CreatePayload(ReadOnlyMemory payload) => JsonSerializer.Deserialize(payload.Span, Serialization.Default.JsonPayload)!; - private protected abstract Task ProcessPayloadAsync(JsonPayload payload); + private protected abstract Task ProcessPayloadAsync(State state, ConnectionState connectionState, JsonPayload payload); private protected async void InvokeLog(LogMessage logMessage) { @@ -507,6 +907,24 @@ private async ValueTask AwaitEventAsync(ValueTask task) } } + [DoesNotReturn] + private static void ThrowConnectionAlreadyStarted() + { + throw new InvalidOperationException("Connection already started."); + } + + [DoesNotReturn] + private static void ThrowConnectionNotStarted(Exception? innerException = null) + { + throw new InvalidOperationException("Connection not started.", innerException); + } + + [DoesNotReturn] + private static void ThrowRateLimitTriggered(int resetAfter) + { + throw new InvalidOperationException("Rate limit triggered."); + } + public void Dispose() { Dispose(true); @@ -516,10 +934,6 @@ public void Dispose() protected virtual void Dispose(bool disposing) { if (disposing) - { - _webSocket.Dispose(); - _disconnectedTokenProvider?.Dispose(); - _closedTokenProvider?.Dispose(); - } + _state?.Dispose(); } } diff --git a/NetCord/Gateway/WebSocketPayloadProperties.cs b/NetCord/Gateway/WebSocketPayloadProperties.cs new file mode 100644 index 00000000..56e06a03 --- /dev/null +++ b/NetCord/Gateway/WebSocketPayloadProperties.cs @@ -0,0 +1,10 @@ +using NetCord.Gateway.WebSockets; + +namespace NetCord.Gateway; + +public partial record WebSocketPayloadProperties +{ + public WebSocketMessageType MessageType { get; set; } + public WebSocketMessageFlags MessageFlags { get; set; } = WebSocketMessageFlags.EndOfMessage; + public WebSocketRetryHandling RetryHandling { get; set; } = WebSocketRetryHandling.Retry; +} diff --git a/NetCord/Gateway/WebSocketRetryHandling.cs b/NetCord/Gateway/WebSocketRetryHandling.cs new file mode 100644 index 00000000..283993e5 --- /dev/null +++ b/NetCord/Gateway/WebSocketRetryHandling.cs @@ -0,0 +1,10 @@ +namespace NetCord.Gateway; + +[Flags] +public enum WebSocketRetryHandling : byte +{ + NoRetry = 0, + RetryRateLimit = 1 << 0, + RetryReconnect = 1 << 1, + Retry = RetryRateLimit | RetryReconnect, +} diff --git a/NetCord/Gateway/WebSockets/IWebSocket.cs b/NetCord/Gateway/WebSockets/IWebSocket.cs deleted file mode 100644 index 91bb1b9c..00000000 --- a/NetCord/Gateway/WebSockets/IWebSocket.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Net.WebSockets; - -namespace NetCord.Gateway.WebSockets; - -public interface IWebSocket : IDisposable -{ - public event Action? Connecting; - public event Action? Connected; - public event Action? Disconnected; - public event Action? Closed; - public event Action>? MessageReceived; - - /// - /// Connects to a WebSocket server. - /// - public Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default); - - /// - /// Closes the . - /// - public Task CloseAsync(WebSocketCloseStatus status, string? statusDescription, CancellationToken cancellationToken = default); - - /// - /// Aborts the . - /// - public void Abort(); - - /// - /// Sends a message. - /// - public ValueTask SendAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default); -} diff --git a/NetCord/Gateway/WebSockets/IWebSocketConnection.cs b/NetCord/Gateway/WebSockets/IWebSocketConnection.cs new file mode 100644 index 00000000..caaf0508 --- /dev/null +++ b/NetCord/Gateway/WebSockets/IWebSocketConnection.cs @@ -0,0 +1,18 @@ +namespace NetCord.Gateway.WebSockets; + +public interface IWebSocketConnection : IDisposable +{ + public int? CloseStatus { get; } + + public string? CloseStatusDescription { get; } + + public ValueTask OpenAsync(Uri uri, CancellationToken cancellationToken = default); + + public ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, WebSocketMessageFlags messageFlags, CancellationToken cancellationToken = default); + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken = default); + + public ValueTask CloseAsync(int closeStatus, string? closeStatusDescription, CancellationToken cancellationToken = default); + + public void Abort(); +} diff --git a/NetCord/Gateway/WebSockets/IWebSocketConnectionProvider.cs b/NetCord/Gateway/WebSockets/IWebSocketConnectionProvider.cs new file mode 100644 index 00000000..cd80fd1f --- /dev/null +++ b/NetCord/Gateway/WebSockets/IWebSocketConnectionProvider.cs @@ -0,0 +1,6 @@ +namespace NetCord.Gateway.WebSockets; + +public interface IWebSocketConnectionProvider +{ + public IWebSocketConnection CreateConnection(); +} diff --git a/NetCord/Gateway/WebSockets/WebSocket.cs b/NetCord/Gateway/WebSockets/WebSocket.cs deleted file mode 100644 index b03e7269..00000000 --- a/NetCord/Gateway/WebSockets/WebSocket.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Net.WebSockets; - -namespace NetCord.Gateway.WebSockets; - -public sealed class WebSocket : IWebSocket -{ - private const int DefaultBufferSize = 8192; - - private State? _state; - private bool _disposed; - - public event Action? Connecting; - public event Action? Connected; - public event Action? Disconnected; - public event Action? Closed; - public event Action>? MessageReceived; - - public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken = default) - { - ObjectDisposedException.ThrowIf(_disposed, typeof(WebSocket)); - - State newState = new(); - var state = Interlocked.CompareExchange(ref _state, newState, null); - if (state is not null) - { - newState.Dispose(); - ThrowAlreadyConnectingOrConnected(); - } - - InvokeEvent(Connecting); - - try - { - await newState.WebSocket.ConnectAsync(uri, cancellationToken).ConfigureAwait(false); - } - catch - { - Interlocked.Exchange(ref _state, null)?.Dispose(); - throw; - } - - InvokeEvent(Connected); - - newState.StartReading(ReadAsync); - } - - public async Task CloseAsync(WebSocketCloseStatus status, string? statusDescription, CancellationToken cancellationToken = default) - { - var state = Interlocked.Exchange(ref _state, null); - - if (state is null || !state.TryIndicateDisconnecting()) - ThrowNotConnected(); - - var webSocket = state.WebSocket; - - try - { - await webSocket.CloseOutputAsync(status, statusDescription, cancellationToken).ConfigureAwait(false); - } - catch - { - webSocket.Abort(); - InvokeEvent(Closed); - throw; - } - - await state.ReadTask.ConfigureAwait(false); - - InvokeEvent(Closed); - } - - public void Abort() - { - var state = Interlocked.Exchange(ref _state, null); - - if (state is null) - return; - - var disconnecting = state.TryIndicateDisconnecting(); - - state.WebSocket.Abort(); - - if (disconnecting) - InvokeEvent(Closed); - } - - public ValueTask SendAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - var state = _state; - - if (state is null) - ThrowNotConnected(); - - return state.WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken); - } - - private async Task ReadAsync(State state) - { - var webSocket = state.WebSocket; - try - { - using RentedArrayBufferWriter writer = new(DefaultBufferSize); - while (true) - { - var result = await webSocket.ReceiveAsync(writer.GetMemory(), default).ConfigureAwait(false); - - if (result.EndOfMessage) - { - if (result.MessageType is WebSocketMessageType.Close) - break; - - writer.Advance(result.Count); - InvokeEvent(MessageReceived, writer.WrittenMemory); - writer.Clear(); - } - else - writer.Advance(result.Count); - } - } - catch - { - } - - if (state.TryIndicateDisconnecting()) - { - _state = null; - state.Dispose(); - InvokeEvent(Disconnected, webSocket.CloseStatus, webSocket.CloseStatusDescription); - } - } - - private static void InvokeEvent(Action? action) - { - if (action is not null) - { - try - { - action(); - } - catch - { - } - } - } - - private static void InvokeEvent(Action? action, WebSocketCloseStatus? status, string? description) - { - if (action is not null) - { - try - { - action(status, description); - } - catch - { - } - } - } - - private static void InvokeEvent(Action>? action, ReadOnlyMemory buffer) - { - if (action is not null) - { - try - { - action(buffer); - } - catch - { - } - } - } - - public void Dispose() - { - _state?.Dispose(); - _disposed = true; - } - - [DoesNotReturn] - private static void ThrowAlreadyConnectingOrConnected() - { - throw new InvalidOperationException("The WebSocket is already connecting or connected."); - } - - [DoesNotReturn] - private static void ThrowNotConnected() - { - throw new InvalidOperationException("The WebSocket is not connected."); - } - - private sealed class State : IDisposable - { - public ClientWebSocket WebSocket { get; } = new(); - - public Task ReadTask => _readCompletionSource.Task; - - public TaskCompletionSource _readCompletionSource = new(); - - private int _state; - - public async void StartReading(Func readAsync) - { - await readAsync(this).ConfigureAwait(false); - - _readCompletionSource.TrySetResult(); - } - - public bool TryIndicateDisconnecting() - { - return Interlocked.Exchange(ref _state, 1) is 0; - } - - public void Dispose() - { - WebSocket.Dispose(); - } - } -} diff --git a/NetCord/Gateway/WebSockets/WebSocketConnection.cs b/NetCord/Gateway/WebSockets/WebSocketConnection.cs new file mode 100644 index 00000000..9046ff1a --- /dev/null +++ b/NetCord/Gateway/WebSockets/WebSocketConnection.cs @@ -0,0 +1,43 @@ +using System.Net.WebSockets; + +namespace NetCord.Gateway.WebSockets; + +internal sealed class WebSocketConnection : IWebSocketConnection +{ + private readonly ClientWebSocket _webSocket = new(); + + public int? CloseStatus => (int?)_webSocket.CloseStatus; + + public string? CloseStatusDescription => _webSocket.CloseStatusDescription; + + public ValueTask OpenAsync(Uri uri, CancellationToken cancellationToken = default) + { + return new(_webSocket.ConnectAsync(uri, cancellationToken)); + } + + public void Abort() + { + _webSocket.Abort(); + } + + public ValueTask CloseAsync(int closeStatus, string? closeStatusDescription, CancellationToken cancellationToken = default) + { + return new(_webSocket.CloseOutputAsync((WebSocketCloseStatus)closeStatus, closeStatusDescription, cancellationToken)); + } + + public async ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken = default) + { + var result = await _webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); + return new(result.Count, (WebSocketMessageType)result.MessageType, result.EndOfMessage); + } + + public ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, WebSocketMessageFlags messageFlags, CancellationToken cancellationToken = default) + { + return _webSocket.SendAsync(buffer, (System.Net.WebSockets.WebSocketMessageType)messageType, (System.Net.WebSockets.WebSocketMessageFlags)messageFlags, cancellationToken); + } + + public void Dispose() + { + _webSocket.Dispose(); + } +} diff --git a/NetCord/Gateway/WebSockets/WebSocketConnectionProvider.cs b/NetCord/Gateway/WebSockets/WebSocketConnectionProvider.cs new file mode 100644 index 00000000..bc1422e3 --- /dev/null +++ b/NetCord/Gateway/WebSockets/WebSocketConnectionProvider.cs @@ -0,0 +1,9 @@ +namespace NetCord.Gateway.WebSockets; + +public class WebSocketConnectionProvider : IWebSocketConnectionProvider +{ + public IWebSocketConnection CreateConnection() + { + return new WebSocketConnection(); + } +} diff --git a/NetCord/Gateway/WebSockets/WebSocketConnectionReceiveResult.cs b/NetCord/Gateway/WebSockets/WebSocketConnectionReceiveResult.cs new file mode 100644 index 00000000..cc097e23 --- /dev/null +++ b/NetCord/Gateway/WebSockets/WebSocketConnectionReceiveResult.cs @@ -0,0 +1,27 @@ +namespace NetCord.Gateway.WebSockets; + +#pragma warning disable IDE0032 // Use auto property + +// Adopted from System.Net.WebSockets.ValueWebSocketReceiveResult + +public readonly struct WebSocketConnectionReceiveResult +{ + private readonly uint _countAndEndOfMessage; + private readonly WebSocketMessageType _messageType; + + public WebSocketConnectionReceiveResult(int count, WebSocketMessageType messageType, bool endOfMessage) + { + ArgumentOutOfRangeException.ThrowIfNegative(count, nameof(count)); + if ((uint)messageType > (uint)WebSocketMessageType.Close) + ThrowMessageTypeOutOfRange(); + + _countAndEndOfMessage = (uint)count | (uint)(endOfMessage ? 1 << 31 : 0); + _messageType = messageType; + + static void ThrowMessageTypeOutOfRange() => throw new ArgumentOutOfRangeException(nameof(messageType)); + } + + public int Count => (int)(_countAndEndOfMessage & 0x7FFFFFFF); + public bool EndOfMessage => (_countAndEndOfMessage & 0x80000000) == 0x80000000; + public WebSocketMessageType MessageType => _messageType; +} diff --git a/NetCord/Gateway/WebSockets/WebSocketMessageFlags.cs b/NetCord/Gateway/WebSockets/WebSocketMessageFlags.cs new file mode 100644 index 00000000..cfaa63cf --- /dev/null +++ b/NetCord/Gateway/WebSockets/WebSocketMessageFlags.cs @@ -0,0 +1,9 @@ +namespace NetCord.Gateway.WebSockets; + +[Flags] +public enum WebSocketMessageFlags : byte +{ + None = 0, + EndOfMessage = 1 << 0, + DisableCompression = 1 << 1, +} diff --git a/NetCord/Gateway/WebSockets/WebSocketMessageType.cs b/NetCord/Gateway/WebSockets/WebSocketMessageType.cs new file mode 100644 index 00000000..890c77e6 --- /dev/null +++ b/NetCord/Gateway/WebSockets/WebSocketMessageType.cs @@ -0,0 +1,8 @@ +namespace NetCord.Gateway.WebSockets; + +public enum WebSocketMessageType : byte +{ + Text = 0, + Binary = 1, + Close = 2, +} diff --git a/NetCord/GuildEmoji.cs b/NetCord/GuildEmoji.cs index ae4f9b5a..a613a701 100644 --- a/NetCord/GuildEmoji.cs +++ b/NetCord/GuildEmoji.cs @@ -3,71 +3,9 @@ namespace NetCord; -public partial class GuildEmoji : Emoji, ISpanFormattable +public partial class GuildEmoji(JsonEmoji jsonModel, ulong guildId, RestClient client) : CustomEmoji(jsonModel, client) { - private readonly RestClient _client; - - public GuildEmoji(JsonEmoji jsonModel, ulong guildId, RestClient client) : base(jsonModel) - { - _client = client; - - var creator = jsonModel.Creator; - if (creator is not null) - Creator = new(creator, client); - - GuildId = guildId; - } - - public ulong Id => _jsonModel.Id.GetValueOrDefault(); - public IReadOnlyList? AllowedRoles => _jsonModel.AllowedRoles; - public User? Creator { get; } - - public bool? RequireColons => _jsonModel.RequireColons; - - public bool? Managed => _jsonModel.Managed; - - public bool? Available => _jsonModel.Available; - - public ulong GuildId { get; } - - public override string ToString() => Animated ? $"" : $"<:{Name}:{Id}>"; - - public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); - - public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) - { - var name = Name; - if (Animated) - { - if (destination.Length < 6 + name.Length || !Id.TryFormat(destination[(4 + name.Length)..^1], out int length)) - { - charsWritten = 0; - return false; - } - - " _jsonModel.UserCount; + public string? CoverImageHash => _jsonModel.CoverImageHash; + + public GuildScheduledEventRecurrenceRule? RecurrenceRule { get; } + public GuildScheduledEvent(JsonModels.JsonGuildScheduledEvent jsonModel, RestClient client) : base(client) { _jsonModel = jsonModel; @@ -44,5 +48,13 @@ public GuildScheduledEvent(JsonModels.JsonGuildScheduledEvent jsonModel, RestCli var creator = _jsonModel.Creator; if (creator is not null) Creator = new(creator, client); + + var recurrenceRule = _jsonModel.RecurrenceRule; + if (recurrenceRule is not null) + RecurrenceRule = new(recurrenceRule); } + + public bool HasCoverImage => CoverImageHash is not null; + + public ImageUrl? GetCoverImageUrl(ImageFormat format) => _jsonModel.CoverImageHash is string hash ? ImageUrl.GuildScheduledEventCover(Id, hash, format) : null; } diff --git a/NetCord/GuildScheduledEventRecurrenceRule.cs b/NetCord/GuildScheduledEventRecurrenceRule.cs new file mode 100644 index 00000000..bd1e6a5f --- /dev/null +++ b/NetCord/GuildScheduledEventRecurrenceRule.cs @@ -0,0 +1,28 @@ +using NetCord.JsonModels; + +namespace NetCord; + +public class GuildScheduledEventRecurrenceRule(JsonGuildScheduledEventRecurrenceRule jsonModel) : IJsonModel +{ + JsonGuildScheduledEventRecurrenceRule IJsonModel.JsonModel => jsonModel; + + public DateTimeOffset? StartAt => jsonModel.StartAt; + + public DateTimeOffset? EndAt => jsonModel.EndAt; + + public GuildScheduledEventRecurrenceRuleFrequency Frequency => jsonModel.Frequency; + + public int Interval => jsonModel.Interval; + + public GuildScheduledEventRecurrenceRuleWeekday? ByWeekday => jsonModel.ByWeekday; + + public GuildScheduledEventRecurrenceRuleNWeekday ByNWeekday { get; } = new(jsonModel.ByNWeekday); + + public GuildScheduledEventRecurrenceRuleMonth? ByMonth => jsonModel.ByMonth; + + public IReadOnlyList? ByMonthDay => jsonModel.ByMonthDay; + + public IReadOnlyList? ByYearDay => jsonModel.ByYearDay; + + public int? Count => jsonModel.Count; +} diff --git a/NetCord/GuildScheduledEventRecurrenceRuleFrequency.cs b/NetCord/GuildScheduledEventRecurrenceRuleFrequency.cs new file mode 100644 index 00000000..622f04ce --- /dev/null +++ b/NetCord/GuildScheduledEventRecurrenceRuleFrequency.cs @@ -0,0 +1,9 @@ +namespace NetCord; + +public enum GuildScheduledEventRecurrenceRuleFrequency : byte +{ + Yearly = 0, + Monthly = 1, + Weekly = 2, + Daily = 3, +} diff --git a/NetCord/GuildScheduledEventRecurrenceRuleMonth.cs b/NetCord/GuildScheduledEventRecurrenceRuleMonth.cs new file mode 100644 index 00000000..a7e28148 --- /dev/null +++ b/NetCord/GuildScheduledEventRecurrenceRuleMonth.cs @@ -0,0 +1,17 @@ +namespace NetCord; + +public enum GuildScheduledEventRecurrenceRuleMonth +{ + January = 1, + February = 2, + March = 3, + April = 4, + May = 5, + June = 6, + July = 7, + August = 8, + September = 9, + October = 10, + November = 11, + December = 12, +} diff --git a/NetCord/GuildScheduledEventRecurrenceRuleNWeekday.cs b/NetCord/GuildScheduledEventRecurrenceRuleNWeekday.cs new file mode 100644 index 00000000..1f46b515 --- /dev/null +++ b/NetCord/GuildScheduledEventRecurrenceRuleNWeekday.cs @@ -0,0 +1,12 @@ +using NetCord.JsonModels; + +namespace NetCord; + +public class GuildScheduledEventRecurrenceRuleNWeekday(JsonGuildScheduledEventRecurrenceRuleNWeekday jsonModel) : IJsonModel +{ + JsonGuildScheduledEventRecurrenceRuleNWeekday IJsonModel.JsonModel => jsonModel; + + public int N => jsonModel.N; + + public GuildScheduledEventRecurrenceRuleWeekday Day => jsonModel.Day; +} diff --git a/NetCord/GuildScheduledEventRecurrenceRuleWeekday.cs b/NetCord/GuildScheduledEventRecurrenceRuleWeekday.cs new file mode 100644 index 00000000..d3f1e12c --- /dev/null +++ b/NetCord/GuildScheduledEventRecurrenceRuleWeekday.cs @@ -0,0 +1,12 @@ +namespace NetCord; + +public enum GuildScheduledEventRecurrenceRuleWeekday : byte +{ + Monday = 0, + Tuesday = 1, + Wednesday = 2, + Thursday = 3, + Friday = 4, + Saturday = 5, + Sunday = 6, +} diff --git a/NetCord/GuildUser.cs b/NetCord/GuildUser.cs index 5a8a0b4c..082c9ffa 100644 --- a/NetCord/GuildUser.cs +++ b/NetCord/GuildUser.cs @@ -3,17 +3,46 @@ namespace NetCord; +/// +/// Represents a user as a member of the guild indicated by the . +/// public partial class GuildUser(JsonGuildUser jsonModel, ulong guildId, RestClient client) : PartialGuildUser(jsonModel, client) { - public ulong GuildId { get; } = guildId; + /// + /// The ID of the guild the object belongs to. + /// + public ulong GuildId => guildId; - public ImageUrl GetGuildAvatarUrl(ImageFormat? format = null) => ImageUrl.GuildUserAvatar(GuildId, Id, GuildAvatarHash!, format); + /// + /// Gets the of the user's guild avatar. + /// + /// The format of the returned . Defaults to (or for animated avatars). + /// An pointing to the user's guild avatar. If the user does not have one set, returns . + public ImageUrl? GetGuildAvatarUrl(ImageFormat? format = null) => GuildAvatarHash is string hash ? ImageUrl.GuildUserAvatar(guildId, Id, hash, format) : null; + /// + /// Gets the of the user's guild banner. + /// + /// The format of the returned . Defaults to (or for animated banners). + /// An pointing to the user's guild banner. If the user does not have one set, returns . + public ImageUrl? GetGuildBannerUrl(ImageFormat? format = null) => GuildBannerHash is string hash ? ImageUrl.GuildUserBanner(guildId, Id, hash, format) : null; + + /// + /// Applies a timeout to the for a specified . + /// + /// How long to time the out for, specified as the time to wait until. + /// Additional properties to apply to the REST request. + /// A object updated with the new timeout. public Task TimeOutAsync(DateTimeOffset until, RestRequestProperties? properties = null) => ModifyAsync(u => u.TimeOutUntil = until, properties); + /// + /// Returns a object representing the . + /// + /// Additional properties to apply to the REST request. + /// public async Task GetInfoAsync(RestRequestProperties? properties = null) { - await foreach (var info in _client.SearchGuildUsersAsync(GuildId, new() { Limit = 1, AndQuery = [new UserIdsGuildUsersSearchQuery([Id])] }, properties).ConfigureAwait(false)) + await foreach (var info in _client.SearchGuildUsersAsync(guildId, new() { Limit = 1, AndQuery = [new UserIdsGuildUsersSearchQuery([Id])] }, properties).ConfigureAwait(false)) return info; throw new EntityNotFoundException("The user was not found."); diff --git a/NetCord/GuildUserFlags.cs b/NetCord/GuildUserFlags.cs index d292263a..986c1674 100644 --- a/NetCord/GuildUserFlags.cs +++ b/NetCord/GuildUserFlags.cs @@ -1,5 +1,8 @@ namespace NetCord; +/// +/// The flags on a object, mostly undocumented by Discord. +/// [Flags] public enum GuildUserFlags { @@ -14,7 +17,7 @@ public enum GuildUserFlags CompletedOnboarding = 1 << 1, /// - /// User bypasses guild verification requirements. + /// User bypasses guild verification requirements. Allows a user who does not meet verification requirements to participate in a guild. /// BypassesVerification = 1 << 2, @@ -22,4 +25,24 @@ public enum GuildUserFlags /// User has started onboarding. /// StartedOnboarding = 1 << 3, + + /// + /// No longer in use, undocumented. + /// + StartedHomeFlags = 1 << 5, + + /// + /// No longer in use, undocumented. + /// + CompletedHomeFlags = 1 << 6, + + /// + /// Undocumented. + /// + AutomodQuarantinedUsernameOrGuildNickname = 1 << 7, + + /// + /// Undocumented. + /// + AutomodQuarantinedBio = 1 << 8, } diff --git a/NetCord/IInteraction.cs b/NetCord/IInteraction.cs index 64110991..de9890e9 100644 --- a/NetCord/IInteraction.cs +++ b/NetCord/IInteraction.cs @@ -14,7 +14,7 @@ public interface IInteraction : IEntity, ISpanFormattable, IJsonModel Entitlements { get; } - public static IInteraction CreateFromJson(JsonModels.JsonInteraction jsonModel, Func sendResponseAsync, RestClient client) + public static IInteraction CreateFromJson(JsonModels.JsonInteraction jsonModel, Func sendResponseAsync, RestClient client) { if (jsonModel.Type is InteractionType.Ping) return new PingInteraction(jsonModel, sendResponseAsync, client); @@ -22,5 +22,5 @@ public static IInteraction CreateFromJson(JsonModels.JsonInteraction jsonModel, return Interaction.CreateFromJson(jsonModel, null, sendResponseAsync, client); } - public Task SendResponseAsync(InteractionCallback callback, RestRequestProperties? properties = null); + public Task SendResponseAsync(InteractionCallback callback, RestRequestProperties? properties = null, CancellationToken cancellationToken = default); } diff --git a/NetCord/IGuildInvite.cs b/NetCord/IInvite.cs similarity index 79% rename from NetCord/IGuildInvite.cs rename to NetCord/IInvite.cs index b13862c0..41b91a6f 100644 --- a/NetCord/IGuildInvite.cs +++ b/NetCord/IInvite.cs @@ -1,7 +1,8 @@ namespace NetCord; -public interface IGuildInvite +public interface IInvite { + public InviteType Type { get; } public ulong? GuildId { get; } public ulong? ChannelId { get; } public string Code { get; } @@ -10,7 +11,7 @@ public interface IGuildInvite public Application? TargetApplication { get; } public int? MaxAge { get; } public int? MaxUses { get; } - public GuildInviteTargetType? TargetType { get; } + public InviteTargetType? TargetType { get; } public bool? Temporary { get; } public int? Uses { get; } public DateTimeOffset? CreatedAt { get; } diff --git a/NetCord/ImageFormat.cs b/NetCord/ImageFormat.cs index 8558066f..0230ccaf 100644 --- a/NetCord/ImageFormat.cs +++ b/NetCord/ImageFormat.cs @@ -1,6 +1,6 @@ namespace NetCord; -public enum ImageFormat +public enum ImageFormat : byte { Jpeg, Png, diff --git a/NetCord/ImageUrl.cs b/NetCord/ImageUrl.cs index 3ad5a00e..65593d17 100644 --- a/NetCord/ImageUrl.cs +++ b/NetCord/ImageUrl.cs @@ -66,7 +66,8 @@ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan private protected virtual bool IsSizeValid(ReadOnlySpan format) { - for (int i = 0; i < format.Length; i++) + int length = format.Length; + for (int i = 0; i < length; i++) { if (!char.IsAsciiDigit(format[i])) return false; @@ -157,9 +158,9 @@ public static ImageUrl GuildUserAvatar(ulong guildId, ulong userId, string avata return new($"/guilds/{guildId}/users/{userId}/avatars/{avatarHash}", GetExtension(avatarHash, format)); } - public static ImageUrl UserAvatarDecoration(ulong userId, string avatarDecorationHash) + public static ImageUrl AvatarDecoration(string avatarDecorationHash) { - return new($"/avatar-decorations/{userId}/{avatarDecorationHash}", "png"); + return new($"/avatar-decoration-presets/{avatarDecorationHash}", "png"); } public static ImageUrl ApplicationIcon(ulong applicationId, string iconHash, ImageFormat format) @@ -202,14 +203,14 @@ public static ImageUrl Sticker(ulong stickerId, ImageFormat format) return new($"/stickers/{stickerId}", GetFormat(format), Discord.MediaUrl); } - public static ImageUrl RoleIcon(ulong roleId, ImageFormat format) + public static ImageUrl RoleIcon(ulong roleId, string iconHash, ImageFormat format) { - return new($"/role-icons/{roleId}/role_icon", GetFormat(format)); + return new($"/role-icons/{roleId}/{iconHash}", GetFormat(format)); } - public static ImageUrl GuildScheduledEventCover(ulong scheduledEventId, string coverHash, ImageFormat format) + public static ImageUrl GuildScheduledEventCover(ulong scheduledEventId, string coverImageHash, ImageFormat format) { - return new($"/guild-events/{scheduledEventId}/{coverHash}", GetFormat(format)); + return new($"/guild-events/{scheduledEventId}/{coverImageHash}", GetFormat(format)); } public static ImageUrl GuildUserBanner(ulong guildId, ulong userId, string bannerHash, ImageFormat? format) diff --git a/NetCord/IntegrationType.cs b/NetCord/IntegrationType.cs index 6d635f34..ddbde53f 100644 --- a/NetCord/IntegrationType.cs +++ b/NetCord/IntegrationType.cs @@ -2,7 +2,7 @@ namespace NetCord; -[JsonConverter(typeof(JsonConverters.StringEnumConverterWithErrorHandling))] +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum IntegrationType { [JsonPropertyName("twitch")] diff --git a/NetCord/Interaction.cs b/NetCord/Interaction.cs index 01ebdd4e..46212e61 100644 --- a/NetCord/Interaction.cs +++ b/NetCord/Interaction.cs @@ -8,9 +8,9 @@ public abstract partial class Interaction : ClientEntity, IInteraction JsonModels.JsonInteraction IJsonModel.JsonModel => _jsonModel; private readonly JsonModels.JsonInteraction _jsonModel; - private readonly Func _sendResponseAsync; + private readonly Func _sendResponseAsync; - private protected Interaction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(client) + private protected Interaction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(client) { _jsonModel = jsonModel; @@ -20,6 +20,10 @@ private protected Interaction(JsonModels.JsonInteraction jsonModel, Guild? guild else User = new(jsonModel.User!, client); + var guildReference = jsonModel.GuildReference; + if (guildReference is not null) + GuildReference = new(guildReference); + Guild = guild; Channel = TextChannel.CreateFromJson(jsonModel.Channel!, client); Entitlements = jsonModel.Entitlements.Select(e => new Entitlement(e)).ToArray(); @@ -33,6 +37,8 @@ private protected Interaction(JsonModels.JsonInteraction jsonModel, Guild? guild public ulong? GuildId => _jsonModel.GuildId; + public InteractionGuildReference? GuildReference { get; } + public Guild? Guild { get; } public TextChannel Channel { get; } @@ -55,7 +61,7 @@ private protected Interaction(JsonModels.JsonInteraction jsonModel, Guild? guild public abstract InteractionData Data { get; } - public static Interaction CreateFromJson(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) + public static Interaction CreateFromJson(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) { return jsonModel.Type switch { @@ -86,8 +92,8 @@ public static Interaction CreateFromJson(JsonModels.JsonInteraction jsonModel, I { var guildId = jsonModel.GuildId; var guild = guildId.HasValue ? cache.Guilds.GetValueOrDefault(guildId.GetValueOrDefault()) : null; - return CreateFromJson(jsonModel, guild, (interaction, interactionCallback, properties) => client.SendInteractionResponseAsync(interaction.Id, interaction.Token, interactionCallback, properties), client); + return CreateFromJson(jsonModel, guild, (interaction, interactionCallback, properties, cancellationToken) => client.SendInteractionResponseAsync(interaction.Id, interaction.Token, interactionCallback, properties, cancellationToken), client); } - public Task SendResponseAsync(InteractionCallback callback, RestRequestProperties? properties = null) => _sendResponseAsync(this, callback, properties); + public Task SendResponseAsync(InteractionCallback callback, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => _sendResponseAsync(this, callback, properties, cancellationToken); } diff --git a/NetCord/InteractionGuildReference.cs b/NetCord/InteractionGuildReference.cs new file mode 100644 index 00000000..f76c8d64 --- /dev/null +++ b/NetCord/InteractionGuildReference.cs @@ -0,0 +1,14 @@ +using NetCord.JsonModels; + +namespace NetCord; + +public class InteractionGuildReference(JsonInteractionGuildReference jsonModel) : Entity, IJsonModel +{ + JsonInteractionGuildReference IJsonModel.JsonModel => jsonModel; + + public override ulong Id => jsonModel.Id; + + public string[] Features => jsonModel.Features; + + public string Locale => jsonModel.Locale; +} diff --git a/NetCord/InteractionResolvedData.cs b/NetCord/InteractionResolvedData.cs index 9bf3fe60..2d373cc9 100644 --- a/NetCord/InteractionResolvedData.cs +++ b/NetCord/InteractionResolvedData.cs @@ -1,4 +1,5 @@ -using NetCord.Rest; +using NetCord.JsonModels; +using NetCord.Rest; namespace NetCord; @@ -12,7 +13,7 @@ public class InteractionResolvedData public IReadOnlyDictionary? Attachments { get; } - public InteractionResolvedData(JsonModels.JsonInteractionResolvedData jsonModel, ulong? guildId, RestClient client) + public InteractionResolvedData(JsonInteractionResolvedData jsonModel, ulong? guildId, RestClient client) { var users = jsonModel.Users; if (users is not null) diff --git a/NetCord/GuildInviteTargetType.cs b/NetCord/InviteTargetType.cs similarity index 67% rename from NetCord/GuildInviteTargetType.cs rename to NetCord/InviteTargetType.cs index c53465ef..f1d6fb60 100644 --- a/NetCord/GuildInviteTargetType.cs +++ b/NetCord/InviteTargetType.cs @@ -1,6 +1,6 @@ namespace NetCord; -public enum GuildInviteTargetType +public enum InviteTargetType { Stream = 1, EmbeddedApplication = 2, diff --git a/NetCord/InviteType.cs b/NetCord/InviteType.cs new file mode 100644 index 00000000..c4365f46 --- /dev/null +++ b/NetCord/InviteType.cs @@ -0,0 +1,8 @@ +namespace NetCord; + +public enum InviteType : byte +{ + Guild = 0, + GroupDMChannel = 1, + Friend = 2, +} diff --git a/NetCord/JsonConverters/AttachmentPropertiesIEnumerableConverter.cs b/NetCord/JsonConverters/AttachmentPropertiesIEnumerableConverter.cs index f75040e4..342df3be 100644 --- a/NetCord/JsonConverters/AttachmentPropertiesIEnumerableConverter.cs +++ b/NetCord/JsonConverters/AttachmentPropertiesIEnumerableConverter.cs @@ -9,6 +9,7 @@ public class AttachmentPropertiesIEnumerableConverter : JsonConverter : JsonConverter where T : struct, Enum +public class SafeStringEnumConverter : JsonConverter where T : struct, Enum { - private static readonly JsonEncodedText _unknownName = JsonEncodedText.Encode(default(ReadOnlySpan)); private static readonly T _unknownValue = (T)(object)-1; - private readonly Dictionary, T> _namesDictionary; - private readonly Dictionary _valuesDictionary; + private readonly FrozenDictionary, T> _namesDictionary; + private readonly FrozenDictionary _valuesDictionary; [UnconditionalSuppressMessage("Trimming", "IL2090:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The generic parameter of the source method or type does not have matching annotations.", Justification = "Literal fields on enums can never be trimmed")] - public StringEnumConverterWithErrorHandling() + public SafeStringEnumConverter() { var enumType = typeof(T); var fields = enumType.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); int length = fields.Length; - Dictionary, T> namesDictionary = new(length, OrdinalReadOnlyMemoryByteComparer.Instance); - Dictionary valuesDictionary = new(length); + var names = new KeyValuePair, T>[length]; + var values = new KeyValuePair[length]; for (var i = 0; i < length; i++) { @@ -37,12 +37,12 @@ public StringEnumConverterWithErrorHandling() var rawValue = field.GetRawConstantValue()!; var value = (T)rawValue; - namesDictionary.Add(nameBytes, value); - valuesDictionary.Add(value, JsonEncodedText.Encode(nameBytes)); + names[i] = new(nameBytes, value); + values[i] = new(value, JsonEncodedText.Encode(nameBytes)); } - _namesDictionary = namesDictionary; - _valuesDictionary = valuesDictionary; + _namesDictionary = names.ToFrozenDictionary(SafeStringEnumConverter.OrdinalReadOnlyMemoryByteComparer.Instance); + _valuesDictionary = values.ToFrozenDictionary(); } public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) @@ -61,15 +61,20 @@ public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConve public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { - writer.WriteStringValue(_valuesDictionary.TryGetValue(value, out var name) ? name : _unknownName); + writer.WriteStringValue(_valuesDictionary.TryGetValue(value, out var name) ? name : SafeStringEnumConverter._unknownName); } public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { - writer.WritePropertyName(_valuesDictionary.TryGetValue(value, out var name) ? name : _unknownName); + writer.WritePropertyName(_valuesDictionary.TryGetValue(value, out var name) ? name : SafeStringEnumConverter._unknownName); } +} + +static file class SafeStringEnumConverter +{ + internal static readonly JsonEncodedText _unknownName = JsonEncodedText.Encode(default(ReadOnlySpan)); - private class OrdinalReadOnlyMemoryByteComparer : IComparer>, IEqualityComparer> + internal class OrdinalReadOnlyMemoryByteComparer : IComparer>, IEqualityComparer> { public static OrdinalReadOnlyMemoryByteComparer Instance { get; } = new(); diff --git a/NetCord/JsonModels/JsonAttachment.cs b/NetCord/JsonModels/JsonAttachment.cs index 68a7ce74..c689dfce 100644 --- a/NetCord/JsonModels/JsonAttachment.cs +++ b/NetCord/JsonModels/JsonAttachment.cs @@ -8,6 +8,9 @@ public class JsonAttachment : JsonEntity [JsonPropertyName("filename")] public string FileName { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } + [JsonPropertyName("description")] public string? Description { get; set; } diff --git a/NetCord/JsonModels/JsonAvatarDecorationData.cs b/NetCord/JsonModels/JsonAvatarDecorationData.cs new file mode 100644 index 00000000..043a2141 --- /dev/null +++ b/NetCord/JsonModels/JsonAvatarDecorationData.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace NetCord.JsonModels; + +public class JsonAvatarDecorationData +{ + [JsonPropertyName("asset")] + public string Hash { get; set; } + + [JsonPropertyName("sku_id")] + public ulong SkuId { get; set; } +} diff --git a/NetCord/JsonModels/JsonComponent.cs b/NetCord/JsonModels/JsonComponent.cs index 515c2b49..670e7643 100644 --- a/NetCord/JsonModels/JsonComponent.cs +++ b/NetCord/JsonModels/JsonComponent.cs @@ -25,6 +25,9 @@ public class JsonComponent [JsonPropertyName("url")] public string? Url { get; set; } + [JsonPropertyName("sku_id")] + public ulong? SkuId { get; set; } + [JsonPropertyName("options")] public JsonMenuSelectOption[] Options { get; set; } diff --git a/NetCord/JsonModels/JsonGuildScheduledEvent.cs b/NetCord/JsonModels/JsonGuildScheduledEvent.cs index cac46759..5a90cc1e 100644 --- a/NetCord/JsonModels/JsonGuildScheduledEvent.cs +++ b/NetCord/JsonModels/JsonGuildScheduledEvent.cs @@ -45,4 +45,10 @@ public class JsonGuildScheduledEvent : JsonEntity [JsonPropertyName("user_count")] public int? UserCount { get; set; } + + [JsonPropertyName("image")] + public string? CoverImageHash { get; set; } + + [JsonPropertyName("recurrence_rule")] + public JsonGuildScheduledEventRecurrenceRule? RecurrenceRule { get; set; } } diff --git a/NetCord/JsonModels/JsonGuildScheduledEventRecurrenceRule.cs b/NetCord/JsonModels/JsonGuildScheduledEventRecurrenceRule.cs new file mode 100644 index 00000000..26f373b8 --- /dev/null +++ b/NetCord/JsonModels/JsonGuildScheduledEventRecurrenceRule.cs @@ -0,0 +1,36 @@ +using System.Text.Json.Serialization; + +namespace NetCord.JsonModels; + +public class JsonGuildScheduledEventRecurrenceRule +{ + [JsonPropertyName("start")] + public DateTimeOffset? StartAt { get; set; } + + [JsonPropertyName("end")] + public DateTimeOffset? EndAt { get; set; } + + [JsonPropertyName("frequency")] + public GuildScheduledEventRecurrenceRuleFrequency Frequency { get; set; } + + [JsonPropertyName("interval")] + public int Interval { get; set; } + + [JsonPropertyName("by_weekday")] + public GuildScheduledEventRecurrenceRuleWeekday? ByWeekday { get; set; } + + [JsonPropertyName("by_n_weekday")] + public JsonGuildScheduledEventRecurrenceRuleNWeekday ByNWeekday { get; set; } + + [JsonPropertyName("by_month")] + public GuildScheduledEventRecurrenceRuleMonth? ByMonth { get; set; } + + [JsonPropertyName("by_month_day")] + public int[]? ByMonthDay { get; set; } + + [JsonPropertyName("by_year_day")] + public int[]? ByYearDay { get; set; } + + [JsonPropertyName("count")] + public int? Count { get; } +} diff --git a/NetCord/JsonModels/JsonGuildScheduledEventRecurrenceRuleNWeekday.cs b/NetCord/JsonModels/JsonGuildScheduledEventRecurrenceRuleNWeekday.cs new file mode 100644 index 00000000..74f33d04 --- /dev/null +++ b/NetCord/JsonModels/JsonGuildScheduledEventRecurrenceRuleNWeekday.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace NetCord.JsonModels; + +public class JsonGuildScheduledEventRecurrenceRuleNWeekday +{ + [JsonPropertyName("n")] + public int N { get; set; } + + [JsonPropertyName("day")] + public GuildScheduledEventRecurrenceRuleWeekday Day { get; set; } +} diff --git a/NetCord/JsonModels/JsonGuildUser.cs b/NetCord/JsonModels/JsonGuildUser.cs index 07351745..485492dc 100644 --- a/NetCord/JsonModels/JsonGuildUser.cs +++ b/NetCord/JsonModels/JsonGuildUser.cs @@ -13,6 +13,9 @@ public class JsonGuildUser [JsonPropertyName("avatar")] public string? GuildAvatarHash { get; set; } + [JsonPropertyName("banner")] + public string? GuildBannerHash { get; set; } + [JsonPropertyName("roles")] public ulong[] RoleIds { get; set; } @@ -42,4 +45,7 @@ public class JsonGuildUser [JsonPropertyName("communication_disabled_until")] public DateTimeOffset? TimeOutUntil { get; set; } + + [JsonPropertyName("avatar_decoration_data")] + public JsonAvatarDecorationData? GuildAvatarDecorationData { get; set; } } diff --git a/NetCord/JsonModels/JsonInteraction.cs b/NetCord/JsonModels/JsonInteraction.cs index 9cd0a632..773ea0f2 100644 --- a/NetCord/JsonModels/JsonInteraction.cs +++ b/NetCord/JsonModels/JsonInteraction.cs @@ -16,6 +16,9 @@ public class JsonInteraction : JsonEntity [JsonPropertyName("guild_id")] public ulong? GuildId { get; set; } + [JsonPropertyName("guild")] + public JsonInteractionGuildReference? GuildReference { get; set; } + [JsonPropertyName("channel")] public JsonChannel? Channel { get; set; } diff --git a/NetCord/JsonModels/JsonInteractionGuildReference.cs b/NetCord/JsonModels/JsonInteractionGuildReference.cs new file mode 100644 index 00000000..f1c1b7a3 --- /dev/null +++ b/NetCord/JsonModels/JsonInteractionGuildReference.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace NetCord.JsonModels; + +public class JsonInteractionGuildReference : JsonEntity +{ + [JsonPropertyName("features")] + public string[] Features { get; set; } + + [JsonPropertyName("locale")] + public string Locale { get; set; } +} diff --git a/NetCord/JsonModels/JsonInteractionResolvedData.cs b/NetCord/JsonModels/JsonInteractionResolvedData.cs index e7cb4a53..cef21c6f 100644 --- a/NetCord/JsonModels/JsonInteractionResolvedData.cs +++ b/NetCord/JsonModels/JsonInteractionResolvedData.cs @@ -5,20 +5,20 @@ namespace NetCord.JsonModels; public class JsonInteractionResolvedData { [JsonPropertyName("users")] - public Dictionary? Users { get; set; } + public IReadOnlyDictionary? Users { get; set; } [JsonPropertyName("members")] - public Dictionary? GuildUsers { get; set; } + public IReadOnlyDictionary? GuildUsers { get; set; } [JsonPropertyName("roles")] - public Dictionary? Roles { get; set; } + public IReadOnlyDictionary? Roles { get; set; } [JsonPropertyName("channels")] - public Dictionary? Channels { get; set; } + public IReadOnlyDictionary? Channels { get; set; } [JsonPropertyName("messages")] - public Dictionary? Messages { get; set; } + public IReadOnlyDictionary? Messages { get; set; } [JsonPropertyName("attachments")] - public Dictionary? Attachments { get; set; } + public IReadOnlyDictionary? Attachments { get; set; } } diff --git a/NetCord/JsonModels/JsonMessage.cs b/NetCord/JsonModels/JsonMessage.cs index ca1935e8..5b4d8262 100644 --- a/NetCord/JsonModels/JsonMessage.cs +++ b/NetCord/JsonModels/JsonMessage.cs @@ -65,11 +65,14 @@ public class JsonMessage : JsonEntity [JsonPropertyName("application_id")] public ulong? ApplicationId { get; set; } + [JsonPropertyName("flags")] + public MessageFlags? Flags { get; set; } + [JsonPropertyName("message_reference")] public JsonMessageReference? MessageReference { get; set; } - [JsonPropertyName("flags")] - public MessageFlags? Flags { get; set; } + [JsonPropertyName("message_snapshots")] + public JsonMessageSnapshot[]? MessageSnapshots { get; set; } [JsonPropertyName("referenced_message")] public JsonMessage? ReferencedMessage { get; set; } @@ -107,4 +110,7 @@ public class JsonMessage : JsonEntity [JsonPropertyName("poll")] public JsonMessagePoll? Poll { get; set; } + + [JsonPropertyName("call")] + public JsonMessageCall? Call { get; set; } } diff --git a/NetCord/JsonModels/JsonMessageCall.cs b/NetCord/JsonModels/JsonMessageCall.cs new file mode 100644 index 00000000..53555571 --- /dev/null +++ b/NetCord/JsonModels/JsonMessageCall.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace NetCord.JsonModels; + +public class JsonMessageCall +{ + [JsonPropertyName("participants")] + public ulong[] Participants { get; set; } + + [JsonPropertyName("ended_timestamp")] + public DateTimeOffset? EndedAt { get; set; } +} diff --git a/NetCord/JsonModels/JsonMessageSnapshot.cs b/NetCord/JsonModels/JsonMessageSnapshot.cs new file mode 100644 index 00000000..6273b843 --- /dev/null +++ b/NetCord/JsonModels/JsonMessageSnapshot.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace NetCord.JsonModels; + +public class JsonMessageSnapshot +{ + [JsonPropertyName("message")] + public JsonMessageSnapshotMessage Message { get; set; } +} diff --git a/NetCord/JsonModels/JsonMessageSnapshotMessage.cs b/NetCord/JsonModels/JsonMessageSnapshotMessage.cs new file mode 100644 index 00000000..737a25fd --- /dev/null +++ b/NetCord/JsonModels/JsonMessageSnapshotMessage.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace NetCord.JsonModels; + +public class JsonMessageSnapshotMessage +{ + [JsonPropertyName("type")] + public MessageType Type { get; set; } + + [JsonPropertyName("content")] + public string Content { get; set; } + + [JsonPropertyName("embeds")] + public JsonEmbed[] Embeds { get; set; } + + [JsonPropertyName("attachments")] + public JsonAttachment[] Attachments { get; set; } + + [JsonPropertyName("timestamp")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonPropertyName("edited_timestamp")] + public DateTimeOffset? EditedAt { get; set; } + + [JsonPropertyName("flags")] + public MessageFlags? Flags { get; set; } + + [JsonPropertyName("mentions")] + public JsonUser[] MentionedUsers { get; set; } + + [JsonPropertyName("mention_roles")] + public ulong[] MentionedRoleIds { get; set; } +} diff --git a/NetCord/JsonModels/JsonSelectMenuDefaultValueType.cs b/NetCord/JsonModels/JsonSelectMenuDefaultValueType.cs index 371e1bd4..992b0835 100644 --- a/NetCord/JsonModels/JsonSelectMenuDefaultValueType.cs +++ b/NetCord/JsonModels/JsonSelectMenuDefaultValueType.cs @@ -4,7 +4,7 @@ namespace NetCord.JsonModels; -[JsonConverter(typeof(StringEnumConverterWithErrorHandling))] +[JsonConverter(typeof(SafeStringEnumConverter))] public enum JsonSelectMenuDefaultValueType { [JsonPropertyName("user")] diff --git a/NetCord/JsonModels/JsonUser.cs b/NetCord/JsonModels/JsonUser.cs index 2c96a529..f2ac4940 100644 --- a/NetCord/JsonModels/JsonUser.cs +++ b/NetCord/JsonModels/JsonUser.cs @@ -50,8 +50,8 @@ public class JsonUser : JsonEntity [JsonPropertyName("public_flags")] public UserFlags? PublicFlags { get; set; } - [JsonPropertyName("avatar_decoration")] - public string? AvatarDecorationHash { get; set; } + [JsonPropertyName("avatar_decoration_data")] + public JsonAvatarDecorationData? AvatarDecorationData { get; set; } [JsonPropertyName("member")] public JsonGuildUser? GuildUser { get; set; } diff --git a/NetCord/MentionableMenuInteraction.cs b/NetCord/MentionableMenuInteraction.cs index 86a3f4e6..c4ce05ee 100644 --- a/NetCord/MentionableMenuInteraction.cs +++ b/NetCord/MentionableMenuInteraction.cs @@ -3,6 +3,6 @@ namespace NetCord; -public class MentionableMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) +public class MentionableMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) { } diff --git a/NetCord/MessageCommandInteraction.cs b/NetCord/MessageCommandInteraction.cs index 821f5e9b..c208b677 100644 --- a/NetCord/MessageCommandInteraction.cs +++ b/NetCord/MessageCommandInteraction.cs @@ -3,7 +3,7 @@ namespace NetCord; -public class MessageCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : ApplicationCommandInteraction(jsonModel, guild, sendResponseAsync, client) +public class MessageCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : ApplicationCommandInteraction(jsonModel, guild, sendResponseAsync, client) { public override MessageCommandInteractionData Data { get; } = new(jsonModel.Data!, client); } diff --git a/NetCord/MessageComponentInteraction.cs b/NetCord/MessageComponentInteraction.cs index 7afee4b7..2c2d4e33 100644 --- a/NetCord/MessageComponentInteraction.cs +++ b/NetCord/MessageComponentInteraction.cs @@ -6,7 +6,7 @@ namespace NetCord; public abstract class MessageComponentInteraction : ComponentInteraction { - private protected MessageComponentInteraction(JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) + private protected MessageComponentInteraction(JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) { var message = jsonModel.Message!; message.GuildId = jsonModel.GuildId; diff --git a/NetCord/MessageFlags.cs b/NetCord/MessageFlags.cs index 5c2dd7c3..fad9aa07 100644 --- a/NetCord/MessageFlags.cs +++ b/NetCord/MessageFlags.cs @@ -29,7 +29,7 @@ public enum MessageFlags : uint Urgent = 1 << 4, /// - /// This message has an associated thread, with the same id as the message. + /// This message has an associated thread, with the same ID as the message. /// HasThread = 1 << 5, diff --git a/NetCord/MessagePollProperties.cs b/NetCord/MessagePollProperties.cs index b7f19af9..f46226ff 100644 --- a/NetCord/MessagePollProperties.cs +++ b/NetCord/MessagePollProperties.cs @@ -2,21 +2,43 @@ namespace NetCord; -public partial class MessagePollProperties(MessagePollMediaProperties question, IEnumerable answers, int durationInHours) +/// +/// +/// +/// The question of the poll. +/// Each of the answers available in the poll, up to 10. +public partial class MessagePollProperties(MessagePollMediaProperties question, IEnumerable answers) { + /// + /// The question of the poll. + /// [JsonPropertyName("question")] public MessagePollMediaProperties Question { get; set; } = question; + /// + /// Each of the answers available in the poll, up to 10. + /// [JsonPropertyName("answers")] public IEnumerable Answers { get; set; } = answers; + /// + /// Number of hours the poll should be open for, up to 32 days. Defaults to 24. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("duration")] - public int DurationInHours { get; set; } = durationInHours; + public int? DurationInHours { get; set; } + /// + /// Whether a user can select multiple answers. Defaults to . + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("allow_multiselect")] public bool AllowMultiselect { get; set; } - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + /// + /// The layout of the poll. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("layout_type")] public MessagePollLayoutType? LayoutType { get; set; } } diff --git a/NetCord/MessageReferenceType.cs b/NetCord/MessageReferenceType.cs new file mode 100644 index 00000000..327bee75 --- /dev/null +++ b/NetCord/MessageReferenceType.cs @@ -0,0 +1,7 @@ +namespace NetCord; + +public enum MessageReferenceType : byte +{ + Reply = 0, + Forward = 1, +} diff --git a/NetCord/MessageSnapshot.cs b/NetCord/MessageSnapshot.cs new file mode 100644 index 00000000..83f696de --- /dev/null +++ b/NetCord/MessageSnapshot.cs @@ -0,0 +1,11 @@ +using NetCord.JsonModels; +using NetCord.Rest; + +namespace NetCord; + +public class MessageSnapshot(JsonMessageSnapshot jsonModel, ulong? guildId, RestClient client) : IJsonModel +{ + JsonMessageSnapshot IJsonModel.JsonModel => jsonModel; + + public MessageSnapshotMessage Message { get; } = new(jsonModel.Message, guildId, client); +} diff --git a/NetCord/MessageSnapshotMessage.cs b/NetCord/MessageSnapshotMessage.cs new file mode 100644 index 00000000..c090a407 --- /dev/null +++ b/NetCord/MessageSnapshotMessage.cs @@ -0,0 +1,57 @@ +using NetCord.JsonModels; +using NetCord.Rest; + +namespace NetCord; + +public class MessageSnapshotMessage(JsonMessageSnapshotMessage jsonModel, ulong? guildId, RestClient client) : IJsonModel +{ + JsonMessageSnapshotMessage IJsonModel.JsonModel => jsonModel; + + /// + /// The type of the message. + /// + public MessageType Type => jsonModel.Type; + + /// + /// The text contents of the message. + /// + public string Content => jsonModel.Content; + + /// + /// A list of objects containing any embedded content present in the message. + /// + public IReadOnlyList Embeds { get; } = jsonModel.Embeds.Select(e => new Embed(e)).ToArray(); + + /// + /// A list of objects indexed by their IDs, containing any files attached in the message. + /// + public IReadOnlyList Attachments { get; } = jsonModel.Attachments.Select(Attachment.CreateFromJson).ToArray(); + + /// + /// When the message was edited (or null if never). + /// + public DateTimeOffset? EditedAt => jsonModel.EditedAt; + + /// + /// A object indicating the message's applied flags. + /// + public MessageFlags Flags => jsonModel.Flags.GetValueOrDefault(); + + /// + /// A list of objects indexed by their IDs, containing users specifically mentioned in the message. + /// + public IReadOnlyList MentionedUsers { get; } = jsonModel.MentionedUsers.Select(u => + { + var guildUser = u.GuildUser; + if (guildUser is null) + return new User(u, client); + + guildUser.User = u; + return new GuildUser(guildUser, guildId.GetValueOrDefault(), client); + }).ToArray(); + + /// + /// A list of IDs corresponding to roles specifically mentioned in the message. + /// + public IReadOnlyList MentionedRoleIds => jsonModel.MentionedRoleIds; +} diff --git a/NetCord/MessageType.cs b/NetCord/MessageType.cs index 23f66083..791c8881 100644 --- a/NetCord/MessageType.cs +++ b/NetCord/MessageType.cs @@ -2,42 +2,188 @@ public enum MessageType { + /// + /// The default message type. + /// Default = 0, + + /// + /// Sent when a recipient is added. + /// RecipientAdd = 1, + + /// + /// Sent when a recipient is removed. + /// RecipientRemove = 2, + + /// + /// Sent when a user is called. + /// Call = 3, + /// - /// Group dm only + /// Sent when a Group DM's name is changed. /// NameChange = 4, + /// - /// Group dm only + /// Sent when a Group DM's icon is changed. /// IconChange = 5, + + /// + /// Sent when a message is pinned to a channel. + /// MessagePinned = 6, + + /// + /// Sent when a member joins a guild. + /// GuildMembedJoined = 7, + + /// + /// Sent when a user boosts a guild. + /// PremiumGuildSubscription = 8, + + /// + /// Send when a guild reaches boost level 1. + /// PremiumGuildSubscriptionTier1 = 9, + + /// + /// Send when a guild reaches boost level 2. + /// PremiumGuildSubscriptionTier2 = 10, + + /// + /// Send when a guild reaches boost level 3. + /// PremiumGuildSubscriptionTier3 = 11, + + /// + /// Sent when a channel subscription is added to another channel. + /// ChannelFollowAdd = 12, + + /// + /// Sent when a guild is disqualified from Server Discovery. + /// GuildDiscoveryDisqualified = 14, + + /// + /// Sent when a guild is requalified for Server Discovery. + /// GuildDiscoveryRequalified = 15, + + /// + /// Sent as intial warning during the grace period for Server Discovery disqualification. + /// GuildDiscoveryGracePeriodInitialWarning = 16, + + /// + /// Sent as final warning during the grace period for Server Discovery disqualification. + /// GuildDiscoveryGracePeriodFinalWarning = 17, + + /// + /// Sent when a thread is created. + /// ThreadCreated = 18, + + /// + /// Sent when a message is a reply to another message. + /// Reply = 19, + + /// + /// Sent when an application command is triggered. + /// ApplicationCommand = 20, + + /// + /// Sent as the initial message in a thread. + /// ThreadStarterMessage = 21, + + /// + /// Sent as a reminder to invite other users to a guild. + /// GuildInviteReminder = 22, + + /// + /// Sent when a context menu command is triggered. + /// ContextMenuCommand = 23, + + /// + /// Sent when an automod action is taken. + /// AutoModerationAction = 24, + + /// + /// Sent when a subscription to a role is purchased. + /// RoleSubscriptionPurchase = 25, + + /// + /// Sent when a premium upsell for an interaction is triggered. + /// InteractionPremiumUpsell = 26, + + /// + /// Sent when a stage is started. + /// StageStart = 27, + + /// + /// Sent when a stage ends. + /// StageEnd = 28, + + /// + /// Sent when a user joins a stage as a speaker. + /// StageSpeaker = 29, + + /// + /// Sent when a user raises their hand in a stage. + /// StageRaiseHand = 30, + + /// + /// Sent alongside . + /// StageTopic = 31, + + /// + /// Sent for premium subscriptions to a guild application. + /// GuildApplicationPremiumSubscription = 32, + + /// + /// Sent when incident alert mode is enabled in a guild. + /// + IncidentAlertModeEnabled = 36, + + /// + /// Sent when incident alert mode is disabled in a guild. + /// + IncidentAlertModeDisabled = 37, + + /// + /// Sent when a raid incident is reported in a guild. + /// + IncidentReportRaid = 38, + + /// + /// Sent when a false alarm incident is reported in a guild. + /// + IncidentReportFalseAlarm = 39, + + /// + /// Sent when a purchase is made in a guild. + /// + PurchaseNotification = 44 } diff --git a/NetCord/ModalInteraction.cs b/NetCord/ModalInteraction.cs index 868d31f1..deb5c161 100644 --- a/NetCord/ModalInteraction.cs +++ b/NetCord/ModalInteraction.cs @@ -5,7 +5,7 @@ namespace NetCord; public class ModalInteraction : ComponentInteraction { - public ModalInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) + public ModalInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : base(jsonModel, guild, sendResponseAsync, client) { var message = jsonModel.Message; if (message is not null) diff --git a/NetCord/NetCord.csproj b/NetCord/NetCord.csproj index 6fdae875..193b5360 100644 --- a/NetCord/NetCord.csproj +++ b/NetCord/NetCord.csproj @@ -14,7 +14,7 @@ SmallSquare.png MIT $(VersionPrefix) - alpha.301 + alpha.316 The modern and fully customizable C# Discord library. true diff --git a/NetCord/PartialGuildUser.cs b/NetCord/PartialGuildUser.cs index 554debe5..a62dd04c 100644 --- a/NetCord/PartialGuildUser.cs +++ b/NetCord/PartialGuildUser.cs @@ -3,22 +3,106 @@ namespace NetCord; -public class PartialGuildUser(JsonGuildUser jsonModel, RestClient client) : User(jsonModel.User, client), IJsonModel +/// +/// Represents a object that lacks a field, as well as methods relying on it. +/// +public class PartialGuildUser : User, IJsonModel { JsonGuildUser IJsonModel.JsonModel => _jsonModel; - private protected new readonly JsonGuildUser _jsonModel = jsonModel; + private protected new readonly JsonGuildUser _jsonModel; + public PartialGuildUser(JsonGuildUser jsonModel, RestClient client) : base(jsonModel.User, client) + { + _jsonModel = jsonModel; + + var guildAvatarDecorationData = jsonModel.GuildAvatarDecorationData; + if (guildAvatarDecorationData is not null) + GuildAvatarDecorationData = new(guildAvatarDecorationData); + } + + /// + /// The user's guild nickname. + /// public string? Nickname => _jsonModel.Nickname; + + /// + /// The user's guild avatar hash. + /// public string? GuildAvatarHash => _jsonModel.GuildAvatarHash; + + /// + /// The user's guild banner hash. + /// + public string? GuildBannerHash => _jsonModel.GuildBannerHash; + + /// + /// A list of IDs representing the user's current roles. + /// public IReadOnlyList RoleIds => _jsonModel.RoleIds; + + /// + /// The ID of the user's hoisted role, used to categorize the user in the member list. + /// public ulong? HoistedRoleId => _jsonModel.HoistedRoleId; + + /// + /// When the user joined the guild. + /// public DateTimeOffset JoinedAt => _jsonModel.JoinedAt; + + /// + /// When the user started boosting the guild. if the user has never boosted. + /// public DateTimeOffset? GuildBoostStart => _jsonModel.GuildBoostStart; + + /// + /// Whether the user is deafened in voice channels. + /// public bool Deafened => _jsonModel.Deafened; + + /// + /// Whether the user is muted in voice channels. + /// public bool Muted => _jsonModel.Muted; + + /// + /// The user's current . + /// public GuildUserFlags GuildFlags => _jsonModel.GuildFlags; + + /// + /// Whether the user has passed the guild's Membership Screening requirements. + /// public bool? IsPending => _jsonModel.IsPending; + + /// + /// When the user's current timeout will expire, allowing them to communicate in the guild again. or a time in the past if the user is not currently timed out. + /// public DateTimeOffset? TimeOutUntil => _jsonModel.TimeOutUntil; + /// + /// Data for the guild user's avatar decoration. + /// + public AvatarDecorationData? GuildAvatarDecorationData { get; } + + /// + /// Whether the user has a guild avatar set. + /// public bool HasGuildAvatar => GuildAvatarHash is not null; + + /// + /// Whether the user has a guild banner set. + /// + public bool HasGuildBanner => GuildBannerHash is not null; + + /// + /// Whether the user has a set avatar decoration. + /// + public bool HasGuildAvatarDecoration => GuildAvatarDecorationData is not null; + + /// + /// Gets the of the user's guild avatar decoration. + /// + /// An pointing to the user's guild avatar decoration. If the user does not have one set, returns . + public ImageUrl? GetGuildAvatarDecorationUrl() => GuildAvatarDecorationData is { Hash: var hash } ? ImageUrl.AvatarDecoration(hash) : null; } diff --git a/NetCord/PartialGuildUserExtensions.cs b/NetCord/PartialGuildUserExtensions.cs index ef94d31a..98364dd4 100644 --- a/NetCord/PartialGuildUserExtensions.cs +++ b/NetCord/PartialGuildUserExtensions.cs @@ -3,20 +3,34 @@ namespace NetCord; +/// +/// Contains methods providing additional functionality for . +/// public static class PartialGuildUserExtensions { + /// + /// Returns an object belonging to the by acquiring it from the specificied . + /// + /// The to acquire roles for. + /// The to acquire the roles from. public static IEnumerable GetRoles(this PartialGuildUser user, RestGuild guild) { var roles = guild.Roles; return user.RoleIds.Select(r => roles[r]); } + /// + /// Returns a object belonging to the by acquiring it from the specificied . + /// + /// The to acquire permissions for. + /// The to acquire the permissions from. public static Permissions GetPermissions(this PartialGuildUser user, RestGuild guild) { if (user.Id == guild.OwnerId) return (Permissions)ulong.MaxValue; - var permissions = guild.EveryoneRole.Permissions; + if (guild.EveryoneRole is not { Permissions: var permissions }) + throw new InvalidOperationException("Cannot calculate permissions based on a partial guild."); foreach (var role in user.GetRoles(guild)) permissions |= role.Permissions; @@ -27,12 +41,24 @@ public static Permissions GetPermissions(this PartialGuildUser user, RestGuild g return permissions; } + /// + /// Returns a -specific object belonging to the by acquiring it from the specificied . + /// + /// The to acquire permissions for. + /// The to acquire the permissions from. + /// The to acquire the permissions for. public static Permissions GetChannelPermissions(this PartialGuildUser user, RestGuild guild, IGuildChannel channel) { var guildPermissions = GetPermissions(user, guild); return user.GetChannelPermissions(guildPermissions, channel); } + /// + /// Returns a -specific object belonging to the by acquiring it from the specificied . + /// + /// The to acquire permissions for. + /// The to acquire the permissions from. + /// The ID of the to acquire the permissions for. public static Permissions GetChannelPermissions(this PartialGuildUser user, Guild guild, ulong channelId) { var guildPermissions = GetPermissions(user, guild); @@ -42,6 +68,12 @@ public static Permissions GetChannelPermissions(this PartialGuildUser user, Guil return user.GetChannelPermissionsCore(guildPermissions, guild.Channels[channelId]); } + /// + /// Returns a -specific object belonging to the by acquiring it from the specificied . + /// + /// The to acquire permissions for. + /// The object to acquire permissions from. + /// The to acquire the permissions for. public static Permissions GetChannelPermissions(this PartialGuildUser user, Permissions guildPermissions, IGuildChannel channel) { if (guildPermissions.HasFlag(Permissions.Administrator)) diff --git a/NetCord/Permissions.cs b/NetCord/Permissions.cs index c8b23d23..869a4e9c 100644 --- a/NetCord/Permissions.cs +++ b/NetCord/Permissions.cs @@ -242,4 +242,9 @@ public enum Permissions : ulong /// Allows sending polls. /// SendPolls = 1uL << 49, + + /// + /// Allows user-installed apps to send public responses. When disabled, users will still be allowed to use their apps but the responses will be ephemeral. This only applies to apps not also installed to the server. + /// + UseExternalApplications = 1uL << 50, } diff --git a/NetCord/PingInteraction.cs b/NetCord/PingInteraction.cs index a4be990c..68b6c161 100644 --- a/NetCord/PingInteraction.cs +++ b/NetCord/PingInteraction.cs @@ -7,9 +7,9 @@ public class PingInteraction : Entity, IInteraction JsonModels.JsonInteraction IJsonModel.JsonModel => _jsonModel; private protected readonly JsonModels.JsonInteraction _jsonModel; - private readonly Func _sendResponseAsync; + private readonly Func _sendResponseAsync; - public PingInteraction(JsonModels.JsonInteraction jsonModel, Func sendResponseAsync, RestClient client) + public PingInteraction(JsonModels.JsonInteraction jsonModel, Func sendResponseAsync, RestClient client) { _jsonModel = jsonModel; @@ -36,5 +36,5 @@ public PingInteraction(JsonModels.JsonInteraction jsonModel, Func Entitlements { get; } - public Task SendResponseAsync(InteractionCallback callback, RestRequestProperties? properties = null) => _sendResponseAsync(this, callback, properties); + public Task SendResponseAsync(InteractionCallback callback, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) => _sendResponseAsync(this, callback, properties, cancellationToken); } diff --git a/NetCord/ReactionType.cs b/NetCord/ReactionType.cs new file mode 100644 index 00000000..eab5af15 --- /dev/null +++ b/NetCord/ReactionType.cs @@ -0,0 +1,7 @@ +namespace NetCord; + +public enum ReactionType : byte +{ + Normal = 0, + Burst = 1, +} diff --git a/NetCord/Rest/ApplicationCommand.cs b/NetCord/Rest/ApplicationCommand.cs index 11ea1d60..eeaa491e 100644 --- a/NetCord/Rest/ApplicationCommand.cs +++ b/NetCord/Rest/ApplicationCommand.cs @@ -13,7 +13,7 @@ public partial class ApplicationCommand(JsonModels.JsonApplicationCommand jsonMo public ApplicationCommandType Type => _jsonModel.Type; /// - /// Id of the parent application. + /// ID of the parent application. /// public ulong ApplicationId => _jsonModel.ApplicationId; diff --git a/NetCord/Rest/ApplicationCommandGuildPermissionProperties.cs b/NetCord/Rest/ApplicationCommandGuildPermissionProperties.cs index 9685b363..673555a2 100644 --- a/NetCord/Rest/ApplicationCommandGuildPermissionProperties.cs +++ b/NetCord/Rest/ApplicationCommandGuildPermissionProperties.cs @@ -5,13 +5,13 @@ namespace NetCord.Rest; /// /// /// -/// Id of the role, user, or channel the permission is for. 'GuildId - 1' for all channels. +/// ID of the role, user, or channel the permission is for. 'GuildId - 1' for all channels. /// Type of the permission. /// to allow, , to disallow. public partial class ApplicationCommandGuildPermissionProperties(ulong id, ApplicationCommandGuildPermissionType type, bool permission) { /// - /// Id of the role, user, or channel the permission is for. 'GuildId - 1' for all channels. + /// ID of the role, user, or channel the permission is for. 'GuildId - 1' for all channels. /// [JsonPropertyName("id")] public ulong Id { get; set; } = id; diff --git a/NetCord/Rest/ApplicationCommandGuildPermissions.cs b/NetCord/Rest/ApplicationCommandGuildPermissions.cs index ee1caa33..1ccc7db5 100644 --- a/NetCord/Rest/ApplicationCommandGuildPermissions.cs +++ b/NetCord/Rest/ApplicationCommandGuildPermissions.cs @@ -5,17 +5,17 @@ public class ApplicationCommandGuildPermissions(JsonModels.JsonApplicationComman JsonModels.JsonApplicationCommandGuildPermissions IJsonModel.JsonModel => jsonModel; /// - /// Id of the command. + /// ID of the command. /// public ulong CommandId => jsonModel.CommandId; /// - /// Id of the application the command belongs to. + /// ID of the application the command belongs to. /// public ulong ApplicationId => jsonModel.ApplicationId; /// - /// Id of the guild. + /// ID of the guild. /// public ulong GuildId => jsonModel.GuildId; diff --git a/NetCord/Rest/ApplicationEmojiOptions.cs b/NetCord/Rest/ApplicationEmojiOptions.cs new file mode 100644 index 00000000..6f5653da --- /dev/null +++ b/NetCord/Rest/ApplicationEmojiOptions.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace NetCord.Rest; + +public partial class ApplicationEmojiOptions +{ + internal ApplicationEmojiOptions() + { + } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("name")] + public string? Name { get; set; } +} diff --git a/NetCord/Rest/ApplicationEmojiProperties.cs b/NetCord/Rest/ApplicationEmojiProperties.cs new file mode 100644 index 00000000..201cbd5f --- /dev/null +++ b/NetCord/Rest/ApplicationEmojiProperties.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace NetCord.Rest; + +public partial class ApplicationEmojiProperties(string name, ImageProperties image) +{ + [JsonPropertyName("name")] + public string Name { get; set; } = name; + + [JsonPropertyName("image")] + public ImageProperties Image { get; set; } = image; +} diff --git a/NetCord/Rest/AttachmentProperties.cs b/NetCord/Rest/AttachmentProperties.cs index 524acabe..f7cd394c 100644 --- a/NetCord/Rest/AttachmentProperties.cs +++ b/NetCord/Rest/AttachmentProperties.cs @@ -26,6 +26,11 @@ protected AttachmentProperties(string fileName) /// public string FileName { get; set; } + /// + /// Title of the attachment. + /// + public string? Title { get; set; } + /// /// Description for the file (max 1024 characters for attachments sent by message, max 200 characters for attachments used for sticker creation). /// diff --git a/NetCord/Rest/ChannelPositionProperties.cs b/NetCord/Rest/ChannelPositionProperties.cs index 47487dcb..0309ea83 100644 --- a/NetCord/Rest/ChannelPositionProperties.cs +++ b/NetCord/Rest/ChannelPositionProperties.cs @@ -5,11 +5,11 @@ namespace NetCord.Rest; /// /// /// -/// Channel id. +/// Channel ID. public partial class GuildChannelPositionProperties(ulong id) { /// - /// Channel id. + /// Channel ID. /// [JsonPropertyName("id")] public ulong Id { get; set; } = id; @@ -29,7 +29,7 @@ public partial class GuildChannelPositionProperties(ulong id) public bool? LockPermissions { get; set; } /// - /// The new parent id for the channel that is moved. + /// The new parent ID for the channel that is moved. /// [JsonPropertyName("parent_id")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/NetCord/Rest/ComponentProperties/ButtonProperties.cs b/NetCord/Rest/ComponentProperties/ButtonProperties.cs index 83991c3c..07e22870 100644 --- a/NetCord/Rest/ComponentProperties/ButtonProperties.cs +++ b/NetCord/Rest/ComponentProperties/ButtonProperties.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest; -public partial class ButtonProperties : IButtonProperties +public partial class ButtonProperties : ICustomizableButtonProperties { /// /// Developer-defined identifier for the button (max 100 characters). diff --git a/NetCord/Rest/ComponentProperties/ChannelMenuProperties.cs b/NetCord/Rest/ComponentProperties/ChannelMenuProperties.cs index c335f801..08bc24df 100644 --- a/NetCord/Rest/ComponentProperties/ChannelMenuProperties.cs +++ b/NetCord/Rest/ComponentProperties/ChannelMenuProperties.cs @@ -8,7 +8,7 @@ namespace NetCord.Rest; /// /// /// -/// Id for the menu (max 100 characters). +/// ID for the menu (max 100 characters). public partial class ChannelMenuProperties(string customId) : EntityMenuProperties(customId) { public override ComponentType ComponentType => ComponentType.ChannelMenu; diff --git a/NetCord/Rest/ComponentProperties/IButtonProperties.cs b/NetCord/Rest/ComponentProperties/IButtonProperties.cs index b26c7158..be89a564 100644 --- a/NetCord/Rest/ComponentProperties/IButtonProperties.cs +++ b/NetCord/Rest/ComponentProperties/IButtonProperties.cs @@ -9,23 +9,13 @@ public partial interface IButtonProperties /// /// Style of the button. /// - public ButtonStyle Style { get; set; } + public ButtonStyle Style { get; } /// /// Type of the component. /// public ComponentType ComponentType { get; } - /// - /// Text that appears on the button (max 80 characters). - /// - public string? Label { get; set; } - - /// - /// Emoji that appears on the button. - /// - public EmojiProperties? Emoji { get; set; } - /// /// Whether the button is disabled. /// @@ -39,11 +29,14 @@ public override void Write(Utf8JsonWriter writer, IButtonProperties button, Json { switch (button) { - case ButtonProperties actionButton: - JsonSerializer.Serialize(writer, actionButton, Serialization.Default.ButtonProperties); + case ButtonProperties buttonProperties: + JsonSerializer.Serialize(writer, buttonProperties, Serialization.Default.ButtonProperties); + break; + case LinkButtonProperties linkButtonProperties: + JsonSerializer.Serialize(writer, linkButtonProperties, Serialization.Default.LinkButtonProperties); break; - case LinkButtonProperties linkButton: - JsonSerializer.Serialize(writer, linkButton, Serialization.Default.LinkButtonProperties); + case PremiumButtonProperties premiumButtonProperties: + JsonSerializer.Serialize(writer, premiumButtonProperties, Serialization.Default.PremiumButtonProperties); break; default: throw new InvalidOperationException($"Invalid {nameof(IButtonProperties)} value."); diff --git a/NetCord/Rest/ComponentProperties/ICustomizableButtonProperties.cs b/NetCord/Rest/ComponentProperties/ICustomizableButtonProperties.cs new file mode 100644 index 00000000..96d7a346 --- /dev/null +++ b/NetCord/Rest/ComponentProperties/ICustomizableButtonProperties.cs @@ -0,0 +1,14 @@ +namespace NetCord.Rest; + +public partial interface ICustomizableButtonProperties : IButtonProperties +{ + /// + /// Text that appears on the button (max 80 characters). + /// + public string? Label { get; set; } + + /// + /// Emoji that appears on the button. + /// + public EmojiProperties? Emoji { get; set; } +} diff --git a/NetCord/Rest/ComponentProperties/LinkButtonProperties.cs b/NetCord/Rest/ComponentProperties/LinkButtonProperties.cs index 76d2ec2b..03bdc6c6 100644 --- a/NetCord/Rest/ComponentProperties/LinkButtonProperties.cs +++ b/NetCord/Rest/ComponentProperties/LinkButtonProperties.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest; -public partial class LinkButtonProperties : IButtonProperties +public partial class LinkButtonProperties : ICustomizableButtonProperties { /// /// Url of the button. @@ -11,7 +11,7 @@ public partial class LinkButtonProperties : IButtonProperties public string Url { get; set; } [JsonPropertyName("style")] - public ButtonStyle Style { get; set; } + public ButtonStyle Style => (ButtonStyle)5; [JsonPropertyName("type")] public ComponentType ComponentType => ComponentType.Button; @@ -37,7 +37,6 @@ public LinkButtonProperties(string url, string label) { Url = url; Label = label; - Style = (ButtonStyle)5; } /// @@ -49,7 +48,6 @@ public LinkButtonProperties(string url, EmojiProperties emoji) { Url = url; Emoji = emoji; - Style = (ButtonStyle)5; } /// @@ -63,6 +61,5 @@ public LinkButtonProperties(string url, string label, EmojiProperties emoji) Url = url; Label = label; Emoji = emoji; - Style = (ButtonStyle)5; } } diff --git a/NetCord/Rest/ComponentProperties/MenuProperties.cs b/NetCord/Rest/ComponentProperties/MenuProperties.cs index 089d89bc..0427c2a0 100644 --- a/NetCord/Rest/ComponentProperties/MenuProperties.cs +++ b/NetCord/Rest/ComponentProperties/MenuProperties.cs @@ -5,11 +5,11 @@ namespace NetCord.Rest; /// /// /// -/// Id for the menu (max 100 characters). +/// ID for the menu (max 100 characters). public abstract partial class MenuProperties(string customId) : MessageComponentProperties { /// - /// Id for the menu (max 100 characters). + /// ID for the menu (max 100 characters). /// [JsonPropertyName("custom_id")] public string CustomId { get; set; } = customId; diff --git a/NetCord/Rest/ComponentProperties/PremiumButtonProperties.cs b/NetCord/Rest/ComponentProperties/PremiumButtonProperties.cs new file mode 100644 index 00000000..6e5dfd9c --- /dev/null +++ b/NetCord/Rest/ComponentProperties/PremiumButtonProperties.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace NetCord.Rest; + +public partial class PremiumButtonProperties(ulong skuId) : IButtonProperties +{ + [JsonPropertyName("sku_id")] + public ulong SkuId { get; set; } = skuId; + + [JsonPropertyName("style")] + public ButtonStyle Style => (ButtonStyle)6; + + [JsonPropertyName("type")] + public ComponentType ComponentType => ComponentType.Button; + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + [JsonPropertyName("disabled")] + public bool Disabled { get; set; } +} diff --git a/NetCord/Rest/Connection.cs b/NetCord/Rest/Connection.cs index b7d816c2..41d23243 100644 --- a/NetCord/Rest/Connection.cs +++ b/NetCord/Rest/Connection.cs @@ -11,7 +11,7 @@ public Connection(JsonModels.JsonConnection jsonModel, RestClient client) var integrations = jsonModel.Integrations; if (integrations is not null) - Integrations = integrations.ToDictionary(i => i.Id, i => new Integration(i, client)); + Integrations = integrations.Select(i => new Integration(i, client)).ToArray(); } /// @@ -30,9 +30,9 @@ public Connection(JsonModels.JsonConnection jsonModel, RestClient client) public bool? Revoked => _jsonModel.Revoked; /// - /// An array of server integrations. + /// A list of server integrations. /// - public IReadOnlyDictionary? Integrations { get; } + public IReadOnlyList? Integrations { get; } /// /// Whether the connection is verified. diff --git a/NetCord/Rest/ConnectionType.cs b/NetCord/Rest/ConnectionType.cs index 5d3f27de..5fb5b678 100644 --- a/NetCord/Rest/ConnectionType.cs +++ b/NetCord/Rest/ConnectionType.cs @@ -2,12 +2,18 @@ namespace NetCord.Rest; -[JsonConverter(typeof(JsonConverters.StringEnumConverterWithErrorHandling))] +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum ConnectionType { [JsonPropertyName("battlenet")] BattleNet, + [JsonPropertyName("bungie")] + Bungie, + + [JsonPropertyName("domain")] + Domain, + [JsonPropertyName("ebay")] Ebay, @@ -38,6 +44,9 @@ public enum ConnectionType [JsonPropertyName("riotgames")] RiotGames, + [JsonPropertyName("roblox")] + Roblox, + [JsonPropertyName("spotify")] Spotify, diff --git a/NetCord/Rest/ContentPaginationAsyncEnumerable.cs b/NetCord/Rest/ContentPaginationAsyncEnumerable.cs index 69a79d29..39703ac7 100644 --- a/NetCord/Rest/ContentPaginationAsyncEnumerable.cs +++ b/NetCord/Rest/ContentPaginationAsyncEnumerable.cs @@ -20,7 +20,7 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellati while (true) { - var results = await convertAsync(await client.SendRequestAsync(method, content, endpoint, null, resourceInfo, properties, global).ConfigureAwait(false)).ConfigureAwait(false); + var results = await convertAsync(await client.SendRequestAsync(method, content, endpoint, null, resourceInfo, properties, global, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); T? last = default; int count = 0; diff --git a/NetCord/Rest/CurrentUserVoiceStateOptions.cs b/NetCord/Rest/CurrentUserVoiceStateOptions.cs index 12af3ed8..aeb8142d 100644 --- a/NetCord/Rest/CurrentUserVoiceStateOptions.cs +++ b/NetCord/Rest/CurrentUserVoiceStateOptions.cs @@ -9,7 +9,7 @@ internal CurrentUserVoiceStateOptions() } /// - /// The id of the channel the user is currently in. + /// The ID of the channel the user is currently in. /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("channel_id")] diff --git a/NetCord/Rest/EmbedImageProperties.cs b/NetCord/Rest/EmbedImageProperties.cs index e962284e..1b388f9d 100644 --- a/NetCord/Rest/EmbedImageProperties.cs +++ b/NetCord/Rest/EmbedImageProperties.cs @@ -16,13 +16,4 @@ public partial class EmbedImageProperties(string? url) public string? Url { get; set; } = url; public static implicit operator EmbedImageProperties(string? url) => new(url); - - public static implicit operator EmbedImageProperties(AttachmentProperties attachment) => FromAttachment(attachment.FileName); - - /// - /// Creates new based on . - /// - /// Attachment file name. - /// - public static EmbedImageProperties FromAttachment(string attachmentFileName) => new($"attachment://{attachmentFileName}"); } diff --git a/NetCord/Rest/FollowAnnouncementGuildChannelProperties.cs b/NetCord/Rest/FollowAnnouncementGuildChannelProperties.cs index 72826598..3fde1455 100644 --- a/NetCord/Rest/FollowAnnouncementGuildChannelProperties.cs +++ b/NetCord/Rest/FollowAnnouncementGuildChannelProperties.cs @@ -3,13 +3,13 @@ namespace NetCord.Rest; /// -/// Id of target channel. +/// ID of target channel. /// /// internal class FollowAnnouncementGuildChannelProperties(ulong webhookChannelId) { /// - /// Id of target channel. + /// ID of target channel. /// [JsonPropertyName("webhook_channel_id")] public ulong WebhookChannelId { get; set; } = webhookChannelId; diff --git a/NetCord/Rest/ForumGuildThreadMessageProperties.cs b/NetCord/Rest/ForumGuildThreadMessageProperties.cs index b863b6da..f5451880 100644 --- a/NetCord/Rest/ForumGuildThreadMessageProperties.cs +++ b/NetCord/Rest/ForumGuildThreadMessageProperties.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest; -public partial class ForumGuildThreadMessageProperties +public partial class ForumGuildThreadMessageProperties : IMessageProperties { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("content")] diff --git a/NetCord/Rest/GuildOnboarding.cs b/NetCord/Rest/GuildOnboarding.cs index 689a5b7b..331e379b 100644 --- a/NetCord/Rest/GuildOnboarding.cs +++ b/NetCord/Rest/GuildOnboarding.cs @@ -13,7 +13,7 @@ public GuildOnboarding(JsonModels.JsonGuildOnboarding jsonModel, RestClient clie } /// - /// Id of the guild this onboarding is part of. + /// ID of the guild this onboarding is part of. /// public ulong GuildId => _jsonModel.GuildId; diff --git a/NetCord/Rest/GuildOnboardingPromptOptionProperties.cs b/NetCord/Rest/GuildOnboardingPromptOptionProperties.cs index 17b4227e..91771bcd 100644 --- a/NetCord/Rest/GuildOnboardingPromptOptionProperties.cs +++ b/NetCord/Rest/GuildOnboardingPromptOptionProperties.cs @@ -5,31 +5,31 @@ namespace NetCord.Rest; /// /// /// -/// Ids for channels a member is added to when the option is selected. -/// Ids for roles assigned to a member when the option is selected. +/// IDs for channels a member is added to when the option is selected. +/// IDs for roles assigned to a member when the option is selected. /// Title of the option. public partial class GuildOnboardingPromptOptionProperties(IEnumerable? channelIds, IEnumerable? roleIds, string title) { /// - /// Id of the option. + /// ID of the option. /// [JsonPropertyName("id")] public ulong? Id { get; set; } /// - /// Ids for channels a member is added to when the option is selected. + /// IDs for channels a member is added to when the option is selected. /// [JsonPropertyName("channel_ids")] public IEnumerable? ChannelIds { get; set; } = channelIds; /// - /// Ids for roles assigned to a member when the option is selected. + /// IDs for roles assigned to a member when the option is selected. /// [JsonPropertyName("role_ids")] public IEnumerable? RoleIds { get; set; } = roleIds; /// - /// Emoji Id of the option. + /// Emoji ID of the option. /// [JsonPropertyName("emoji_id")] public ulong? EmojiId { get; set; } diff --git a/NetCord/Rest/GuildOnboardingPromptProperties.cs b/NetCord/Rest/GuildOnboardingPromptProperties.cs index 6bfe02cd..d4739450 100644 --- a/NetCord/Rest/GuildOnboardingPromptProperties.cs +++ b/NetCord/Rest/GuildOnboardingPromptProperties.cs @@ -11,7 +11,7 @@ namespace NetCord.Rest; public partial class GuildOnboardingPromptProperties(GuildOnboardingPromptType type, IEnumerable options, string title) { /// - /// Id of the prompt. + /// ID of the prompt. /// [JsonPropertyName("id")] public ulong? Id { get; set; } diff --git a/NetCord/Rest/GuildThreadGenerator.cs b/NetCord/Rest/GuildThreadGenerator.cs index ffa439a0..06a00f6f 100644 --- a/NetCord/Rest/GuildThreadGenerator.cs +++ b/NetCord/Rest/GuildThreadGenerator.cs @@ -2,10 +2,10 @@ internal static class GuildThreadGenerator { - public static Dictionary CreateThreads(JsonModels.JsonRestGuildThreadResult jsonThreads, RestClient client) + public static IEnumerable CreateThreads(JsonModels.JsonRestGuildThreadResult jsonThreads, RestClient client) { var users = jsonThreads.Users.ToDictionary(u => u.ThreadId); - return jsonThreads.Threads.ToDictionary(t => t.Id, t => + return jsonThreads.Threads.Select(t => { if (users.TryGetValue(t.Id, out var user)) t.CurrentUser = user; @@ -13,16 +13,4 @@ public static Dictionary CreateThreads(JsonModels.JsonRestGu return GuildThread.CreateFromJson(t, client); }); } - - public static IEnumerable CreateThreads(JsonModels.JsonRestGuildThreadPartialResult jsonThreads, RestClient client) - { - var users = jsonThreads.Users.ToDictionary(u => u.ThreadId); - foreach (var t in jsonThreads.Threads) - { - if (users.TryGetValue(t.Id, out var user)) - t.CurrentUser = user; - - yield return GuildThread.CreateFromJson(t, client); - } - } } diff --git a/NetCord/Rest/GuildUserInfo.cs b/NetCord/Rest/GuildUserInfo.cs index 74cb1313..e8fb7372 100644 --- a/NetCord/Rest/GuildUserInfo.cs +++ b/NetCord/Rest/GuildUserInfo.cs @@ -11,11 +11,23 @@ public GuildUserInfo(JsonModels.JsonGuildUserInfo jsonModel, ulong guildId, Rest User = new(_jsonModel.User, guildId, client); } + /// + /// The object representing the user. + /// public GuildUser User { get; } + /// + /// The code of the invite the joined from. + /// public string? SourceInviteCode => _jsonModel.SourceInviteCode; + /// + /// Specifies how the joined the guild. + /// public GuildUserJoinSourceType JoinSourceType => _jsonModel.JoinSourceType; + /// + /// The ID of the user that invited the to the guild. + /// public ulong? InviterId => _jsonModel.InviterId; } diff --git a/NetCord/Rest/GuildUserJoinSourceType.cs b/NetCord/Rest/GuildUserJoinSourceType.cs index 138fee18..d3057962 100644 --- a/NetCord/Rest/GuildUserJoinSourceType.cs +++ b/NetCord/Rest/GuildUserJoinSourceType.cs @@ -1,8 +1,22 @@ namespace NetCord.Rest; +/// +/// Indicates how a joined a guild. +/// public enum GuildUserJoinSourceType { + /// + /// User joined via the 'Discovery' tab. + /// GuildDiscovery = 3, + + /// + /// User joined from a manually created invite link. + /// GuildUserInvite = 5, + + /// + /// User joined using the guild's vanity invite. + /// GuildVanityInvite = 6, } diff --git a/NetCord/Rest/HttpInteractionFactory.cs b/NetCord/Rest/HttpInteractionFactory.cs index ac23011b..52e7b30a 100644 --- a/NetCord/Rest/HttpInteractionFactory.cs +++ b/NetCord/Rest/HttpInteractionFactory.cs @@ -4,12 +4,12 @@ namespace NetCord.Rest; public static class HttpInteractionFactory { - public static async ValueTask CreateAsync(Stream body, RestClient client, Func sendResponseAsync, CancellationToken cancellationToken = default) + public static async ValueTask CreateAsync(Stream body, RestClient client, Func sendResponseAsync, CancellationToken cancellationToken = default) => IInteraction.CreateFromJson((await JsonSerializer.DeserializeAsync(body, Serialization.Default.JsonInteraction, cancellationToken).ConfigureAwait(false))!, sendResponseAsync, client); - public static IInteraction Create(Stream body, Func sendResponseAsync, RestClient client) + public static IInteraction Create(Stream body, Func sendResponseAsync, RestClient client) => IInteraction.CreateFromJson(JsonSerializer.Deserialize(body, Serialization.Default.JsonInteraction)!, sendResponseAsync, client); - public static IInteraction Create(ReadOnlySpan body, Func sendResponseAsync, RestClient client) + public static IInteraction Create(ReadOnlySpan body, Func sendResponseAsync, RestClient client) => IInteraction.CreateFromJson(JsonSerializer.Deserialize(body, Serialization.Default.JsonInteraction)!, sendResponseAsync, client); } diff --git a/NetCord/Rest/IMessageProperties.cs b/NetCord/Rest/IMessageProperties.cs new file mode 100644 index 00000000..c4d3947b --- /dev/null +++ b/NetCord/Rest/IMessageProperties.cs @@ -0,0 +1,16 @@ +namespace NetCord.Rest; + +public partial interface IMessageProperties +{ + public string? Content { get; set; } + + public IEnumerable? Embeds { get; set; } + + public AllowedMentionsProperties? AllowedMentions { get; set; } + + public IEnumerable? Attachments { get; set; } + + public IEnumerable? Components { get; set; } + + public MessageFlags? Flags { get; set; } +} diff --git a/NetCord/Rest/IRestRequestHandler.cs b/NetCord/Rest/IRestRequestHandler.cs index cb324de1..53202a75 100644 --- a/NetCord/Rest/IRestRequestHandler.cs +++ b/NetCord/Rest/IRestRequestHandler.cs @@ -2,7 +2,7 @@ public interface IRestRequestHandler : IDisposable { - public Task SendAsync(HttpRequestMessage request); + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default); public void AddDefaultHeader(string name, IEnumerable values); } diff --git a/NetCord/Rest/InteractionCallback.cs b/NetCord/Rest/InteractionCallback.cs index 30a354b0..aa01f252 100644 --- a/NetCord/Rest/InteractionCallback.cs +++ b/NetCord/Rest/InteractionCallback.cs @@ -66,12 +66,6 @@ public static InteractionCallback Auto public static InteractionCallback Modal(ModalProperties modal) => new(InteractionCallbackType.Modal, modal); - /// - /// Respond to an interaction with an upgrade button, only available for apps with monetization enabled. - /// - public static InteractionCallback PremiumRequired - => new(InteractionCallbackType.PremiumRequired); - public HttpContent Serialize() { switch (this) diff --git a/NetCord/Rest/InteractionCallbackType.cs b/NetCord/Rest/InteractionCallbackType.cs index db4b68e5..c2a70d2c 100644 --- a/NetCord/Rest/InteractionCallbackType.cs +++ b/NetCord/Rest/InteractionCallbackType.cs @@ -36,9 +36,4 @@ public enum InteractionCallbackType /// Respond to an interaction with a popup modal. /// Modal = 9, - - /// - /// Respond to an interaction with an upgrade button, only available for apps with monetization enabled. - /// - PremiumRequired = 10, } diff --git a/NetCord/Rest/InteractionMessageProperties.cs b/NetCord/Rest/InteractionMessageProperties.cs index 515c9753..6fa8bb55 100644 --- a/NetCord/Rest/InteractionMessageProperties.cs +++ b/NetCord/Rest/InteractionMessageProperties.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest; -public partial class InteractionMessageProperties : IHttpSerializable +public partial class InteractionMessageProperties : IHttpSerializable, IMessageProperties { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("tts")] diff --git a/NetCord/Rest/GuildInviteProperties.cs b/NetCord/Rest/InviteProperties.cs similarity index 85% rename from NetCord/Rest/GuildInviteProperties.cs rename to NetCord/Rest/InviteProperties.cs index 134bc42e..e9bd3e73 100644 --- a/NetCord/Rest/GuildInviteProperties.cs +++ b/NetCord/Rest/InviteProperties.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest; -public partial class GuildInviteProperties +public partial class InviteProperties { [JsonPropertyName("max_age")] public int? MaxAge { get; set; } @@ -17,7 +17,7 @@ public partial class GuildInviteProperties public bool? Unique { get; set; } [JsonPropertyName("target_type")] - public GuildInviteTargetType? TargetType { get; set; } + public InviteTargetType? TargetType { get; set; } [JsonPropertyName("target_user_id")] public ulong? TargetUserId { get; set; } diff --git a/NetCord/Rest/JsonModels/JsonRestGuildThreadPartialResult.cs b/NetCord/Rest/JsonModels/JsonRestGuildThreadPartialResult.cs index 8d825658..8397df41 100644 --- a/NetCord/Rest/JsonModels/JsonRestGuildThreadPartialResult.cs +++ b/NetCord/Rest/JsonModels/JsonRestGuildThreadPartialResult.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest.JsonModels; -public class JsonRestGuildThreadPartialResult : JsonRestGuildThreadResult +internal class JsonRestGuildThreadPartialResult : JsonRestGuildThreadResult { [JsonPropertyName("has_more")] public bool HasMore { get; set; } diff --git a/NetCord/Rest/JsonModels/JsonRestGuildThreadResult.cs b/NetCord/Rest/JsonModels/JsonRestGuildThreadResult.cs index 61853100..5c9ec76c 100644 --- a/NetCord/Rest/JsonModels/JsonRestGuildThreadResult.cs +++ b/NetCord/Rest/JsonModels/JsonRestGuildThreadResult.cs @@ -4,7 +4,7 @@ namespace NetCord.Rest.JsonModels; -public class JsonRestGuildThreadResult +internal class JsonRestGuildThreadResult { [JsonPropertyName("threads")] public JsonChannel[] Threads { get; set; } diff --git a/NetCord/Rest/JsonModels/JsonRestGuildInvite.cs b/NetCord/Rest/JsonModels/JsonRestInvite.cs similarity index 90% rename from NetCord/Rest/JsonModels/JsonRestGuildInvite.cs rename to NetCord/Rest/JsonModels/JsonRestInvite.cs index 890ccfab..b64cee49 100644 --- a/NetCord/Rest/JsonModels/JsonRestGuildInvite.cs +++ b/NetCord/Rest/JsonModels/JsonRestInvite.cs @@ -4,8 +4,11 @@ namespace NetCord.Rest.JsonModels; -public class JsonRestGuildInvite +public class JsonRestInvite { + [JsonPropertyName("type")] + public InviteType Type { get; set; } + [JsonPropertyName("code")] public string Code { get; set; } @@ -19,7 +22,7 @@ public class JsonRestGuildInvite public JsonUser? Inviter { get; set; } [JsonPropertyName("target_type")] - public GuildInviteTargetType? TargetType { get; set; } + public InviteTargetType? TargetType { get; set; } [JsonPropertyName("target_user")] public JsonUser? TargetUser { get; set; } diff --git a/NetCord/Rest/MentionableValueProperties.cs b/NetCord/Rest/MentionableValueProperties.cs index 58d43cb8..b5a71e09 100644 --- a/NetCord/Rest/MentionableValueProperties.cs +++ b/NetCord/Rest/MentionableValueProperties.cs @@ -9,7 +9,7 @@ public partial struct MentionableValueProperties(ulong id, MentionableValueType [JsonPropertyName("id")] public ulong Id { get; set; } = id; - [JsonConverter(typeof(StringEnumConverterWithErrorHandling))] + [JsonConverter(typeof(SafeStringEnumConverter))] [JsonPropertyName("type")] public MentionableValueType Type { get; set; } = type; } diff --git a/NetCord/Rest/MessageCall.cs b/NetCord/Rest/MessageCall.cs new file mode 100644 index 00000000..2799ded7 --- /dev/null +++ b/NetCord/Rest/MessageCall.cs @@ -0,0 +1,12 @@ +using NetCord.JsonModels; + +namespace NetCord.Rest; + +public class MessageCall(JsonMessageCall jsonModel) : IJsonModel +{ + JsonMessageCall IJsonModel.JsonModel => jsonModel; + + public IReadOnlyList Participants => jsonModel.Participants; + + public DateTimeOffset? EndedAt => jsonModel.EndedAt; +} diff --git a/NetCord/Rest/MessageProperties.cs b/NetCord/Rest/MessageProperties.cs index 15d32a35..b7773825 100644 --- a/NetCord/Rest/MessageProperties.cs +++ b/NetCord/Rest/MessageProperties.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest; -public partial class MessageProperties : IHttpSerializable +public partial class MessageProperties : IHttpSerializable, IMessageProperties { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("content")] diff --git a/NetCord/Rest/MessageReactionsPaginationProperties.cs b/NetCord/Rest/MessageReactionsPaginationProperties.cs new file mode 100644 index 00000000..99175e95 --- /dev/null +++ b/NetCord/Rest/MessageReactionsPaginationProperties.cs @@ -0,0 +1,9 @@ +namespace NetCord.Rest; + +public partial record MessageReactionsPaginationProperties : PaginationProperties, IPaginationProperties +{ + public ReactionType? Type { get; set; } + + static MessageReactionsPaginationProperties IPaginationProperties.Create() => new(); + static MessageReactionsPaginationProperties IPaginationProperties.Create(MessageReactionsPaginationProperties properties) => new(properties); +} diff --git a/NetCord/Rest/MessageReferenceProperties.cs b/NetCord/Rest/MessageReferenceProperties.cs index e7e5ecf3..291270cd 100644 --- a/NetCord/Rest/MessageReferenceProperties.cs +++ b/NetCord/Rest/MessageReferenceProperties.cs @@ -2,11 +2,43 @@ namespace NetCord.Rest; -public partial class MessageReferenceProperties(ulong messageId, bool failIfNotExists = true) +public partial class MessageReferenceProperties { + public static MessageReferenceProperties Reply(ulong messageId, bool failIfNotExists = true) + { + return new() + { + Type = MessageReferenceType.Reply, + MessageId = messageId, + FailIfNotExists = failIfNotExists, + }; + } + + public static MessageReferenceProperties Forward(ulong channelId, ulong messageId, bool failIfNotExists = true) + { + return new() + { + Type = MessageReferenceType.Forward, + ChannelId = channelId, + MessageId = messageId, + FailIfNotExists = failIfNotExists, + }; + } + + private MessageReferenceProperties() + { + } + + [JsonPropertyName("type")] + public MessageReferenceType Type { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + [JsonPropertyName("channel_id")] + public ulong? ChannelId { get; set; } + [JsonPropertyName("message_id")] - public ulong Id { get; set; } = messageId; + public ulong MessageId { get; set; } [JsonPropertyName("fail_if_not_exists")] - public bool FailIfNotExists { get; set; } = failIfNotExists; + public bool FailIfNotExists { get; set; } } diff --git a/NetCord/Rest/OptimizedQueryPaginationAsyncEnumerable.cs b/NetCord/Rest/OptimizedQueryPaginationAsyncEnumerable.cs index c7cea345..c1359132 100644 --- a/NetCord/Rest/OptimizedQueryPaginationAsyncEnumerable.cs +++ b/NetCord/Rest/OptimizedQueryPaginationAsyncEnumerable.cs @@ -18,7 +18,7 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellati while (true) { - (var results, var hasMore) = await convertAsync(await client.SendRequestAsync(method, endpoint, query, resourceInfo, properties, global).ConfigureAwait(false)).ConfigureAwait(false); + (var results, var hasMore) = await convertAsync(await client.SendRequestAsync(method, endpoint, query, resourceInfo, properties, global, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); T? last = default; diff --git a/NetCord/Rest/QueryPaginationAsyncEnumerable.cs b/NetCord/Rest/QueryPaginationAsyncEnumerable.cs index 666145a0..b5d413dd 100644 --- a/NetCord/Rest/QueryPaginationAsyncEnumerable.cs +++ b/NetCord/Rest/QueryPaginationAsyncEnumerable.cs @@ -20,7 +20,7 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellati while (true) { - var results = await convertAsync(await client.SendRequestAsync(method, endpoint, query, resourceInfo, properties, global).ConfigureAwait(false)).ConfigureAwait(false); + var results = await convertAsync(await client.SendRequestAsync(method, endpoint, query, resourceInfo, properties, global, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); T? last = default; int count = 0; diff --git a/NetCord/Rest/RateLimitedException.cs b/NetCord/Rest/RateLimitedException.cs index bf7719a9..540862c7 100644 --- a/NetCord/Rest/RateLimitedException.cs +++ b/NetCord/Rest/RateLimitedException.cs @@ -1,4 +1,4 @@ -namespace NetCord.Rest.RateLimits; +namespace NetCord.Rest; public class RateLimitedException(long reset, RateLimitScope scope) : Exception("Rate limit triggered.") { diff --git a/NetCord/Rest/RateLimits/GlobalRateLimiter.cs b/NetCord/Rest/RateLimits/GlobalRateLimiter.cs index ae67b00d..1820f8d5 100644 --- a/NetCord/Rest/RateLimits/GlobalRateLimiter.cs +++ b/NetCord/Rest/RateLimits/GlobalRateLimiter.cs @@ -7,7 +7,7 @@ internal class GlobalRateLimiter(int limit, long duration) : IGlobalRateLimiter private int _remaining = limit; private long _reset; - public ValueTask TryAcquireAsync() + public ValueTask TryAcquireAsync(CancellationToken cancellationToken = default) { var timestamp = Environment.TickCount64; lock (_lock) @@ -27,10 +27,10 @@ public ValueTask TryAcquireAsync() } } - return new(RateLimitAcquisitionResult.NoRateLimit()); + return new(RateLimitAcquisitionResult.NoRateLimit); } - public ValueTask IndicateRateLimitAsync(long reset) + public ValueTask IndicateRateLimitAsync(long reset, CancellationToken cancellationToken = default) { lock (_lock) { diff --git a/NetCord/Rest/RateLimits/IGlobalRateLimiter.cs b/NetCord/Rest/RateLimits/IGlobalRateLimiter.cs index 8e58d579..8a9b8955 100644 --- a/NetCord/Rest/RateLimits/IGlobalRateLimiter.cs +++ b/NetCord/Rest/RateLimits/IGlobalRateLimiter.cs @@ -2,5 +2,5 @@ public interface IGlobalRateLimiter : IRateLimiter { - public ValueTask IndicateRateLimitAsync(long reset); + public ValueTask IndicateRateLimitAsync(long reset, CancellationToken cancellationToken = default); } diff --git a/NetCord/Rest/RateLimits/IRateLimitManager.cs b/NetCord/Rest/RateLimits/IRateLimitManager.cs index b4b86e8d..f1ae6772 100644 --- a/NetCord/Rest/RateLimits/IRateLimitManager.cs +++ b/NetCord/Rest/RateLimits/IRateLimitManager.cs @@ -2,9 +2,9 @@ public interface IRateLimitManager : IDisposable { - public ValueTask GetGlobalRateLimiterAsync(); + public ValueTask GetGlobalRateLimiterAsync(CancellationToken cancellationToken = default); - public ValueTask GetRouteRateLimiterAsync(Route route); + public ValueTask GetRouteRateLimiterAsync(Route route, CancellationToken cancellationToken = default); - public ValueTask ExchangeRouteRateLimiterAsync(Route route, RateLimitInfo? rateLimitInfo, BucketInfo? previousBucketInfo); + public ValueTask ExchangeRouteRateLimiterAsync(Route route, RateLimitInfo? rateLimitInfo, BucketInfo? previousBucketInfo, CancellationToken cancellationToken = default); } diff --git a/NetCord/Rest/RateLimits/IRateLimiter.cs b/NetCord/Rest/RateLimits/IRateLimiter.cs index ea877bd1..31948220 100644 --- a/NetCord/Rest/RateLimits/IRateLimiter.cs +++ b/NetCord/Rest/RateLimits/IRateLimiter.cs @@ -2,5 +2,5 @@ public interface IRateLimiter { - public ValueTask TryAcquireAsync(); + public ValueTask TryAcquireAsync(CancellationToken cancellationToken = default); } diff --git a/NetCord/Rest/RateLimits/IRouteRateLimiter.cs b/NetCord/Rest/RateLimits/IRouteRateLimiter.cs index 89aca046..9b4e8ba5 100644 --- a/NetCord/Rest/RateLimits/IRouteRateLimiter.cs +++ b/NetCord/Rest/RateLimits/IRouteRateLimiter.cs @@ -6,9 +6,9 @@ public interface IRouteRateLimiter : IRateLimiter public BucketInfo? BucketInfo { get; } - public ValueTask CancelAcquireAsync(long timestamp); + public ValueTask CancelAcquireAsync(long acquisitionTimestamp, CancellationToken cancellationToken = default); - public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo); + public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo, CancellationToken cancellationToken = default); - public ValueTask IndicateExchangeAsync(long timestamp); + public ValueTask IndicateExchangeAsync(long timestamp, CancellationToken cancellationToken = default); } diff --git a/NetCord/Rest/RateLimits/NoRateLimitRouteRateLimiter.cs b/NetCord/Rest/RateLimits/NoRateLimitRouteRateLimiter.cs index 58fa80c5..73cfc50e 100644 --- a/NetCord/Rest/RateLimits/NoRateLimitRouteRateLimiter.cs +++ b/NetCord/Rest/RateLimits/NoRateLimitRouteRateLimiter.cs @@ -8,22 +8,22 @@ internal class NoRateLimitRouteRateLimiter : ITrackingRouteRateLimiter public long LastAccess { get; private set; } = Environment.TickCount64; - public ValueTask TryAcquireAsync() + public ValueTask TryAcquireAsync(CancellationToken cancellationToken = default) { - return new(RateLimitAcquisitionResult.NoRateLimit()); + return new(RateLimitAcquisitionResult.NoRateLimit); } - public ValueTask CancelAcquireAsync(long timestamp) + public ValueTask CancelAcquireAsync(long acquisitionTimestamp, CancellationToken cancellationToken = default) { return default; } - public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo) + public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo, CancellationToken cancellationToken = default) { return default; } - public ValueTask IndicateExchangeAsync(long timestamp) + public ValueTask IndicateExchangeAsync(long timestamp, CancellationToken cancellationToken = default) { return default; } diff --git a/NetCord/Rest/RateLimits/RateLimitAcquisitionResult.cs b/NetCord/Rest/RateLimits/RateLimitAcquisitionResult.cs index 23d869c0..57236aa2 100644 --- a/NetCord/Rest/RateLimits/RateLimitAcquisitionResult.cs +++ b/NetCord/Rest/RateLimits/RateLimitAcquisitionResult.cs @@ -9,9 +9,9 @@ private RateLimitAcquisitionResult(int resetAfter, bool rateLimited, bool always AlwaysRetry = alwaysRetryOnce; } - public static RateLimitAcquisitionResult Retry() => new(0, false, true); + public static RateLimitAcquisitionResult Retry { get; } = new(0, false, true); - public static RateLimitAcquisitionResult NoRateLimit() => new(0, false, false); + public static RateLimitAcquisitionResult NoRateLimit { get; } = new(0, false, false); public static RateLimitAcquisitionResult RateLimit(int resetAfter) => new(resetAfter, true, false); diff --git a/NetCord/Rest/RateLimits/RateLimitManager.cs b/NetCord/Rest/RateLimits/RateLimitManager.cs index 7224cf38..646b4686 100644 --- a/NetCord/Rest/RateLimits/RateLimitManager.cs +++ b/NetCord/Rest/RateLimits/RateLimitManager.cs @@ -28,9 +28,9 @@ public RateLimitManager(RateLimitManagerConfiguration? configuration = null) _bucketRateLimiters = new(cacheSize); } - public ValueTask GetGlobalRateLimiterAsync() => new(_globalRateLimiter); + public ValueTask GetGlobalRateLimiterAsync(CancellationToken cancellationToken = default) => new(_globalRateLimiter); - public ValueTask GetRouteRateLimiterAsync(Route route) + public ValueTask GetRouteRateLimiterAsync(Route route, CancellationToken cancellationToken = default) { ITrackingRouteRateLimiter? rateLimiter; @@ -50,7 +50,7 @@ public ValueTask GetRouteRateLimiterAsync(Route route) return new(rateLimiter); } - public async ValueTask ExchangeRouteRateLimiterAsync(Route route, RateLimitInfo? rateLimitInfo, BucketInfo? previousBucketInfo) + public ValueTask ExchangeRouteRateLimiterAsync(Route route, RateLimitInfo? rateLimitInfo, BucketInfo? previousBucketInfo, CancellationToken cancellationToken = default) { if (rateLimitInfo is null) { @@ -60,7 +60,7 @@ public async ValueTask ExchangeRouteRateLimiterAsync(Route route, RateLimitInfo? _routeRateLimiters[route] = new NoRateLimitRouteRateLimiter(); } - return; + return default; } ITrackingRouteRateLimiter? rateLimiter; @@ -83,11 +83,11 @@ public async ValueTask ExchangeRouteRateLimiterAsync(Route route, RateLimitInfo? { CleanupRateLimiters(); bucketRateLimiters[bucketInfo] = _routeRateLimiters[route] = new RouteRateLimiter(rateLimitInfo); - return; + return default; } } - await rateLimiter.UpdateAsync(rateLimitInfo).ConfigureAwait(false); + return rateLimiter.UpdateAsync(rateLimitInfo, cancellationToken); } private void CleanupRateLimiters() diff --git a/NetCord/Rest/RateLimits/RouteRateLimiter.cs b/NetCord/Rest/RateLimits/RouteRateLimiter.cs index c7759f49..c3c6f3cf 100644 --- a/NetCord/Rest/RateLimits/RouteRateLimiter.cs +++ b/NetCord/Rest/RateLimits/RouteRateLimiter.cs @@ -15,7 +15,7 @@ internal class RouteRateLimiter(RateLimitInfo rateLimitInfo) : ITrackingRouteRat public long LastAccess { get; private set; } = Environment.TickCount64; - public ValueTask TryAcquireAsync() + public ValueTask TryAcquireAsync(CancellationToken cancellationToken = default) { var timestamp = Environment.TickCount64; lock (_lock) @@ -34,20 +34,27 @@ public ValueTask TryAcquireAsync() _remaining--; } } - return new(RateLimitAcquisitionResult.NoRateLimit()); + return new(RateLimitAcquisitionResult.NoRateLimit); } - public ValueTask CancelAcquireAsync(long timestamp) + public ValueTask CancelAcquireAsync(long acquisitionTimestamp, CancellationToken cancellationToken = default) { + var currentTimestamp = Environment.TickCount64; lock (_lock) { - if (timestamp - (_reset - _maxResetAfter) >= -50 && _remaining < _limit) + var reset = _reset; + var safeStart = reset - _maxResetAfter - 50; + if (acquisitionTimestamp <= reset + && acquisitionTimestamp >= safeStart + && currentTimestamp <= reset + && currentTimestamp >= safeStart + && _remaining < _limit) _remaining++; } return default; } - public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo) + public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo, CancellationToken cancellationToken = default) { lock (_lock) { @@ -67,7 +74,7 @@ public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo) return default; } - public ValueTask IndicateExchangeAsync(long timestamp) => CancelAcquireAsync(timestamp); + public ValueTask IndicateExchangeAsync(long timestamp, CancellationToken cancellationToken = default) => CancelAcquireAsync(timestamp, cancellationToken); public void IndicateAccess() { diff --git a/NetCord/Rest/RateLimits/UnknownRouteRateLimiter.cs b/NetCord/Rest/RateLimits/UnknownRouteRateLimiter.cs index f445c8d5..6505ff1d 100644 --- a/NetCord/Rest/RateLimits/UnknownRouteRateLimiter.cs +++ b/NetCord/Rest/RateLimits/UnknownRouteRateLimiter.cs @@ -11,18 +11,18 @@ internal class UnknownRouteRateLimiter : ITrackingRouteRateLimiter public long LastAccess { get; private set; } = Environment.TickCount64; - public async ValueTask TryAcquireAsync() + public async ValueTask TryAcquireAsync(CancellationToken cancellationToken = default) { - await _semaphore.WaitAsync().ConfigureAwait(false); + await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); if (_retry) - return RateLimitAcquisitionResult.Retry(); + return RateLimitAcquisitionResult.Retry; _retry = true; - return RateLimitAcquisitionResult.NoRateLimit(); + return RateLimitAcquisitionResult.NoRateLimit; } - public ValueTask CancelAcquireAsync(long timestamp) + public ValueTask CancelAcquireAsync(long acquisitionTimestamp, CancellationToken cancellationToken = default) { _retry = false; _semaphore.Release(); @@ -30,7 +30,7 @@ public ValueTask CancelAcquireAsync(long timestamp) return default; } - public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo) + public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo, CancellationToken cancellationToken = default) { _retry = false; _semaphore.Release(); @@ -38,7 +38,7 @@ public ValueTask UpdateAsync(RateLimitInfo rateLimitInfo) return default; } - public ValueTask IndicateExchangeAsync(long timestamp) + public ValueTask IndicateExchangeAsync(long timestamp, CancellationToken cancellationToken = default) { _retry = true; _semaphore.Release(int.MaxValue); diff --git a/NetCord/Rest/ReplyMessageProperties.cs b/NetCord/Rest/ReplyMessageProperties.cs index 0414bf2b..70951117 100644 --- a/NetCord/Rest/ReplyMessageProperties.cs +++ b/NetCord/Rest/ReplyMessageProperties.cs @@ -1,6 +1,6 @@ namespace NetCord.Rest; -public partial class ReplyMessageProperties +public partial class ReplyMessageProperties : IMessageProperties { public string? Content { get; set; } public NonceProperties? Nonce { get; set; } @@ -12,6 +12,7 @@ public partial class ReplyMessageProperties public IEnumerable? Components { get; set; } public IEnumerable? StickerIds { get; set; } public MessageFlags? Flags { get; set; } + public MessagePollProperties? Poll { get; set; } public MessageProperties ToMessageProperties(ulong messageReferenceId) { @@ -23,10 +24,11 @@ public MessageProperties ToMessageProperties(ulong messageReferenceId) Attachments = Attachments, Embeds = Embeds, AllowedMentions = AllowedMentions ?? new(), - MessageReference = new(messageReferenceId, FailIfNotExists.GetValueOrDefault(true)), + MessageReference = MessageReferenceProperties.Reply(messageReferenceId, FailIfNotExists.GetValueOrDefault(true)), Components = Components, StickerIds = StickerIds, Flags = Flags, + Poll = Poll, }; } diff --git a/NetCord/Rest/RestAuditLogEntry.cs b/NetCord/Rest/RestAuditLogEntry.cs index d2d48b48..d72929ea 100644 --- a/NetCord/Rest/RestAuditLogEntry.cs +++ b/NetCord/Rest/RestAuditLogEntry.cs @@ -3,7 +3,7 @@ namespace NetCord.Rest; -public class RestAuditLogEntry(JsonAuditLogEntry jsonModel, RestAuditLogEntryData data) : AuditLogEntry(jsonModel) +public class RestAuditLogEntry(JsonAuditLogEntry jsonModel, RestAuditLogEntryData data, ulong guildId) : AuditLogEntry(jsonModel, guildId) { /// /// Data of objects referenced in the audit log. diff --git a/NetCord/Rest/RestClient.Application.cs b/NetCord/Rest/RestClient.Application.cs index 47f19aaa..6b81e845 100644 --- a/NetCord/Rest/RestClient.Application.cs +++ b/NetCord/Rest/RestClient.Application.cs @@ -3,15 +3,15 @@ public partial class RestClient { [GenerateAlias([typeof(CurrentApplication)], CastType = typeof(Application), Modifiers = ["override"])] - public async Task GetCurrentApplicationAsync(RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/@me", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); + public async Task GetCurrentApplicationAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/@me", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); [GenerateAlias([typeof(CurrentApplication)])] - public async Task ModifyCurrentApplicationAsync(Action action, RestRequestProperties? properties = null) + public async Task ModifyCurrentApplicationAsync(Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { CurrentApplicationOptions currentApplicationOptions = new(); action(currentApplicationOptions); using (HttpContent content = new JsonContent(currentApplicationOptions, Serialization.Default.CurrentApplicationOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/applications/@me", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/applications/@me", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); } } diff --git a/NetCord/Rest/RestClient.ApplicationRoleConnectionMetadata.cs b/NetCord/Rest/RestClient.ApplicationRoleConnectionMetadata.cs index 846d48e2..143b6df7 100644 --- a/NetCord/Rest/RestClient.ApplicationRoleConnectionMetadata.cs +++ b/NetCord/Rest/RestClient.ApplicationRoleConnectionMetadata.cs @@ -2,12 +2,12 @@ public partial class RestClient { - public async Task> GetApplicationRoleConnectionMetadataRecordsAsync(ulong applicationId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/role-connections/metadata", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.IEnumerableJsonApplicationRoleConnectionMetadata).ConfigureAwait(false)).Select(m => new ApplicationRoleConnectionMetadata(m)); + public async Task> GetApplicationRoleConnectionMetadataRecordsAsync(ulong applicationId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/role-connections/metadata", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.IEnumerableJsonApplicationRoleConnectionMetadata).ConfigureAwait(false)).Select(m => new ApplicationRoleConnectionMetadata(m)); - public async Task> UpdateApplicationRoleConnectionMetadataRecordsAsync(ulong applicationId, IEnumerable applicationRoleConnectionMetadataProperties, RestRequestProperties? properties = null) + public async Task> UpdateApplicationRoleConnectionMetadataRecordsAsync(ulong applicationId, IEnumerable applicationRoleConnectionMetadataProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent>(applicationRoleConnectionMetadataProperties, Serialization.Default.IEnumerableApplicationRoleConnectionMetadataProperties)) - return (await (await SendRequestAsync(HttpMethod.Put, $"/applications/{applicationId}/role-connections/metadata", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.IEnumerableJsonApplicationRoleConnectionMetadata).ConfigureAwait(false)).Select(m => new ApplicationRoleConnectionMetadata(m)); + return (await (await SendRequestAsync(HttpMethod.Put, $"/applications/{applicationId}/role-connections/metadata", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.IEnumerableJsonApplicationRoleConnectionMetadata).ConfigureAwait(false)).Select(m => new ApplicationRoleConnectionMetadata(m)); } } diff --git a/NetCord/Rest/RestClient.AuditLog.cs b/NetCord/Rest/RestClient.AuditLog.cs index 9a156cb3..c4aee1e1 100644 --- a/NetCord/Rest/RestClient.AuditLog.cs +++ b/NetCord/Rest/RestClient.AuditLog.cs @@ -19,17 +19,17 @@ public IAsyncEnumerable GetGuildAuditLogAsync(ulong guildId, { var jsonAuditLog = await s.ToObjectAsync(Serialization.Default.JsonAuditLog).ConfigureAwait(false); RestAuditLogEntryData data = new(jsonAuditLog, this); - return jsonAuditLog.AuditLogEntries.Select(e => new RestAuditLogEntry(e, data)); + return jsonAuditLog.AuditLogEntries.Select(e => new RestAuditLogEntry(e, data, guildId)); }, e => e.Id, HttpMethod.Get, $"/guilds/{guildId}/audit-logs", new(paginationProperties.Limit.GetValueOrDefault(), paginationProperties.Direction.GetValueOrDefault(), id => id.ToString(), userId.HasValue ? (actionType.HasValue - ? $"?user_id={userId.GetValueOrDefault()}&action_type={actionType.GetValueOrDefault()}&" + ? $"?user_id={userId.GetValueOrDefault()}&action_type={(int)actionType.GetValueOrDefault()}&" : $"?user_id={userId.GetValueOrDefault()}&") : (actionType.HasValue - ? $"?action_type={actionType.GetValueOrDefault()}&" + ? $"?action_type={(int)actionType.GetValueOrDefault()}&" : "?")), new(guildId), properties); diff --git a/NetCord/Rest/RestClient.AutoModeration.cs b/NetCord/Rest/RestClient.AutoModeration.cs index eac7e0e3..f57004f6 100644 --- a/NetCord/Rest/RestClient.AutoModeration.cs +++ b/NetCord/Rest/RestClient.AutoModeration.cs @@ -5,33 +5,33 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetAutoModerationRulesAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/auto-moderation/rules", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRuleArray).ConfigureAwait(false)).ToDictionary(r => r.Id, r => new AutoModerationRule(r, this)); + public async Task> GetAutoModerationRulesAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/auto-moderation/rules", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRuleArray).ConfigureAwait(false)).Select(r => new AutoModerationRule(r, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(AutoModerationRule)], nameof(AutoModerationRule.GuildId), nameof(AutoModerationRule.Id))] - public async Task GetAutoModerationRuleAsync(ulong guildId, ulong autoModerationRuleId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/auto-moderation/rules/{autoModerationRuleId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRule).ConfigureAwait(false), this); + public async Task GetAutoModerationRuleAsync(ulong guildId, ulong autoModerationRuleId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/auto-moderation/rules/{autoModerationRuleId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRule).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateAutoModerationRuleAsync(ulong guildId, AutoModerationRuleProperties autoModerationRuleProperties, RestRequestProperties? properties = null) + public async Task CreateAutoModerationRuleAsync(ulong guildId, AutoModerationRuleProperties autoModerationRuleProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(autoModerationRuleProperties, Serialization.Default.AutoModerationRuleProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/auto-moderation/rules", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRule).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/auto-moderation/rules", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRule).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(AutoModerationRule)], nameof(AutoModerationRule.GuildId), nameof(AutoModerationRule.Id))] - public async Task ModifyAutoModerationRuleAsync(ulong guildId, ulong autoModerationRuleId, Action action, RestRequestProperties? properties = null) + public async Task ModifyAutoModerationRuleAsync(ulong guildId, ulong autoModerationRuleId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { AutoModerationRuleOptions autoModerationRuleOptions = new(); action(autoModerationRuleOptions); using (HttpContent content = new JsonContent(autoModerationRuleOptions, Serialization.Default.AutoModerationRuleOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/auto-moderation/rules/{autoModerationRuleId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRule).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/auto-moderation/rules/{autoModerationRuleId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAutoModerationRule).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(AutoModerationRule)], nameof(AutoModerationRule.GuildId), nameof(AutoModerationRule.Id))] - public Task DeleteAutoModerationRuleAsync(ulong guildId, ulong autoModerationRuleId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/auto-moderation/rules/{autoModerationRuleId}", null, new(guildId), properties); + public Task DeleteAutoModerationRuleAsync(ulong guildId, ulong autoModerationRuleId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/auto-moderation/rules/{autoModerationRuleId}", null, new(guildId), properties, cancellationToken: cancellationToken); } diff --git a/NetCord/Rest/RestClient.Channel.cs b/NetCord/Rest/RestClient.Channel.cs index 292a6d61..d518c00e 100644 --- a/NetCord/Rest/RestClient.Channel.cs +++ b/NetCord/Rest/RestClient.Channel.cs @@ -1,34 +1,32 @@ -using NetCord.Gateway; - -namespace NetCord.Rest; +namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(Channel)], nameof(Channel.Id), Cast = true)] - public async Task GetChannelAsync(ulong channelId, RestRequestProperties? properties = null) - => Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + public async Task GetChannelAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); [GenerateAlias([typeof(GroupDMChannel)], nameof(GroupDMChannel.Id), Cast = true)] - public async Task ModifyGroupDMChannelAsync(ulong channelId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGroupDMChannelAsync(ulong channelId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GroupDMChannelOptions groupDMChannelOptions = new(); action(groupDMChannelOptions); using (HttpContent content = new JsonContent(groupDMChannelOptions, Serialization.Default.GroupDMChannelOptions)) - return Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/channels/{channelId}", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + return Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/channels/{channelId}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); } [GenerateAlias([typeof(IGuildChannel)], nameof(IGuildChannel.Id), Cast = true)] - public async Task ModifyGuildChannelAsync(ulong channelId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildChannelAsync(ulong channelId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildChannelOptions guildChannelOptions = new(); action(guildChannelOptions); using (HttpContent content = new JsonContent(guildChannelOptions, Serialization.Default.GuildChannelOptions)) - return Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/channels/{channelId}", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + return Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/channels/{channelId}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); } [GenerateAlias([typeof(Channel)], nameof(Channel.Id), Cast = true)] - public async Task DeleteChannelAsync(ulong channelId, RestRequestProperties? properties = null) - => Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + public async Task DeleteChannelAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => Channel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] public IAsyncEnumerable GetMessagesAsync(ulong channelId, PaginationProperties? paginationProperties = null, RestRequestProperties? properties = null) @@ -53,49 +51,51 @@ public IAsyncEnumerable GetMessagesAsync(ulong channelId, Paginatio } [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - public async Task> GetMessagesAroundAsync(ulong channelId, ulong messageId, int? limit = null, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/messages", $"?limit={limit.GetValueOrDefault(100)}&around={messageId}", new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessageArray).ConfigureAwait(false)).ToDictionary(m => m.Id, m => new RestMessage(m, this)); + public async Task> GetMessagesAroundAsync(ulong channelId, ulong messageId, int? limit = null, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/messages", $"?limit={limit.GetValueOrDefault(100)}&around={messageId}", new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessageArray).ConfigureAwait(false)).Select(m => new RestMessage(m, this)).ToArray(); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public async Task GetMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/messages/{messageId}", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public async Task GetMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/messages/{messageId}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), TypeNameOverride = "Message")] - public async Task SendMessageAsync(ulong channelId, MessageProperties message, RestRequestProperties? properties = null) + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), TypeNameOverride = "Message")] + public async Task SendMessageAsync(ulong channelId, MessageProperties message, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = message.Serialize()) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/messages", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/messages", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); } [GenerateAlias([typeof(AnnouncementGuildChannel)], nameof(AnnouncementGuildChannel.Id))] [GenerateAlias([typeof(AnnouncementGuildThread)], nameof(AnnouncementGuildThread.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public async Task CrosspostMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Post, $"/channels/{channelId}/messages/{messageId}/crosspost", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public async Task CrosspostMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Post, $"/channels/{channelId}/messages/{messageId}/crosspost", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task AddMessageReactionAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Put, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}/@me", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task AddMessageReactionAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Put, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}/@me", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task DeleteMessageReactionAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}/@me", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task DeleteMessageReactionAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}/@me", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task DeleteMessageReactionAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, ulong userId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}/{userId}", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task DeleteMessageReactionAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}/{userId}", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public IAsyncEnumerable GetMessageReactionsAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, PaginationProperties? paginationProperties = null, RestRequestProperties? properties = null) + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public IAsyncEnumerable GetMessageReactionsAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, MessageReactionsPaginationProperties? paginationProperties = null, RestRequestProperties? properties = null) { paginationProperties = PaginationProperties.PrepareWithDirectionValidation(paginationProperties, PaginationDirection.After, 100); + var type = paginationProperties.Type; + return new QueryPaginationAsyncEnumerable( this, paginationProperties, @@ -103,40 +103,40 @@ public IAsyncEnumerable GetMessageReactionsAsync(ulong channelId, ulong me u => u.Id, HttpMethod.Get, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}", - new(paginationProperties.Limit.GetValueOrDefault(), paginationProperties.Direction.GetValueOrDefault(), id => id.ToString()), + new(paginationProperties.Limit.GetValueOrDefault(), paginationProperties.Direction.GetValueOrDefault(), id => id.ToString(), type.HasValue ? $"?type={(byte)type.GetValueOrDefault()}&" : "?"), new(channelId), properties); } [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task DeleteAllMessageReactionsAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task DeleteAllMessageReactionsAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task DeleteAllMessageReactionsAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task DeleteAllMessageReactionsAsync(ulong channelId, ulong messageId, ReactionEmojiProperties emoji, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}/reactions/{ReactionEmojiToString(emoji)}", null, new(channelId), properties, cancellationToken: cancellationToken); private static string ReactionEmojiToString(ReactionEmojiProperties emoji) => emoji.Id.HasValue ? $"{emoji.Name}:{emoji.Id.GetValueOrDefault()}" : emoji.Name; [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Action action, RestRequestProperties? properties = null) + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { MessageOptions messageOptions = new(); action(messageOptions); using (HttpContent content = messageOptions.Serialize()) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/channels/{channelId}/messages/{messageId}", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/channels/{channelId}/messages/{messageId}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); } [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task DeleteMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task DeleteMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/messages/{messageId}", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - public Task DeleteMessagesAsync(ulong channelId, IEnumerable messageIds, RestRequestProperties? properties = null) + public Task DeleteMessagesAsync(ulong channelId, IEnumerable messageIds, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { var ids = new ulong[100]; int c = 0; @@ -146,21 +146,21 @@ public Task DeleteMessagesAsync(ulong channelId, IEnumerable messageIds, ids[c] = id; if (c == 99) { - tasks.Add(BulkDeleteMessagesAsync(channelId, ids, properties)); + tasks.Add(BulkDeleteMessagesAsync(channelId, ids, properties, cancellationToken)); c = 0; } else c++; } if (c > 1) - tasks.Add(BulkDeleteMessagesAsync(channelId, new(ids, 0, c), properties)); + tasks.Add(BulkDeleteMessagesAsync(channelId, new(ids, 0, c), properties, cancellationToken)); else if (c == 1) - tasks.Add(DeleteMessageAsync(channelId, ids[0], properties)); + tasks.Add(DeleteMessageAsync(channelId, ids[0], properties, cancellationToken)); return Task.WhenAll(tasks); } [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - public async Task DeleteMessagesAsync(ulong channelId, IAsyncEnumerable messageIds, RestRequestProperties? properties = null) + public async Task DeleteMessagesAsync(ulong channelId, IAsyncEnumerable messageIds, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { var ids = new ulong[100]; int c = 0; @@ -170,137 +170,137 @@ public async Task DeleteMessagesAsync(ulong channelId, IAsyncEnumerable m ids[c] = id; if (c == 99) { - tasks.Add(BulkDeleteMessagesAsync(channelId, ids, properties)); + tasks.Add(BulkDeleteMessagesAsync(channelId, ids, properties, cancellationToken)); c = 0; } else c++; } if (c > 1) - tasks.Add(BulkDeleteMessagesAsync(channelId, new(ids, 0, c), properties)); + tasks.Add(BulkDeleteMessagesAsync(channelId, new(ids, 0, c), properties, cancellationToken)); else if (c == 1) - tasks.Add(DeleteMessageAsync(channelId, ids[0], properties)); + tasks.Add(DeleteMessageAsync(channelId, ids[0], properties, cancellationToken)); await Task.WhenAll(tasks).ConfigureAwait(false); } - private async Task BulkDeleteMessagesAsync(ulong channelId, ArraySegment messageIds, RestRequestProperties? properties = null) + private async Task BulkDeleteMessagesAsync(ulong channelId, ArraySegment messageIds, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new(messageIds), Serialization.Default.BulkDeleteMessagesProperties)) - await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/messages/bulk-delete", null, new(channelId), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/messages/bulk-delete", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); } [GenerateAlias([typeof(IGuildChannel)], nameof(IGuildChannel.Id))] - public async Task ModifyGuildChannelPermissionsAsync(ulong channelId, PermissionOverwriteProperties permissionOverwrite, RestRequestProperties? properties = null) + public async Task ModifyGuildChannelPermissionsAsync(ulong channelId, PermissionOverwriteProperties permissionOverwrite, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(permissionOverwrite, Serialization.Default.PermissionOverwriteProperties)) - await SendRequestAsync(HttpMethod.Put, content, $"/channels/{channelId}/permissions/{permissionOverwrite.Id}", null, new(channelId), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Put, content, $"/channels/{channelId}/permissions/{permissionOverwrite.Id}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); } [GenerateAlias([typeof(IGuildChannel)], nameof(IGuildChannel.Id))] - public async Task> GetGuildChannelInvitesAsync(ulong channelId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/invites", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildInviteArray).ConfigureAwait(false)).Select(r => new RestGuildInvite(r, this)); + public async Task> GetGuildChannelInvitesAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/invites", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestInviteArray).ConfigureAwait(false)).Select(r => new RestInvite(r, this)); [GenerateAlias([typeof(IGuildChannel)], nameof(IGuildChannel.Id))] - public async Task CreateGuildChannelInviteAsync(ulong channelId, GuildInviteProperties? guildInviteProperties = null, RestRequestProperties? properties = null) + public async Task CreateGuildChannelInviteAsync(ulong channelId, InviteProperties? inviteProperties = null, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. { - using (HttpContent content = new JsonContent(guildInviteProperties, Serialization.Default.GuildInviteProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/invites", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildInvite).ConfigureAwait(false), this); + using (HttpContent content = new JsonContent(inviteProperties, Serialization.Default.InviteProperties)) + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/invites", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestInvite).ConfigureAwait(false), this); } #pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. [GenerateAlias([typeof(IGuildChannel)], nameof(IGuildChannel.Id))] - public Task DeleteGuildChannelPermissionAsync(ulong channelId, ulong overwriteId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/permissions/{overwriteId}", null, new(channelId), properties); + public Task DeleteGuildChannelPermissionAsync(ulong channelId, ulong overwriteId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/permissions/{overwriteId}", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(AnnouncementGuildChannel)], nameof(AnnouncementGuildChannel.Id))] [GenerateAlias([typeof(AnnouncementGuildThread)], nameof(AnnouncementGuildThread.Id), TypeNameOverride = nameof(AnnouncementGuildChannel))] - public async Task FollowAnnouncementGuildChannelAsync(ulong channelId, ulong webhookChannelId, RestRequestProperties? properties = null) + public async Task FollowAnnouncementGuildChannelAsync(ulong channelId, ulong webhookChannelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new(webhookChannelId), Serialization.Default.FollowAnnouncementGuildChannelProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/followers", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonFollowedChannel).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/followers", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonFollowedChannel).ConfigureAwait(false), this); } [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - public Task TriggerTypingStateAsync(ulong channelId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Post, $"/channels/{channelId}/typing", null, new(channelId), properties); + public Task TriggerTypingStateAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Post, $"/channels/{channelId}/typing", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - public async Task EnterTypingStateAsync(ulong channelId, RestRequestProperties? properties = null) + public async Task EnterTypingStateAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { TypingReminder typingReminder = new(channelId, this, properties); - await typingReminder.StartAsync().ConfigureAwait(false); + await typingReminder.StartAsync(cancellationToken).ConfigureAwait(false); return typingReminder; } [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - public async Task> GetPinnedMessagesAsync(ulong channelId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/pins", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessageArray).ConfigureAwait(false)).ToDictionary(m => m.Id, m => new RestMessage(m, this)); + public async Task> GetPinnedMessagesAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/pins", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessageArray).ConfigureAwait(false)).Select(m => new RestMessage(m, this)).ToArray(); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task PinMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Put, $"/channels/{channelId}/pins/{messageId}", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task PinMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Put, $"/channels/{channelId}/pins/{messageId}", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public Task UnpinMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/pins/{messageId}", null, new(channelId), properties); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public Task UnpinMessageAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/pins/{messageId}", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(GroupDMChannel)], nameof(GroupDMChannel.Id))] - public async Task GroupDMChannelAddUserAsync(ulong channelId, ulong userId, GroupDMChannelUserAddProperties groupDMChannelUserAddProperties, RestRequestProperties? properties = null) + public async Task GroupDMChannelAddUserAsync(ulong channelId, ulong userId, GroupDMChannelUserAddProperties groupDMChannelUserAddProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(groupDMChannelUserAddProperties, Serialization.Default.GroupDMChannelUserAddProperties)) - await SendRequestAsync(HttpMethod.Put, content, $"/channels/{channelId}/recipients/{userId}", null, new(channelId), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Put, content, $"/channels/{channelId}/recipients/{userId}", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); } [GenerateAlias([typeof(GroupDMChannel)], nameof(GroupDMChannel.Id))] - public Task GroupDMChannelDeleteUserAsync(ulong channelId, ulong userId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/recipients/{userId}", null, new(channelId), properties); + public Task GroupDMChannelDeleteUserAsync(ulong channelId, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{channelId}/recipients/{userId}", null, new(channelId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(TextGuildChannel)], nameof(TextGuildChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] - public async Task CreateGuildThreadAsync(ulong channelId, ulong messageId, GuildThreadFromMessageProperties threadFromMessageProperties, RestRequestProperties? properties = null) + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = "Message")] + public async Task CreateGuildThreadAsync(ulong channelId, ulong messageId, GuildThreadFromMessageProperties threadFromMessageProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(threadFromMessageProperties, Serialization.Default.GuildThreadFromMessageProperties)) - return GuildThread.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/messages/{messageId}/threads", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + return GuildThread.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/messages/{messageId}/threads", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); } [GenerateAlias([typeof(TextGuildChannel)], nameof(TextGuildChannel.Id))] - public async Task CreateGuildThreadAsync(ulong channelId, GuildThreadProperties threadProperties, RestRequestProperties? properties = null) + public async Task CreateGuildThreadAsync(ulong channelId, GuildThreadProperties threadProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(threadProperties, Serialization.Default.GuildThreadProperties)) - return GuildThread.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/threads", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + return GuildThread.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/threads", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); } [GenerateAlias([typeof(ForumGuildChannel)], nameof(ForumGuildChannel.Id))] - public async Task CreateForumGuildThreadAsync(ulong channelId, ForumGuildThreadProperties threadProperties, RestRequestProperties? properties = null) + public async Task CreateForumGuildThreadAsync(ulong channelId, ForumGuildThreadProperties threadProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = threadProperties.Serialize()) - return new ForumGuildThread(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/threads", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + return new ForumGuildThread(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/threads", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); } [GenerateAlias([typeof(GuildThread)], nameof(GuildThread.Id))] - public Task JoinGuildThreadAsync(ulong threadId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Put, $"/channels/{threadId}/thread-members/@me", null, new(threadId), properties); + public Task JoinGuildThreadAsync(ulong threadId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Put, $"/channels/{threadId}/thread-members/@me", null, new(threadId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(GuildThread)], nameof(GuildThread.Id))] - public Task AddGuildThreadUserAsync(ulong threadId, ulong userId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Put, $"/channels/{threadId}/thread-members/{userId}", null, new(threadId), properties); + public Task AddGuildThreadUserAsync(ulong threadId, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Put, $"/channels/{threadId}/thread-members/{userId}", null, new(threadId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(GuildThread)], nameof(GuildThread.Id))] - public Task LeaveGuildThreadAsync(ulong threadId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{threadId}/thread-members/@me", null, new(threadId), properties); + public Task LeaveGuildThreadAsync(ulong threadId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{threadId}/thread-members/@me", null, new(threadId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(GuildThread)], nameof(GuildThread.Id))] [GenerateAlias([typeof(GuildThreadUser)], nameof(GuildThreadUser.ThreadId), nameof(GuildThreadUser.Id))] - public Task DeleteGuildThreadUserAsync(ulong threadId, ulong userId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/channels/{threadId}/thread-members/{userId}", null, new(threadId), properties); + public Task DeleteGuildThreadUserAsync(ulong threadId, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/channels/{threadId}/thread-members/{userId}", null, new(threadId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(GuildThread)], nameof(GuildThread.Id))] - public async Task GetGuildThreadUserAsync(ulong threadId, ulong userId, bool withGuildUser = false, RestRequestProperties? properties = null) + public async Task GetGuildThreadUserAsync(ulong threadId, ulong userId, bool withGuildUser = false, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { - var user = await (await SendRequestAsync(HttpMethod.Get, $"/channels/{threadId}/thread-members/{userId}", $"?with_member={withGuildUser}", new(threadId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonThreadUser).ConfigureAwait(false); + var user = await (await SendRequestAsync(HttpMethod.Get, $"/channels/{threadId}/thread-members/{userId}", $"?with_member={withGuildUser}", new(threadId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonThreadUser).ConfigureAwait(false); return withGuildUser ? new GuildThreadUser(user, this) : new ThreadUser(user, this); } diff --git a/NetCord/Rest/RestClient.Emoji.cs b/NetCord/Rest/RestClient.Emoji.cs index 6492cc67..9eb64fe1 100644 --- a/NetCord/Rest/RestClient.Emoji.cs +++ b/NetCord/Rest/RestClient.Emoji.cs @@ -5,33 +5,64 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildEmojisAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/emojis", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmojiArray).ConfigureAwait(false)).ToDictionary(e => e.Id.GetValueOrDefault(), e => new GuildEmoji(e, guildId, this)); + public async Task> GetGuildEmojisAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/emojis", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmojiArray).ConfigureAwait(false)).Select(e => new GuildEmoji(e, guildId, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildEmoji)], nameof(GuildEmoji.GuildId), nameof(GuildEmoji.Id))] - public async Task GetGuildEmojiAsync(ulong guildId, ulong emojiId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/emojis/{emojiId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), guildId, this); + public async Task GetGuildEmojiAsync(ulong guildId, ulong emojiId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/emojis/{emojiId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), guildId, this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateGuildEmojiAsync(ulong guildId, GuildEmojiProperties guildEmojiProperties, RestRequestProperties? properties = null) + public async Task CreateGuildEmojiAsync(ulong guildId, GuildEmojiProperties guildEmojiProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(guildEmojiProperties, Serialization.Default.GuildEmojiProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/emojis", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), guildId, this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/emojis", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), guildId, this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildEmoji)], nameof(GuildEmoji.GuildId), nameof(GuildEmoji.Id))] - public async Task ModifyGuildEmojiAsync(ulong guildId, ulong emojiId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildEmojiAsync(ulong guildId, ulong emojiId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildEmojiOptions guildEmojiOptions = new(); action(guildEmojiOptions); using (HttpContent content = new JsonContent(guildEmojiOptions, Serialization.Default.GuildEmojiOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/emojis/{emojiId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), guildId, this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/emojis/{emojiId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), guildId, this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildEmoji)], nameof(GuildEmoji.GuildId), nameof(GuildEmoji.Id))] - public Task DeleteGuildEmojiAsync(ulong guildId, ulong emojiId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/emojis/{emojiId}", null, new(guildId), properties); + public Task DeleteGuildEmojiAsync(ulong guildId, ulong emojiId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/emojis/{emojiId}", null, new(guildId), properties, cancellationToken: cancellationToken); + + [GenerateAlias([typeof(Application)], nameof(Application.Id))] + public async Task> GetApplicationEmojisAsync(ulong applicationId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/emojis", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmojiArray).ConfigureAwait(false)).Select(e => new ApplicationEmoji(e, applicationId, this)).ToArray(); + + [GenerateAlias([typeof(Application)], nameof(Application.Id))] + [GenerateAlias([typeof(ApplicationEmoji)], nameof(ApplicationEmoji.ApplicationId), nameof(ApplicationEmoji.Id))] + public async Task GetApplicationEmojiAsync(ulong applicationId, ulong emojiId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/emojis/{emojiId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), applicationId, this); + + [GenerateAlias([typeof(Application)], nameof(Application.Id))] + public async Task CreateApplicationEmojiAsync(ulong applicationId, ApplicationEmojiProperties applicationEmojiProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + { + using (HttpContent content = new JsonContent(applicationEmojiProperties, Serialization.Default.ApplicationEmojiProperties)) + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/applications/{applicationId}/emojis", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), applicationId, this); + } + + [GenerateAlias([typeof(Application)], nameof(Application.Id))] + [GenerateAlias([typeof(ApplicationEmoji)], nameof(ApplicationEmoji.ApplicationId), nameof(ApplicationEmoji.Id))] + public async Task ModifyApplicationEmojiAsync(ulong applicationId, ulong emojiId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + { + ApplicationEmojiOptions applicationEmojiOptions = new(); + action(applicationEmojiOptions); + using (HttpContent content = new JsonContent(applicationEmojiOptions, Serialization.Default.ApplicationEmojiOptions)) + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/applications/{applicationId}/emojis/{emojiId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEmoji).ConfigureAwait(false), applicationId, this); + } + + [GenerateAlias([typeof(Application)], nameof(Application.Id))] + [GenerateAlias([typeof(ApplicationEmoji)], nameof(ApplicationEmoji.ApplicationId), nameof(ApplicationEmoji.Id))] + public Task DeleteApplicationEmojiAsync(ulong applicationId, ulong emojiId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/applications/{applicationId}/emojis/{emojiId}", null, null, properties, cancellationToken: cancellationToken); } diff --git a/NetCord/Rest/RestClient.Gateway.cs b/NetCord/Rest/RestClient.Gateway.cs index 403d25c3..9347ce9c 100644 --- a/NetCord/Rest/RestClient.Gateway.cs +++ b/NetCord/Rest/RestClient.Gateway.cs @@ -2,9 +2,9 @@ public partial class RestClient { - public async Task GetGatewayAsync(RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/gateway", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGateway).ConfigureAwait(false)).Url; + public async Task GetGatewayAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/gateway", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGateway).ConfigureAwait(false)).Url; - public async Task GetGatewayBotAsync(RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/gateway/bot", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGatewayBot).ConfigureAwait(false)); + public async Task GetGatewayBotAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/gateway/bot", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGatewayBot).ConfigureAwait(false)); } diff --git a/NetCord/Rest/RestClient.Guild.cs b/NetCord/Rest/RestClient.Guild.cs index 6f1ca5b5..171546d5 100644 --- a/NetCord/Rest/RestClient.Guild.cs +++ b/NetCord/Rest/RestClient.Guild.cs @@ -6,59 +6,59 @@ namespace NetCord.Rest; public partial class RestClient { - public async Task CreateGuildAsync(GuildProperties guildProperties, RestRequestProperties? properties = null) + public async Task CreateGuildAsync(GuildProperties guildProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(guildProperties, Serialization.Default.GuildProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildAsync(ulong guildId, bool withCounts = false, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}", $"?with_counts={withCounts}", new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); + public async Task GetGuildAsync(ulong guildId, bool withCounts = false, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}", $"?with_counts={withCounts}", new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildPreviewAsync(ulong guildId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/preview", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); + public async Task GetGuildPreviewAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/preview", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyGuildAsync(ulong guildId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildAsync(ulong guildId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildOptions guildOptions = new(); action(guildOptions); using (HttpContent content = new JsonContent(guildOptions, Serialization.Default.GuildOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public Task DeleteGuildAsync(ulong guildId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}", null, new(guildId), properties); + public Task DeleteGuildAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildChannelsAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/channels", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannelArray).ConfigureAwait(false)).ToDictionary(c => c.Id, c => (IGuildChannel)Channel.CreateFromJson(c, this)); + public async Task> GetGuildChannelsAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/channels", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannelArray).ConfigureAwait(false)).Select(c => IGuildChannel.CreateFromJson(c, guildId, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateGuildChannelAsync(ulong guildId, GuildChannelProperties channelProperties, RestRequestProperties? properties = null) + public async Task CreateGuildChannelAsync(ulong guildId, GuildChannelProperties channelProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(channelProperties, Serialization.Default.GuildChannelProperties)) - return IGuildChannel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/channels", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), guildId, this); + return IGuildChannel.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/channels", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), guildId, this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyGuildChannelPositionsAsync(ulong guildId, IEnumerable positions, RestRequestProperties? properties = null) + public async Task ModifyGuildChannelPositionsAsync(ulong guildId, IEnumerable positions, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent>(positions, Serialization.Default.IEnumerableGuildChannelPositionProperties)) - await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/channels", null, new(guildId), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/channels", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetActiveGuildThreadsAsync(ulong guildId, RestRequestProperties? properties = null) - => GuildThreadGenerator.CreateThreads(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/threads/active", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildThreadResult).ConfigureAwait(false), this); + public async Task> GetActiveGuildThreadsAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => GuildThreadGenerator.CreateThreads(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/threads/active", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildThreadResult).ConfigureAwait(false), this).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), nameof(GuildUser.Id), Modifiers = ["new"])] - public async Task GetGuildUserAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); + public async Task GetGuildUserAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] public IAsyncEnumerable GetGuildUsersAsync(ulong guildId, PaginationProperties? paginationProperties = null, RestRequestProperties? properties = null) @@ -78,15 +78,15 @@ public IAsyncEnumerable GetGuildUsersAsync(ulong guildId, PaginationP } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> FindGuildUserAsync(ulong guildId, string name, int limit, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/members/search", $"?query={Uri.EscapeDataString(name)}&limit={limit}", new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUserArray).ConfigureAwait(false)).ToDictionary(u => u.User.Id, u => new GuildUser(u, guildId, this)); + public async Task> FindGuildUserAsync(ulong guildId, string name, int limit, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/members/search", $"?query={Uri.EscapeDataString(name)}&limit={limit}", new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUserArray).ConfigureAwait(false)).Select(u => new GuildUser(u, guildId, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task AddGuildUserAsync(ulong guildId, ulong userId, GuildUserProperties userProperties, RestRequestProperties? properties = null) + public async Task AddGuildUserAsync(ulong guildId, ulong userId, GuildUserProperties userProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { Stream? stream; using (HttpContent content = new JsonContent(userProperties, Serialization.Default.GuildUserProperties)) - stream = await SendRequestAsync(HttpMethod.Put, content, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties).ConfigureAwait(false); + stream = await SendRequestAsync(HttpMethod.Put, content, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); if (stream.Length == 0) return null; else @@ -95,37 +95,37 @@ public async Task> FindGuildUserAsync(ulon [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), nameof(GuildUser.Id))] - public async Task ModifyGuildUserAsync(ulong guildId, ulong userId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildUserAsync(ulong guildId, ulong userId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildUserOptions guildUserOptions = new(); action(guildUserOptions); using (HttpContent content = new JsonContent(guildUserOptions, Serialization.Default.GuildUserOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyCurrentGuildUserAsync(ulong guildId, Action action, RestRequestProperties? properties = null) + public async Task ModifyCurrentGuildUserAsync(ulong guildId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { CurrentGuildUserOptions currentGuildUserOptions = new(); action(currentGuildUserOptions); using (HttpContent content = new JsonContent(currentGuildUserOptions, Serialization.Default.CurrentGuildUserOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/members/@me", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/members/@me", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), nameof(GuildUser.Id))] - public Task AddGuildUserRoleAsync(ulong guildId, ulong userId, ulong roleId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Put, $"/guilds/{guildId}/members/{userId}/roles/{roleId}", null, new(guildId), properties); + public Task AddGuildUserRoleAsync(ulong guildId, ulong userId, ulong roleId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Put, $"/guilds/{guildId}/members/{userId}/roles/{roleId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), nameof(GuildUser.Id))] - public Task RemoveGuildUserRoleAsync(ulong guildId, ulong userId, ulong roleId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/members/{userId}/roles/{roleId}", null, new(guildId), properties); + public Task RemoveGuildUserRoleAsync(ulong guildId, ulong userId, ulong roleId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/members/{userId}/roles/{roleId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), nameof(GuildUser.Id))] - public Task KickGuildUserAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties); + public Task KickGuildUserAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/members/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] public IAsyncEnumerable GetGuildBansAsync(ulong guildId, PaginationProperties? paginationProperties = null, RestRequestProperties? properties = null) @@ -150,72 +150,72 @@ public IAsyncEnumerable GetGuildBansAsync(ulong guildId, PaginationPro } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildBanAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/bans/{userId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildBan).ConfigureAwait(false), guildId, this); + public async Task GetGuildBanAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/bans/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildBan).ConfigureAwait(false), guildId, this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), nameof(GuildUser.Id))] - public async Task BanGuildUserAsync(ulong guildId, ulong userId, int deleteMessageSeconds = 0, RestRequestProperties? properties = null) + public async Task BanGuildUserAsync(ulong guildId, ulong userId, int deleteMessageSeconds = 0, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new(deleteMessageSeconds), Serialization.Default.GuildBanProperties)) - await SendRequestAsync(HttpMethod.Put, content, $"/guilds/{guildId}/bans/{userId}", null, new(guildId), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Put, content, $"/guilds/{guildId}/bans/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task BanGuildUsersAsync(ulong guildId, IEnumerable userIds, int deleteMessageSeconds = 0, RestRequestProperties? properties = null) + public async Task BanGuildUsersAsync(ulong guildId, IEnumerable userIds, int deleteMessageSeconds = 0, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new(userIds, deleteMessageSeconds), Serialization.Default.GuildBulkBanProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/bulk-ban", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildBulkBan).ConfigureAwait(false)); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/bulk-ban", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildBulkBan).ConfigureAwait(false)); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildBan)], nameof(GuildBan.GuildId), $"{nameof(GuildBan.User)}.{nameof(GuildBan.User.Id)}", NameOverride = "DeleteAsync", ClientName = "client")] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), nameof(GuildUser.Id))] - public Task UnbanGuildUserAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/bans/{userId}", null, new(guildId), properties); + public Task UnbanGuildUserAsync(ulong guildId, ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/bans/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildRolesAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/roles", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRoleArray).ConfigureAwait(false)).ToDictionary(r => r.Id, r => new Role(r, guildId, this)); + public async Task> GetGuildRolesAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/roles", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRoleArray).ConfigureAwait(false)).Select(r => new Role(r, guildId, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateGuildRoleAsync(ulong guildId, RoleProperties guildRoleProperties, RestRequestProperties? properties = null) + public async Task CreateGuildRoleAsync(ulong guildId, RoleProperties guildRoleProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(guildRoleProperties, Serialization.Default.RoleProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/roles", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRole).ConfigureAwait(false), guildId, this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/roles", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRole).ConfigureAwait(false), guildId, this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> ModifyGuildRolePositionsAsync(ulong guildId, IEnumerable positions, RestRequestProperties? properties = null) + public async Task> ModifyGuildRolePositionsAsync(ulong guildId, IEnumerable positions, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent>(positions, Serialization.Default.IEnumerableRolePositionProperties)) - return (await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/roles", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRoleArray).ConfigureAwait(false)).ToDictionary(r => r.Id, r => new Role(r, guildId, this)); + return (await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/roles", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRoleArray).ConfigureAwait(false)).Select(r => new Role(r, guildId, this)).ToArray(); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(Role)], nameof(Role.Id), TypeNameOverride = $"{nameof(Guild)}{nameof(Role)}")] - public async Task ModifyGuildRoleAsync(ulong guildId, ulong roleId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildRoleAsync(ulong guildId, ulong roleId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { RoleOptions obj = new(); action(obj); using (HttpContent content = new JsonContent(obj, Serialization.Default.RoleOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/roles/{roleId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRole).ConfigureAwait(false), guildId, this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/roles/{roleId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRole).ConfigureAwait(false), guildId, this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(Role)], nameof(Role.Id), TypeNameOverride = $"{nameof(Guild)}{nameof(Role)}")] - public Task DeleteGuildRoleAsync(ulong guildId, ulong roleId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/roles/{roleId}", null, new(guildId), properties); + public Task DeleteGuildRoleAsync(ulong guildId, ulong roleId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/roles/{roleId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyGuildMfaLevelAsync(ulong guildId, MfaLevel mfaLevel, RestRequestProperties? properties = null) + public async Task ModifyGuildMfaLevelAsync(ulong guildId, MfaLevel mfaLevel, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new GuildMfaLevelProperties(mfaLevel), Serialization.Default.GuildMfaLevelProperties)) - return (await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/mfa", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildMfaLevel).ConfigureAwait(false)).Level; + return (await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/mfa", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildMfaLevel).ConfigureAwait(false)).Level; } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildPruneCountAsync(ulong guildId, int days, IEnumerable? roles = null, RestRequestProperties? properties = null) + public async Task GetGuildPruneCountAsync(ulong guildId, int days, IEnumerable? roles = null, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { var query = roles is null ? $"?days={days}" @@ -225,95 +225,95 @@ public async Task GetGuildPruneCountAsync(ulong guildId, int days, IEnumera .Append("&include_roles=") .AppendJoin(',', roles) .ToString(); - return (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/prune", query, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildPruneCountResult).ConfigureAwait(false)).Pruned; + return (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/prune", query, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildPruneCountResult).ConfigureAwait(false)).Pruned; } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GuildPruneAsync(ulong guildId, GuildPruneProperties pruneProperties, RestRequestProperties? properties = null) + public async Task GuildPruneAsync(ulong guildId, GuildPruneProperties pruneProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(pruneProperties, Serialization.Default.GuildPruneProperties)) - return (await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/prune", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildPruneResult).ConfigureAwait(false)).Pruned; + return (await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/prune", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildPruneResult).ConfigureAwait(false)).Pruned; } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildVoiceRegionsAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/regions", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonVoiceRegionArray).ConfigureAwait(false)).Select(r => new VoiceRegion(r)); + public async Task> GetGuildVoiceRegionsAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/regions", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonVoiceRegionArray).ConfigureAwait(false)).Select(r => new VoiceRegion(r)); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildInvitesAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/invites", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildInviteArray).ConfigureAwait(false)).Select(i => new RestGuildInvite(i, this)); + public async Task> GetGuildInvitesAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/invites", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestInviteArray).ConfigureAwait(false)).Select(i => new RestInvite(i, this)); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildIntegrationsAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/integrations", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonIntegrationArray).ConfigureAwait(false)).ToDictionary(i => i.Id, i => new Integration(i, this)); + public async Task> GetGuildIntegrationsAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/integrations", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonIntegrationArray).ConfigureAwait(false)).Select(i => new Integration(i, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public Task DeleteGuildIntegrationAsync(ulong guildId, ulong integrationId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/integrations/{integrationId}", null, new(guildId), properties); + public Task DeleteGuildIntegrationAsync(ulong guildId, ulong integrationId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/integrations/{integrationId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildWidgetSettingsAsync(ulong guildId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/widget", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWidgetSettings).ConfigureAwait(false)); + public async Task GetGuildWidgetSettingsAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/widget", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWidgetSettings).ConfigureAwait(false)); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyGuildWidgetSettingsAsync(ulong guildId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildWidgetSettingsAsync(ulong guildId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildWidgetSettingsOptions guildWidgetSettingsOptions = new(); action(guildWidgetSettingsOptions); using (HttpContent content = new JsonContent(guildWidgetSettingsOptions, Serialization.Default.GuildWidgetSettingsOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/widget", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWidgetSettings).ConfigureAwait(false)); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/widget", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWidgetSettings).ConfigureAwait(false)); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildWidgetAsync(ulong guildId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/widget.json", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWidget).ConfigureAwait(false), this); + public async Task GetGuildWidgetAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/widget.json", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWidget).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildVanityInviteAsync(ulong guildId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/vanity-url", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildVanityInvite).ConfigureAwait(false)); + public async Task GetGuildVanityInviteAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/vanity-url", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildVanityInvite).ConfigureAwait(false)); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildWelcomeScreenAsync(ulong guildId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/welcome-screen", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWelcomeScreen).ConfigureAwait(false)); + public async Task GetGuildWelcomeScreenAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/welcome-screen", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWelcomeScreen).ConfigureAwait(false)); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyGuildWelcomeScreenAsync(ulong guildId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildWelcomeScreenAsync(ulong guildId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildWelcomeScreenOptions guildWelcomeScreenOptions = new(); action(guildWelcomeScreenOptions); using (HttpContent content = new JsonContent(guildWelcomeScreenOptions, Serialization.Default.GuildWelcomeScreenOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/welcome-screen", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWelcomeScreen).ConfigureAwait(false)); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/welcome-screen", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildWelcomeScreen).ConfigureAwait(false)); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildOnboardingAsync(ulong guildId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/onboarding", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildOnboarding).ConfigureAwait(false), this); + public async Task GetGuildOnboardingAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/onboarding", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildOnboarding).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyGuildOnboardingAsync(ulong guildId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildOnboardingAsync(ulong guildId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildOnboardingOptions guildOnboardingOptions = new(); action(guildOnboardingOptions); using (HttpContent content = new JsonContent(guildOnboardingOptions, Serialization.Default.GuildOnboardingOptions)) - return new(await (await SendRequestAsync(HttpMethod.Put, content, $"/guilds/{guildId}/onboarding", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildOnboarding).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Put, content, $"/guilds/{guildId}/onboarding", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildOnboarding).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyCurrentGuildUserVoiceStateAsync(ulong guildId, Action action, RestRequestProperties? properties = null) + public async Task ModifyCurrentGuildUserVoiceStateAsync(ulong guildId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { CurrentUserVoiceStateOptions obj = new(); action(obj); using (HttpContent content = new JsonContent(obj, Serialization.Default.CurrentUserVoiceStateOptions)) - await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/voice-states/@me", null, new(guildId), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/voice-states/@me", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildUser)], nameof(GuildUser.GuildId), null, nameof(GuildUser.Id))] - public async Task ModifyGuildUserVoiceStateAsync(ulong guildId, ulong channelId, ulong userId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildUserVoiceStateAsync(ulong guildId, ulong channelId, ulong userId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { VoiceStateOptions obj = new(channelId); action(obj); using (HttpContent content = new JsonContent(obj, Serialization.Default.VoiceStateOptions)) - await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/voice-states/{userId}", null, new(guildId), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/voice-states/{userId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false); } } diff --git a/NetCord/Rest/RestClient.GuildScheduledEvent.cs b/NetCord/Rest/RestClient.GuildScheduledEvent.cs index af0a078e..9e092f7d 100644 --- a/NetCord/Rest/RestClient.GuildScheduledEvent.cs +++ b/NetCord/Rest/RestClient.GuildScheduledEvent.cs @@ -5,35 +5,35 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildScheduledEventsAsync(ulong guildId, bool withUserCount = false, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/scheduled-events", $"?with_user_count={withUserCount}", new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEventArray).ConfigureAwait(false)).ToDictionary(e => e.Id, e => new GuildScheduledEvent(e, this)); + public async Task> GetGuildScheduledEventsAsync(ulong guildId, bool withUserCount = false, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/scheduled-events", $"?with_user_count={withUserCount}", new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEventArray).ConfigureAwait(false)).Select(e => new GuildScheduledEvent(e, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateGuildScheduledEventAsync(ulong guildId, GuildScheduledEventProperties guildScheduledEventProperties, RestRequestProperties? properties = null) + public async Task CreateGuildScheduledEventAsync(ulong guildId, GuildScheduledEventProperties guildScheduledEventProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(guildScheduledEventProperties, Serialization.Default.GuildScheduledEventProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/scheduled-events", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEvent).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/scheduled-events", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEvent).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildScheduledEvent)], nameof(GuildScheduledEvent.GuildId), nameof(GuildScheduledEvent.Id))] - public async Task GetGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, bool withUserCount = false, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/scheduled-events/{scheduledEventId}", $"?with_user_count={withUserCount}", new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEvent).ConfigureAwait(false), this); + public async Task GetGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, bool withUserCount = false, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/scheduled-events/{scheduledEventId}", $"?with_user_count={withUserCount}", new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEvent).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildScheduledEvent)], nameof(GuildScheduledEvent.GuildId), nameof(GuildScheduledEvent.Id))] - public async Task ModifyGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildScheduledEventOptions options = new(); action(options); using (HttpContent content = new JsonContent(options, Serialization.Default.GuildScheduledEventOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/scheduled-events/{scheduledEventId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEvent).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/scheduled-events/{scheduledEventId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildScheduledEvent).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildScheduledEvent)], nameof(GuildScheduledEvent.GuildId), nameof(GuildScheduledEvent.Id))] - public Task DeleteGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/scheduled-events/{scheduledEventId}", null, new(guildId), properties); + public Task DeleteGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/scheduled-events/{scheduledEventId}", null, new(guildId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildScheduledEvent)], nameof(GuildScheduledEvent.GuildId), nameof(GuildScheduledEvent.Id))] diff --git a/NetCord/Rest/RestClient.GuildTemplate.cs b/NetCord/Rest/RestClient.GuildTemplate.cs index 21f80c4f..34f9565f 100644 --- a/NetCord/Rest/RestClient.GuildTemplate.cs +++ b/NetCord/Rest/RestClient.GuildTemplate.cs @@ -5,44 +5,44 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(GuildTemplate)], nameof(GuildTemplate.Code))] - public async Task GetGuildTemplateAsync(string templateCode, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/templates/{templateCode}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); + public async Task GetGuildTemplateAsync(string templateCode, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/templates/{templateCode}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); [GenerateAlias([typeof(GuildTemplate)], nameof(GuildTemplate.Code), NameOverride = "CreateGuildAsync")] - public async Task CreateGuildFromGuildTemplateAsync(string templateCode, GuildFromGuildTemplateProperties guildProperties, RestRequestProperties? properties = null) + public async Task CreateGuildFromGuildTemplateAsync(string templateCode, GuildFromGuildTemplateProperties guildProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(guildProperties, Serialization.Default.GuildFromGuildTemplateProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/templates/{templateCode}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/templates/{templateCode}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuild).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildTemplatesAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/templates", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplateArray).ConfigureAwait(false)).Select(t => new GuildTemplate(t, this)); + public async Task> GetGuildTemplatesAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/templates", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplateArray).ConfigureAwait(false)).Select(t => new GuildTemplate(t, this)); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateGuildTemplateAsync(ulong guildId, GuildTemplateProperties guildTemplateProperties, RestRequestProperties? properties = null) + public async Task CreateGuildTemplateAsync(ulong guildId, GuildTemplateProperties guildTemplateProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(guildTemplateProperties, Serialization.Default.GuildTemplateProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/templates", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/templates", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildTemplate)], nameof(GuildTemplate.SourceGuildId), nameof(GuildTemplate.Code))] - public async Task SyncGuildTemplateAsync(ulong guildId, string templateCode, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Put, $"/guilds/{guildId}/templates/{templateCode}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); + public async Task SyncGuildTemplateAsync(ulong guildId, string templateCode, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Put, $"/guilds/{guildId}/templates/{templateCode}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildTemplate)], nameof(GuildTemplate.SourceGuildId), nameof(GuildTemplate.Code))] - public async Task ModifyGuildTemplateAsync(ulong guildId, string templateCode, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildTemplateAsync(ulong guildId, string templateCode, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildTemplateOptions guildTemplateOptions = new(); action(guildTemplateOptions); using (HttpContent content = new JsonContent(guildTemplateOptions, Serialization.Default.GuildTemplateOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/templates/{templateCode}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/templates/{templateCode}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildTemplate)], nameof(GuildTemplate.SourceGuildId), nameof(GuildTemplate.Code))] - public async Task DeleteGuildTemplateAsync(ulong guildId, string templateCode, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/templates/{templateCode}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); + public async Task DeleteGuildTemplateAsync(ulong guildId, string templateCode, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/templates/{templateCode}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildTemplate).ConfigureAwait(false), this); } diff --git a/NetCord/Rest/RestClient.Interactions.ApplicationCommands.cs b/NetCord/Rest/RestClient.Interactions.ApplicationCommands.cs index a7cf0236..9505ee3b 100644 --- a/NetCord/Rest/RestClient.Interactions.ApplicationCommands.cs +++ b/NetCord/Rest/RestClient.Interactions.ApplicationCommands.cs @@ -4,92 +4,92 @@ namespace NetCord.Rest; public partial class RestClient { - public async Task> GetGlobalApplicationCommandsAsync(ulong applicationId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/commands", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).ToDictionary(c => c.Id, c => new ApplicationCommand(c, this)); + public async Task> GetGlobalApplicationCommandsAsync(ulong applicationId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/commands", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).Select(c => new ApplicationCommand(c, this)).ToArray(); - public async Task CreateGlobalApplicationCommandAsync(ulong applicationId, ApplicationCommandProperties applicationCommandProperties, RestRequestProperties? properties = null) + public async Task CreateGlobalApplicationCommandAsync(ulong applicationId, ApplicationCommandProperties applicationCommandProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(applicationCommandProperties, Serialization.Default.ApplicationCommandProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/applications/{applicationId}/commands", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/applications/{applicationId}/commands", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); } [GenerateAlias([typeof(ApplicationCommand)], nameof(ApplicationCommand.ApplicationId), nameof(ApplicationCommand.Id), Modifiers = ["virtual"], TypeNameOverride = $"Global{nameof(ApplicationCommand)}")] - public async Task GetGlobalApplicationCommandAsync(ulong applicationId, ulong commandId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/commands/{commandId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); + public async Task GetGlobalApplicationCommandAsync(ulong applicationId, ulong commandId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/commands/{commandId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); [GenerateAlias([typeof(ApplicationCommand)], nameof(ApplicationCommand.ApplicationId), nameof(ApplicationCommand.Id), Modifiers = ["virtual"], TypeNameOverride = $"Global{nameof(ApplicationCommand)}")] - public async Task ModifyGlobalApplicationCommandAsync(ulong applicationId, ulong commandId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGlobalApplicationCommandAsync(ulong applicationId, ulong commandId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { ApplicationCommandOptions applicationCommandOptions = new(); action(applicationCommandOptions); using (HttpContent content = new JsonContent(applicationCommandOptions, Serialization.Default.ApplicationCommandOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/applications/{applicationId}/commands/{commandId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/applications/{applicationId}/commands/{commandId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); } [GenerateAlias([typeof(ApplicationCommand)], nameof(ApplicationCommand.ApplicationId), nameof(ApplicationCommand.Id), Modifiers = ["virtual"], TypeNameOverride = $"Global{nameof(ApplicationCommand)}")] - public Task DeleteGlobalApplicationCommandAsync(ulong applicationId, ulong commandId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/applications/{applicationId}/commands/{commandId}", null, null, properties); + public Task DeleteGlobalApplicationCommandAsync(ulong applicationId, ulong commandId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/applications/{applicationId}/commands/{commandId}", null, null, properties, cancellationToken: cancellationToken); - public async Task> BulkOverwriteGlobalApplicationCommandsAsync(ulong applicationId, IEnumerable commands, RestRequestProperties? properties = null) + public async Task> BulkOverwriteGlobalApplicationCommandsAsync(ulong applicationId, IEnumerable commands, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent>(commands, Serialization.Default.IEnumerableApplicationCommandProperties)) - return (await (await SendRequestAsync(HttpMethod.Put, content, $"/applications/{applicationId}/commands", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).ToDictionary(c => c.Id, c => new ApplicationCommand(c, this)); + return (await (await SendRequestAsync(HttpMethod.Put, content, $"/applications/{applicationId}/commands", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).Select(c => new ApplicationCommand(c, this)).ToArray(); } [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildApplicationCommandsAsync(ulong applicationId, ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).ToDictionary(c => c.Id, c => new GuildApplicationCommand(c, this)); + public async Task> GetGuildApplicationCommandsAsync(ulong applicationId, ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).Select(c => new GuildApplicationCommand(c, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ApplicationCommandProperties applicationCommandProperties, RestRequestProperties? properties = null) + public async Task CreateGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ApplicationCommandProperties applicationCommandProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(applicationCommandProperties, Serialization.Default.ApplicationCommandProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/applications/{applicationId}/guilds/{guildId}/commands", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/applications/{applicationId}/guilds/{guildId}/commands", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); } [GenerateAlias([typeof(GuildApplicationCommand)], nameof(GuildApplicationCommand.ApplicationId), nameof(GuildApplicationCommand.GuildId), nameof(GuildApplicationCommand.Id), Modifiers = ["override"], CastType = typeof(ApplicationCommand))] [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); + public async Task GetGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); [GenerateAlias([typeof(GuildApplicationCommand)], nameof(GuildApplicationCommand.ApplicationId), nameof(GuildApplicationCommand.GuildId), nameof(GuildApplicationCommand.Id), Modifiers = ["override"], CastType = typeof(ApplicationCommand))] [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task ModifyGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { ApplicationCommandOptions applicationCommandOptions = new(); action(applicationCommandOptions); using (HttpContent content = new JsonContent(applicationCommandOptions, Serialization.Default.ApplicationCommandOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommand).ConfigureAwait(false), this); } [GenerateAlias([typeof(GuildApplicationCommand)], nameof(GuildApplicationCommand.ApplicationId), nameof(GuildApplicationCommand.GuildId), nameof(GuildApplicationCommand.Id), Modifiers = ["override"])] [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public Task DeleteGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}", null, null, properties); + public Task DeleteGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}", null, null, properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> BulkOverwriteGuildApplicationCommandsAsync(ulong applicationId, ulong guildId, IEnumerable commands, RestRequestProperties? properties = null) + public async Task> BulkOverwriteGuildApplicationCommandsAsync(ulong applicationId, ulong guildId, IEnumerable commands, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent>(commands, Serialization.Default.IEnumerableApplicationCommandProperties)) - return (await (await SendRequestAsync(HttpMethod.Put, content, $"/applications/{applicationId}/guilds/{guildId}/commands", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).ToDictionary(c => c.Id, c => new GuildApplicationCommand(c, this)); + return (await (await SendRequestAsync(HttpMethod.Put, content, $"/applications/{applicationId}/guilds/{guildId}/commands", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandArray).ConfigureAwait(false)).Select(c => new GuildApplicationCommand(c, this)).ToArray(); } [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetApplicationCommandsGuildPermissionsAsync(ulong applicationId, ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands/permissions", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandGuildPermissionsArray).ConfigureAwait(false)).ToDictionary(p => p.CommandId, p => new ApplicationCommandGuildPermissions(p)); + public async Task> GetApplicationCommandsGuildPermissionsAsync(ulong applicationId, ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands/permissions", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandGuildPermissionsArray).ConfigureAwait(false)).Select(p => new ApplicationCommandGuildPermissions(p)).ToArray(); [GenerateAlias([typeof(ApplicationCommand)], nameof(ApplicationCommand.ApplicationId), null, nameof(ApplicationCommand.Id))] [GenerateAlias([typeof(GuildApplicationCommand)], nameof(GuildApplicationCommand.ApplicationId), nameof(GuildApplicationCommand.GuildId), nameof(GuildApplicationCommand.Id), TypeNameOverride = "ApplicationCommandGuild")] [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task GetApplicationCommandGuildPermissionsAsync(ulong applicationId, ulong guildId, ulong commandId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}/permissions", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandGuildPermissions).ConfigureAwait(false)); + public async Task GetApplicationCommandGuildPermissionsAsync(ulong applicationId, ulong guildId, ulong commandId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}/permissions", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandGuildPermissions).ConfigureAwait(false)); [GenerateAlias([typeof(ApplicationCommand)], nameof(ApplicationCommand.ApplicationId), null, nameof(ApplicationCommand.Id))] [GenerateAlias([typeof(GuildApplicationCommand)], nameof(GuildApplicationCommand.ApplicationId), nameof(GuildApplicationCommand.GuildId), nameof(GuildApplicationCommand.Id), TypeNameOverride = "ApplicationCommandGuild")] [GenerateAlias([typeof(RestGuild)], null, nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task OverwriteApplicationCommandGuildPermissionsAsync(ulong applicationId, ulong guildId, ulong commandId, IEnumerable newPermissions, RestRequestProperties? properties = null) + public async Task OverwriteApplicationCommandGuildPermissionsAsync(ulong applicationId, ulong guildId, ulong commandId, IEnumerable newPermissions, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new(newPermissions), Serialization.Default.ApplicationCommandGuildPermissionsProperties)) - return new(await (await SendRequestAsync(HttpMethod.Put, content, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}/permissions", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandGuildPermissions).ConfigureAwait(false)); + return new(await (await SendRequestAsync(HttpMethod.Put, content, $"/applications/{applicationId}/guilds/{guildId}/commands/{commandId}/permissions", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationCommandGuildPermissions).ConfigureAwait(false)); } } diff --git a/NetCord/Rest/RestClient.Interactions.ReceivingAndResponding.cs b/NetCord/Rest/RestClient.Interactions.ReceivingAndResponding.cs index 51b3a31a..84d7e5f1 100644 --- a/NetCord/Rest/RestClient.Interactions.ReceivingAndResponding.cs +++ b/NetCord/Rest/RestClient.Interactions.ReceivingAndResponding.cs @@ -2,50 +2,50 @@ public partial class RestClient { - public async Task SendInteractionResponseAsync(ulong interactionId, string interactionToken, InteractionCallback callback, RestRequestProperties? properties = null) + public async Task SendInteractionResponseAsync(ulong interactionId, string interactionToken, InteractionCallback callback, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = callback.Serialize()) - await SendRequestAsync(HttpMethod.Post, content, $"/interactions/{interactionId}/{interactionToken}/callback", null, new(interactionId, interactionToken), properties, false).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Post, content, $"/interactions/{interactionId}/{interactionToken}/callback", null, new(interactionId, interactionToken), properties, false, cancellationToken: cancellationToken).ConfigureAwait(false); } [GenerateAlias([typeof(Interaction)], nameof(Interaction.ApplicationId), nameof(Interaction.Token))] - public async Task GetInteractionResponseAsync(ulong applicationId, string interactionToken, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{applicationId}/{interactionToken}/messages/@original", null, new(applicationId, interactionToken), properties, false).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + public async Task GetInteractionResponseAsync(ulong applicationId, string interactionToken, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{applicationId}/{interactionToken}/messages/@original", null, new(applicationId, interactionToken), properties, false, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); [GenerateAlias([typeof(Interaction)], nameof(Interaction.ApplicationId), nameof(Interaction.Token))] - public async Task ModifyInteractionResponseAsync(ulong applicationId, string interactionToken, Action action, RestRequestProperties? properties = null) + public async Task ModifyInteractionResponseAsync(ulong applicationId, string interactionToken, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { MessageOptions messageOptions = new(); action(messageOptions); using (HttpContent content = messageOptions.Serialize()) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{applicationId}/{interactionToken}/messages/@original", null, new(applicationId, interactionToken), properties, false).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{applicationId}/{interactionToken}/messages/@original", null, new(applicationId, interactionToken), properties, false, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); } [GenerateAlias([typeof(Interaction)], nameof(Interaction.ApplicationId), nameof(Interaction.Token))] - public Task DeleteInteractionResponseAsync(ulong applicationId, string interactionToken, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{applicationId}/{interactionToken}/messages/@original", null, new(applicationId, interactionToken), properties, false); + public Task DeleteInteractionResponseAsync(ulong applicationId, string interactionToken, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{applicationId}/{interactionToken}/messages/@original", null, new(applicationId, interactionToken), properties, false, cancellationToken: cancellationToken); [GenerateAlias([typeof(Interaction)], nameof(Interaction.ApplicationId), nameof(Interaction.Token))] - public async Task SendInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, InteractionMessageProperties message, RestRequestProperties? properties = null) + public async Task SendInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, InteractionMessageProperties message, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = message.Serialize()) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/webhooks/{applicationId}/{interactionToken}", null, new(applicationId, interactionToken), properties, false).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/webhooks/{applicationId}/{interactionToken}", null, new(applicationId, interactionToken), properties, false, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); } [GenerateAlias([typeof(Interaction)], nameof(Interaction.ApplicationId), nameof(Interaction.Token))] - public async Task GetInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{applicationId}/{interactionToken}/messages/{messageId}", null, new(applicationId, interactionToken), properties, false).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + public async Task GetInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{applicationId}/{interactionToken}/messages/{messageId}", null, new(applicationId, interactionToken), properties, false, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); [GenerateAlias([typeof(Interaction)], nameof(Interaction.ApplicationId), nameof(Interaction.Token))] - public async Task ModifyInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, Action action, RestRequestProperties? properties = null) + public async Task ModifyInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { MessageOptions messageOptions = new(); action(messageOptions); using (HttpContent content = messageOptions.Serialize()) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{applicationId}/{interactionToken}/messages/{messageId}", null, new(applicationId, interactionToken), properties, false).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{applicationId}/{interactionToken}/messages/{messageId}", null, new(applicationId, interactionToken), properties, false, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); } [GenerateAlias([typeof(Interaction)], nameof(Interaction.ApplicationId), nameof(Interaction.Token))] - public Task DeleteInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{applicationId}/{interactionToken}/messages/{messageId}", null, new(applicationId, interactionToken), properties, false); + public Task DeleteInteractionFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{applicationId}/{interactionToken}/messages/{messageId}", null, new(applicationId, interactionToken), properties, false, cancellationToken: cancellationToken); } diff --git a/NetCord/Rest/RestClient.Invite.cs b/NetCord/Rest/RestClient.Invite.cs index 1374084c..effd5101 100644 --- a/NetCord/Rest/RestClient.Invite.cs +++ b/NetCord/Rest/RestClient.Invite.cs @@ -4,16 +4,16 @@ namespace NetCord.Rest; public partial class RestClient { - [GenerateAlias([typeof(RestGuildInvite)], nameof(RestGuildInvite.Code), TypeNameOverride = nameof(GuildInvite))] - public async Task GetGuildInviteAsync(string inviteCode, bool withCounts = false, bool withExpiration = false, ulong? guildScheduledEventId = null, RestRequestProperties? properties = null) + [GenerateAlias([typeof(RestInvite)], nameof(RestInvite.Code), TypeNameOverride = nameof(Invite))] + public async Task GetGuildInviteAsync(string inviteCode, bool withCounts = false, bool withExpiration = false, ulong? guildScheduledEventId = null, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { if (guildScheduledEventId.HasValue) - return new(await (await SendRequestAsync(HttpMethod.Get, $"/invites/{inviteCode}", $"?with_counts={withCounts}&with_expiration={withExpiration}&guild_scheduled_event_id={guildScheduledEventId}", null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildInvite).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Get, $"/invites/{inviteCode}", $"?with_counts={withCounts}&with_expiration={withExpiration}&guild_scheduled_event_id={guildScheduledEventId}", null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestInvite).ConfigureAwait(false), this); else - return new(await (await SendRequestAsync(HttpMethod.Get, $"/invites/{inviteCode}", $"?with_counts={withCounts}&with_expiration={withExpiration}", null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildInvite).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Get, $"/invites/{inviteCode}", $"?with_counts={withCounts}&with_expiration={withExpiration}", null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestInvite).ConfigureAwait(false), this); } - [GenerateAlias([typeof(RestGuildInvite)], nameof(RestGuildInvite.Code), TypeNameOverride = nameof(GuildInvite))] - public async Task DeleteGuildInviteAsync(string inviteCode, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Delete, $"/invites/{inviteCode}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestGuildInvite).ConfigureAwait(false), this); + [GenerateAlias([typeof(RestInvite)], nameof(RestInvite.Code), TypeNameOverride = nameof(Invite))] + public async Task DeleteGuildInviteAsync(string inviteCode, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Delete, $"/invites/{inviteCode}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonRestInvite).ConfigureAwait(false), this); } diff --git a/NetCord/Rest/RestClient.Monetization.Entitlements.cs b/NetCord/Rest/RestClient.Monetization.Entitlements.cs index 8ad5fb0b..57bf1a68 100644 --- a/NetCord/Rest/RestClient.Monetization.Entitlements.cs +++ b/NetCord/Rest/RestClient.Monetization.Entitlements.cs @@ -44,15 +44,15 @@ public IAsyncEnumerable GetEntitlementsAsync(ulong applicationId, E properties); } - public Task ConsumeEntitlementAsync(ulong applicationId, ulong entitlementId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Post, $"/applications/{applicationId}/entitlements/{entitlementId}/consume", null, null, properties); + public Task ConsumeEntitlementAsync(ulong applicationId, ulong entitlementId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Post, $"/applications/{applicationId}/entitlements/{entitlementId}/consume", null, null, properties, cancellationToken: cancellationToken); - public async Task CreateTestEntitlementAsync(ulong applicationId, TestEntitlementProperties testEntitlementProperties, RestRequestProperties? properties = null) + public async Task CreateTestEntitlementAsync(ulong applicationId, TestEntitlementProperties testEntitlementProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(testEntitlementProperties, Serialization.Default.TestEntitlementProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/applications/{applicationId}/entitlements", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEntitlement).ConfigureAwait(false)); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/applications/{applicationId}/entitlements", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonEntitlement).ConfigureAwait(false)); } - public Task DeleteTestEntitlementAsync(ulong applicationId, ulong entitlementId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/applications/{applicationId}/entitlements/{entitlementId}", null, null, properties); + public Task DeleteTestEntitlementAsync(ulong applicationId, ulong entitlementId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/applications/{applicationId}/entitlements/{entitlementId}", null, null, properties, cancellationToken: cancellationToken); } diff --git a/NetCord/Rest/RestClient.Monetization.Skus.cs b/NetCord/Rest/RestClient.Monetization.Skus.cs index 14afa87e..3882fbb5 100644 --- a/NetCord/Rest/RestClient.Monetization.Skus.cs +++ b/NetCord/Rest/RestClient.Monetization.Skus.cs @@ -2,6 +2,6 @@ public partial class RestClient { - public async Task> GetSkusAsync(ulong applicationId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/skus", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSkuArray).ConfigureAwait(false)).Select(s => new Sku(s)).ToArray(); + public async Task> GetSkusAsync(ulong applicationId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/skus", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSkuArray).ConfigureAwait(false)).Select(s => new Sku(s)).ToArray(); } diff --git a/NetCord/Rest/RestClient.OAuth2.cs b/NetCord/Rest/RestClient.OAuth2.cs index 3347ebd1..ce8334cb 100644 --- a/NetCord/Rest/RestClient.OAuth2.cs +++ b/NetCord/Rest/RestClient.OAuth2.cs @@ -2,9 +2,9 @@ public partial class RestClient { - public async Task GetCurrentBotApplicationInformationAsync(RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/oauth2/applications/@me", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); + public async Task GetCurrentBotApplicationInformationAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/oauth2/applications/@me", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); - public async Task GetCurrentAuthorizationInformationAsync(RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/oauth2/@me", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAuthorizationInformation).ConfigureAwait(false), this); + public async Task GetCurrentAuthorizationInformationAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/oauth2/@me", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonAuthorizationInformation).ConfigureAwait(false), this); } diff --git a/NetCord/Rest/RestClient.Poll.cs b/NetCord/Rest/RestClient.Poll.cs index edfe1494..78d12144 100644 --- a/NetCord/Rest/RestClient.Poll.cs +++ b/NetCord/Rest/RestClient.Poll.cs @@ -5,7 +5,7 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = nameof(Message))] + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = nameof(Message))] public IAsyncEnumerable GetMessagePollAnswerVotersAsync(ulong channelId, ulong messageId, int answerId, PaginationProperties? paginationProperties = null, RestRequestProperties? properties = null) { paginationProperties = PaginationProperties.PrepareWithDirectionValidation(paginationProperties, PaginationDirection.After, 100); @@ -23,7 +23,7 @@ public IAsyncEnumerable GetMessagePollAnswerVotersAsync(ulong channelId, u } [GenerateAlias([typeof(TextChannel)], nameof(TextChannel.Id))] - [GenerateAlias([typeof(RestMessage), typeof(IPartialMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = nameof(Message))] - public async Task EndMessagePollAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Post, $"/channels/{channelId}/polls/{messageId}/expire", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + [GenerateAlias([typeof(RestMessage)], nameof(RestMessage.ChannelId), nameof(RestMessage.Id), TypeNameOverride = nameof(Message))] + public async Task EndMessagePollAsync(ulong channelId, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Post, $"/channels/{channelId}/polls/{messageId}/expire", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); } diff --git a/NetCord/Rest/RestClient.StageInstance.cs b/NetCord/Rest/RestClient.StageInstance.cs index 4541520e..57bfa5a6 100644 --- a/NetCord/Rest/RestClient.StageInstance.cs +++ b/NetCord/Rest/RestClient.StageInstance.cs @@ -2,29 +2,29 @@ public partial class RestClient { - public async Task CreateStageInstanceAsync(StageInstanceProperties stageInstanceProperties, RestRequestProperties? properties = null) + public async Task CreateStageInstanceAsync(StageInstanceProperties stageInstanceProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(stageInstanceProperties, Serialization.Default.StageInstanceProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/stage-instances", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStageInstance).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/stage-instances", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStageInstance).ConfigureAwait(false), this); } [GenerateAlias([typeof(StageGuildChannel)], nameof(StageGuildChannel.Id))] [GenerateAlias([typeof(StageInstance)], nameof(StageInstance.ChannelId))] - public async Task GetStageInstanceAsync(ulong channelId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/stage-instances/{channelId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStageInstance).ConfigureAwait(false), this); + public async Task GetStageInstanceAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/stage-instances/{channelId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStageInstance).ConfigureAwait(false), this); [GenerateAlias([typeof(StageGuildChannel)], nameof(StageGuildChannel.Id))] [GenerateAlias([typeof(StageInstance)], nameof(StageInstance.ChannelId))] - public async Task ModifyStageInstanceAsync(ulong channelId, Action action, RestRequestProperties? properties = null) + public async Task ModifyStageInstanceAsync(ulong channelId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { StageInstanceOptions stageInstanceOptions = new(); action(stageInstanceOptions); using (HttpContent content = new JsonContent(stageInstanceOptions, Serialization.Default.StageInstanceOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/stage-instances/{channelId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStageInstance).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/stage-instances/{channelId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStageInstance).ConfigureAwait(false), this); } [GenerateAlias([typeof(StageGuildChannel)], nameof(StageGuildChannel.Id))] [GenerateAlias([typeof(StageInstance)], nameof(StageInstance.ChannelId))] - public Task DeleteStageInstanceAsync(ulong channelId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/stage-instances/{channelId}", null, null, properties); + public Task DeleteStageInstanceAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/stage-instances/{channelId}", null, null, properties, cancellationToken: cancellationToken); } diff --git a/NetCord/Rest/RestClient.Sticker.cs b/NetCord/Rest/RestClient.Sticker.cs index 47bc9968..1724a63c 100644 --- a/NetCord/Rest/RestClient.Sticker.cs +++ b/NetCord/Rest/RestClient.Sticker.cs @@ -4,40 +4,40 @@ namespace NetCord.Rest; public partial class RestClient { - public async Task GetStickerAsync(ulong stickerId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/stickers/{stickerId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false)); + public async Task GetStickerAsync(ulong stickerId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/stickers/{stickerId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false)); - public async Task> GetStickerPacksAsync(RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/sticker-packs", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStickerPacks).ConfigureAwait(false)).StickerPacks.ToDictionary(s => s.Id, s => new StickerPack(s)); + public async Task> GetStickerPacksAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/sticker-packs", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStickerPacks).ConfigureAwait(false)).StickerPacks.Select(s => new StickerPack(s)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildStickersAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/stickers", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStickerArray).ConfigureAwait(false)).ToDictionary(s => s.Id, s => new GuildSticker(s, this)); + public async Task> GetGuildStickersAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/stickers", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonStickerArray).ConfigureAwait(false)).Select(s => new GuildSticker(s, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildSticker)], nameof(GuildSticker.GuildId), nameof(GuildSticker.Id))] - public async Task GetGuildStickerAsync(ulong guildId, ulong stickerId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/stickers/{stickerId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false), this); + public async Task GetGuildStickerAsync(ulong guildId, ulong stickerId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/stickers/{stickerId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false), this); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task CreateGuildStickerAsync(ulong guildId, GuildStickerProperties sticker, RestRequestProperties? properties = null) + public async Task CreateGuildStickerAsync(ulong guildId, GuildStickerProperties sticker, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = sticker.Serialize()) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/stickers", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/guilds/{guildId}/stickers", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildSticker)], nameof(GuildSticker.GuildId), nameof(GuildSticker.Id))] - public async Task ModifyGuildStickerAsync(ulong guildId, ulong stickerId, Action action, RestRequestProperties? properties = null) + public async Task ModifyGuildStickerAsync(ulong guildId, ulong stickerId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { GuildStickerOptions guildStickerOptions = new(); action(guildStickerOptions); using (HttpContent content = new JsonContent(guildStickerOptions, Serialization.Default.GuildStickerOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/stickers/{stickerId}", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/guilds/{guildId}/stickers/{stickerId}", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonSticker).ConfigureAwait(false), this); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] [GenerateAlias([typeof(GuildSticker)], nameof(GuildSticker.GuildId), nameof(GuildSticker.Id))] - public Task DeleteGuildStickerAsync(ulong guildId, ulong stickerId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/stickers/{stickerId}", null, new(guildId), properties); + public Task DeleteGuildStickerAsync(ulong guildId, ulong stickerId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/guilds/{guildId}/stickers/{stickerId}", null, new(guildId), properties, cancellationToken: cancellationToken); } diff --git a/NetCord/Rest/RestClient.Undocumented.cs b/NetCord/Rest/RestClient.Undocumented.cs index 50948855..302fccd8 100644 --- a/NetCord/Rest/RestClient.Undocumented.cs +++ b/NetCord/Rest/RestClient.Undocumented.cs @@ -5,14 +5,14 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(Application)], nameof(Application.Id), Modifiers = ["virtual"])] - public async Task GetApplicationAsync(ulong applicationId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/rpc", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); + public async Task GetApplicationAsync(ulong applicationId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/applications/{applicationId}/rpc", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplication).ConfigureAwait(false), this); [GenerateAlias([typeof(Channel)], nameof(Channel.Id))] - public async Task> CreateGoogleCloudPlatformStorageBucketsAsync(ulong channelId, IEnumerable buckets, RestRequestProperties? properties = null) + public async Task> CreateGoogleCloudPlatformStorageBucketsAsync(ulong channelId, IEnumerable buckets, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new(buckets), Serialization.Default.GoogleCloudPlatformStorageBucketsProperties)) - return (await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/attachments", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonCreateGoogleCloudPlatformStorageBucketResult).ConfigureAwait(false)).Buckets.Select(a => new GoogleCloudPlatformStorageBucket(a)); + return (await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/attachments", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonCreateGoogleCloudPlatformStorageBucketResult).ConfigureAwait(false)).Buckets.Select(a => new GoogleCloudPlatformStorageBucket(a)); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] diff --git a/NetCord/Rest/RestClient.User.cs b/NetCord/Rest/RestClient.User.cs index 634f5d88..17ac78c3 100644 --- a/NetCord/Rest/RestClient.User.cs +++ b/NetCord/Rest/RestClient.User.cs @@ -5,20 +5,20 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(CurrentUser)], Modifiers = ["new"])] - public async Task GetCurrentUserAsync(RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/@me", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonUser).ConfigureAwait(false), this); + public async Task GetCurrentUserAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/@me", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonUser).ConfigureAwait(false), this); [GenerateAlias([typeof(User)], nameof(User.Id))] - public async Task GetUserAsync(ulong userId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/{userId}", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonUser).ConfigureAwait(false), this); + public async Task GetUserAsync(ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/{userId}", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonUser).ConfigureAwait(false), this); [GenerateAlias([typeof(CurrentUser)])] - public async Task ModifyCurrentUserAsync(Action action, RestRequestProperties? properties = null) + public async Task ModifyCurrentUserAsync(Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { CurrentUserOptions currentUserOptions = new(); action(currentUserOptions); using (HttpContent content = new JsonContent(currentUserOptions, Serialization.Default.CurrentUserOptions)) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/users/@me", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonUser).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/users/@me", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonUser).ConfigureAwait(false), this); } [GenerateAlias([typeof(CurrentUser)])] @@ -39,39 +39,40 @@ public IAsyncEnumerable GetCurrentUserGuildsAsync(GuildsPaginationPro } [GenerateAlias([typeof(CurrentUser)])] - public async Task GetCurrentUserGuildUserAsync(ulong guildId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/@me/guilds/{guildId}/member", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); + [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id))] + public async Task GetCurrentUserGuildUserAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/@me/guilds/{guildId}/member", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonGuildUser).ConfigureAwait(false), guildId, this); [GenerateAlias([typeof(CurrentUser)])] [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public Task LeaveGuildAsync(ulong guildId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/users/@me/guilds/{guildId}", null, null, properties); + public Task LeaveGuildAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/users/@me/guilds/{guildId}", null, null, properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(User)], nameof(User.Id))] - public async Task GetDMChannelAsync(ulong userId, RestRequestProperties? properties = null) + public async Task GetDMChannelAsync(ulong userId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(new(userId), Serialization.Default.DMChannelProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/users/@me/channels", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/users/@me/channels", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); } - public async Task CreateGroupDMChannelAsync(GroupDMChannelProperties groupDMChannelProperties, RestRequestProperties? properties = null) + public async Task CreateGroupDMChannelAsync(GroupDMChannelProperties groupDMChannelProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(groupDMChannelProperties, Serialization.Default.GroupDMChannelProperties)) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/users/@me/channels", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/users/@me/channels", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonChannel).ConfigureAwait(false), this); } [GenerateAlias([typeof(CurrentUser)])] - public async Task> GetCurrentUserConnectionsAsync(RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/users/@me/connections", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonConnectionArray).ConfigureAwait(false)).ToDictionary(c => c.Id, c => new Connection(c, this)); + public async Task> GetCurrentUserConnectionsAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/users/@me/connections", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonConnectionArray).ConfigureAwait(false)).Select(c => new Connection(c, this)).ToArray(); [GenerateAlias([typeof(CurrentUser)])] - public async Task GetCurrentUserApplicationRoleConnectionAsync(ulong applicationId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/@me/applications/{applicationId}/role-connection", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationRoleConnection).ConfigureAwait(false)); + public async Task GetCurrentUserApplicationRoleConnectionAsync(ulong applicationId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/users/@me/applications/{applicationId}/role-connection", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationRoleConnection).ConfigureAwait(false)); [GenerateAlias([typeof(CurrentUser)])] - public async Task UpdateCurrentUserApplicationRoleConnectionAsync(ulong applicationId, ApplicationRoleConnectionProperties applicationRoleConnectionProperties, RestRequestProperties? properties = null) + public async Task UpdateCurrentUserApplicationRoleConnectionAsync(ulong applicationId, ApplicationRoleConnectionProperties applicationRoleConnectionProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(applicationRoleConnectionProperties, Serialization.Default.ApplicationRoleConnectionProperties)) - return new(await (await SendRequestAsync(HttpMethod.Put, content, $"/users/@me/applications/{applicationId}/role-connection", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationRoleConnection).ConfigureAwait(false)); + return new(await (await SendRequestAsync(HttpMethod.Put, content, $"/users/@me/applications/{applicationId}/role-connection", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonApplicationRoleConnection).ConfigureAwait(false)); } } diff --git a/NetCord/Rest/RestClient.Voice.cs b/NetCord/Rest/RestClient.Voice.cs index 2ed74773..a09123a4 100644 --- a/NetCord/Rest/RestClient.Voice.cs +++ b/NetCord/Rest/RestClient.Voice.cs @@ -2,6 +2,6 @@ public partial class RestClient { - public async Task> GetVoiceRegionsAsync(RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/voice/regions", null, null, properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonVoiceRegionArray).ConfigureAwait(false)).Select(r => new VoiceRegion(r)); + public async Task> GetVoiceRegionsAsync(RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/voice/regions", null, null, properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonVoiceRegionArray).ConfigureAwait(false)).Select(r => new VoiceRegion(r)); } diff --git a/NetCord/Rest/RestClient.Webhook.cs b/NetCord/Rest/RestClient.Webhook.cs index b1af4c6a..b7cfe180 100644 --- a/NetCord/Rest/RestClient.Webhook.cs +++ b/NetCord/Rest/RestClient.Webhook.cs @@ -5,68 +5,68 @@ namespace NetCord.Rest; public partial class RestClient { [GenerateAlias([typeof(TextGuildChannel)], nameof(TextGuildChannel.Id))] - public async Task CreateWebhookAsync(ulong channelId, WebhookProperties webhookProperties, RestRequestProperties? properties = null) + public async Task CreateWebhookAsync(ulong channelId, WebhookProperties webhookProperties, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = new JsonContent(webhookProperties, Serialization.Default.WebhookProperties)) - return Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/webhooks", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); + return Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Post, content, $"/channels/{channelId}/webhooks", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); } [GenerateAlias([typeof(TextGuildChannel)], nameof(TextGuildChannel.Id))] - public async Task> GetChannelWebhooksAsync(ulong channelId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/webhooks", null, new(channelId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhookArray).ConfigureAwait(false)).ToDictionary(w => w.Id, w => Webhook.CreateFromJson(w, this)); + public async Task> GetChannelWebhooksAsync(ulong channelId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/channels/{channelId}/webhooks", null, new(channelId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhookArray).ConfigureAwait(false)).Select(w => Webhook.CreateFromJson(w, this)).ToArray(); [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] - public async Task> GetGuildWebhooksAsync(ulong guildId, RestRequestProperties? properties = null) - => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/webhooks", null, new(guildId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhookArray).ConfigureAwait(false)).ToDictionary(w => w.Id, w => Webhook.CreateFromJson(w, this)); + public async Task> GetGuildWebhooksAsync(ulong guildId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => (await (await SendRequestAsync(HttpMethod.Get, $"/guilds/{guildId}/webhooks", null, new(guildId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhookArray).ConfigureAwait(false)).Select(w => Webhook.CreateFromJson(w, this)).ToArray(); [GenerateAlias([typeof(Webhook)], nameof(Webhook.Id), Cast = true)] - public async Task GetWebhookAsync(ulong webhookId, RestRequestProperties? properties = null) - => Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{webhookId}", null, new(webhookId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); + public async Task GetWebhookAsync(ulong webhookId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{webhookId}", null, new(webhookId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); [GenerateAlias([typeof(IncomingWebhook)], nameof(IncomingWebhook.Id), nameof(IncomingWebhook.Token), Cast = true, TypeNameOverride = nameof(Webhook))] [GenerateAlias([typeof(WebhookClient)], nameof(WebhookClient.Id), nameof(WebhookClient.Token), TypeNameOverride = $"{nameof(Webhook)}WithToken")] - public async Task GetWebhookWithTokenAsync(ulong webhookId, string webhookToken, RestRequestProperties? properties = null) - => Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{webhookId}/{webhookToken}", null, new(webhookId, webhookToken), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); + public async Task GetWebhookWithTokenAsync(ulong webhookId, string webhookToken, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{webhookId}/{webhookToken}", null, new(webhookId, webhookToken), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); [GenerateAlias([typeof(Webhook)], nameof(Webhook.Id), Cast = true)] - public async Task ModifyWebhookAsync(ulong webhookId, Action action, RestRequestProperties? properties = null) + public async Task ModifyWebhookAsync(ulong webhookId, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { WebhookOptions webhookOptions = new(); action(webhookOptions); using (HttpContent content = new JsonContent(webhookOptions, Serialization.Default.WebhookOptions)) - return Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{webhookId}", null, new(webhookId), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); + return Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{webhookId}", null, new(webhookId), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); } [GenerateAlias([typeof(IncomingWebhook)], nameof(IncomingWebhook.Id), nameof(IncomingWebhook.Token), Cast = true, TypeNameOverride = nameof(Webhook))] [GenerateAlias([typeof(WebhookClient)], nameof(WebhookClient.Id), nameof(WebhookClient.Token), TypeNameOverride = $"{nameof(Webhook)}WithToken")] - public async Task ModifyWebhookWithTokenAsync(ulong webhookId, string webhookToken, Action action, RestRequestProperties? properties = null) + public async Task ModifyWebhookWithTokenAsync(ulong webhookId, string webhookToken, Action action, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { WebhookOptions webhookOptions = new(); action(webhookOptions); using (HttpContent content = new JsonContent(webhookOptions, Serialization.Default.WebhookOptions)) - return Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{webhookId}/{webhookToken}", null, new(webhookId, webhookToken), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); + return Webhook.CreateFromJson(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{webhookId}/{webhookToken}", null, new(webhookId, webhookToken), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonWebhook).ConfigureAwait(false), this); } [GenerateAlias([typeof(Webhook)], nameof(Webhook.Id))] - public Task DeleteWebhookAsync(ulong webhookId, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{webhookId}", null, new(webhookId), properties); + public Task DeleteWebhookAsync(ulong webhookId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{webhookId}", null, new(webhookId), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(IncomingWebhook)], nameof(IncomingWebhook.Id), nameof(IncomingWebhook.Token), TypeNameOverride = nameof(Webhook))] [GenerateAlias([typeof(WebhookClient)], nameof(WebhookClient.Id), nameof(WebhookClient.Token), TypeNameOverride = $"{nameof(Webhook)}WithToken")] - public Task DeleteWebhookWithTokenAsync(ulong webhookId, string webhookToken, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{webhookId}/{webhookToken}", null, new(webhookId, webhookToken), properties); + public Task DeleteWebhookWithTokenAsync(ulong webhookId, string webhookToken, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{webhookId}/{webhookToken}", null, new(webhookId, webhookToken), properties, cancellationToken: cancellationToken); [GenerateAlias([typeof(IncomingWebhook)], nameof(IncomingWebhook.Id), nameof(IncomingWebhook.Token), TypeNameOverride = nameof(Webhook))] [GenerateAlias([typeof(WebhookClient)], nameof(WebhookClient.Id), nameof(WebhookClient.Token), TypeNameOverride = nameof(Webhook))] - public async Task ExecuteWebhookAsync(ulong webhookId, string webhookToken, WebhookMessageProperties message, bool wait = false, ulong? threadId = null, RestRequestProperties? properties = null) + public async Task ExecuteWebhookAsync(ulong webhookId, string webhookToken, WebhookMessageProperties message, bool wait = false, ulong? threadId = null, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { using (HttpContent content = message.Serialize()) { if (wait) - return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/webhooks/{webhookId}/{webhookToken}", threadId.HasValue ? $"?wait=True&thread_id={threadId.GetValueOrDefault()}" : $"?wait=True", new(webhookId, webhookToken), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Post, content, $"/webhooks/{webhookId}/{webhookToken}", threadId.HasValue ? $"?wait=True&thread_id={threadId.GetValueOrDefault()}" : $"?wait=True", new(webhookId, webhookToken), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); else { - await SendRequestAsync(HttpMethod.Post, content, $"/webhooks/{webhookId}/{webhookToken}", threadId.HasValue ? $"?thread_id={threadId.GetValueOrDefault()}" : null, new(webhookId, webhookToken), properties).ConfigureAwait(false); + await SendRequestAsync(HttpMethod.Post, content, $"/webhooks/{webhookId}/{webhookToken}", threadId.HasValue ? $"?thread_id={threadId.GetValueOrDefault()}" : null, new(webhookId, webhookToken), properties, cancellationToken: cancellationToken).ConfigureAwait(false); return null; } } @@ -74,21 +74,21 @@ public Task DeleteWebhookWithTokenAsync(ulong webhookId, string webhookToken, Re [GenerateAlias([typeof(IncomingWebhook)], nameof(IncomingWebhook.Id), nameof(IncomingWebhook.Token), TypeNameOverride = nameof(Webhook))] [GenerateAlias([typeof(WebhookClient)], nameof(WebhookClient.Id), nameof(WebhookClient.Token), TypeNameOverride = nameof(Webhook))] - public async Task GetWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, RestRequestProperties? properties = null) - => new(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{webhookId}/{webhookToken}/messages/{messageId}", null, new(webhookId, webhookToken), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + public async Task GetWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => new(await (await SendRequestAsync(HttpMethod.Get, $"/webhooks/{webhookId}/{webhookToken}/messages/{messageId}", null, new(webhookId, webhookToken), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); [GenerateAlias([typeof(IncomingWebhook)], nameof(IncomingWebhook.Id), nameof(IncomingWebhook.Token), TypeNameOverride = nameof(Webhook))] [GenerateAlias([typeof(WebhookClient)], nameof(WebhookClient.Id), nameof(WebhookClient.Token), TypeNameOverride = nameof(Webhook))] - public async Task ModifyWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, Action action, ulong? threadId = null, RestRequestProperties? properties = null) + public async Task ModifyWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, Action action, ulong? threadId = null, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) { MessageOptions messageOptions = new(); action(messageOptions); using (HttpContent content = messageOptions.Serialize()) - return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{webhookId}/{webhookToken}/messages/{messageId}", threadId.HasValue ? $"?thread_id={threadId.GetValueOrDefault()}" : null, new(webhookId, webhookToken), properties).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); + return new(await (await SendRequestAsync(HttpMethod.Patch, content, $"/webhooks/{webhookId}/{webhookToken}/messages/{messageId}", threadId.HasValue ? $"?thread_id={threadId.GetValueOrDefault()}" : null, new(webhookId, webhookToken), properties, cancellationToken: cancellationToken).ConfigureAwait(false)).ToObjectAsync(Serialization.Default.JsonMessage).ConfigureAwait(false), this); } [GenerateAlias([typeof(IncomingWebhook)], nameof(IncomingWebhook.Id), nameof(IncomingWebhook.Token), TypeNameOverride = nameof(Webhook))] [GenerateAlias([typeof(WebhookClient)], nameof(WebhookClient.Id), nameof(WebhookClient.Token), TypeNameOverride = nameof(Webhook))] - public Task DeleteWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, ulong? threadId = null, RestRequestProperties? properties = null) - => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{webhookId}/{webhookToken}/messages/{messageId}", threadId.HasValue ? $"?thread_id={threadId.GetValueOrDefault()}" : null, new(webhookId, webhookToken), properties); + public Task DeleteWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, ulong? threadId = null, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendRequestAsync(HttpMethod.Delete, $"/webhooks/{webhookId}/{webhookToken}/messages/{messageId}", threadId.HasValue ? $"?thread_id={threadId.GetValueOrDefault()}" : null, new(webhookId, webhookToken), properties, cancellationToken: cancellationToken); } diff --git a/NetCord/Rest/RestClient.cs b/NetCord/Rest/RestClient.cs index d5ec60ce..5e25470b 100644 --- a/NetCord/Rest/RestClient.cs +++ b/NetCord/Rest/RestClient.cs @@ -45,13 +45,13 @@ public RestClient(IToken token, RestClientConfiguration? configuration = null) : _requestHandler.AddDefaultHeader("Authorization", [token.HttpHeaderValue]); } - public Task SendRequestAsync(HttpMethod method, FormattableString route, string? query = null, TopLevelResourceInfo? resourceInfo = null, RestRequestProperties? properties = null, bool global = true) + public Task SendRequestAsync(HttpMethod method, FormattableString route, string? query = null, TopLevelResourceInfo? resourceInfo = null, RestRequestProperties? properties = null, bool global = true, CancellationToken cancellationToken = default) { properties ??= _defaultRequestProperties; var url = $"{_baseUrl}{route}{query}"; - return SendRequestAsync(new(method, route.Format, resourceInfo), global, CreateMessage, properties); + return SendRequestAsync(new(method, route.Format, resourceInfo), global, CreateMessage, properties, cancellationToken); HttpRequestMessage CreateMessage() { @@ -65,13 +65,13 @@ HttpRequestMessage CreateMessage() } } - public Task SendRequestAsync(HttpMethod method, HttpContent content, FormattableString route, string? query = null, TopLevelResourceInfo? resourceInfo = null, RestRequestProperties? properties = null, bool global = true) + public Task SendRequestAsync(HttpMethod method, HttpContent content, FormattableString route, string? query = null, TopLevelResourceInfo? resourceInfo = null, RestRequestProperties? properties = null, bool global = true, CancellationToken cancellationToken = default) { properties ??= _defaultRequestProperties; var url = $"{_baseUrl}{route}{query}"; - return SendRequestAsync(new(method, route.Format, resourceInfo), global, CreateMessage, properties); + return SendRequestAsync(new(method, route.Format, resourceInfo), global, CreateMessage, properties, cancellationToken); HttpRequestMessage CreateMessage() { @@ -88,23 +88,23 @@ HttpRequestMessage CreateMessage() } } - private async Task SendRequestAsync(Route route, bool global, Func messageFunc, RestRequestProperties properties) + private async Task SendRequestAsync(Route route, bool global, Func messageFunc, RestRequestProperties properties, CancellationToken cancellationToken) { while (true) { - var globalRateLimiter = global ? await AcquireGlobalRateLimiterAsync(properties).ConfigureAwait(false) : null; + var globalRateLimiter = global ? await AcquireGlobalRateLimiterAsync(properties, cancellationToken).ConfigureAwait(false) : null; - var rateLimiter = await AcquireRouteRateLimiterAsync(route, properties).ConfigureAwait(false); + var rateLimiter = await AcquireRouteRateLimiterAsync(route, properties, cancellationToken).ConfigureAwait(false); var timestamp = Environment.TickCount64; HttpResponseMessage response; try { - response = await _requestHandler.SendAsync(messageFunc()).ConfigureAwait(false); + response = await _requestHandler.SendAsync(messageFunc(), cancellationToken).ConfigureAwait(false); } catch { - await rateLimiter.CancelAcquireAsync(timestamp).ConfigureAwait(false); + await rateLimiter.CancelAcquireAsync(timestamp, default).ConfigureAwait(false); throw; } @@ -124,17 +124,17 @@ private async Task SendRequestAsync(Route route, bool global, Func SendRequestAsync(Route route, bool global, Func SendRequestAsync(Route route, bool global, Func SendRequestAsync(Route route, bool global, Func SendRequestAsync(Route route, bool global, Func AcquireGlobalRateLimiterAsync(RestRequestProperties properties) + private async ValueTask AcquireGlobalRateLimiterAsync(RestRequestProperties properties, CancellationToken cancellationToken) { while (true) { - var rateLimiter = await _rateLimitManager.GetGlobalRateLimiterAsync().ConfigureAwait(false); - var info = await rateLimiter.TryAcquireAsync().ConfigureAwait(false); + var rateLimiter = await _rateLimitManager.GetGlobalRateLimiterAsync(cancellationToken).ConfigureAwait(false); + var info = await rateLimiter.TryAcquireAsync(cancellationToken).ConfigureAwait(false); if (info.RateLimited) { if (properties.RateLimitHandling.HasFlag(RestRateLimitHandling.RetryGlobal)) - await Task.Delay(info.ResetAfter).ConfigureAwait(false); + await Task.Delay(info.ResetAfter, cancellationToken).ConfigureAwait(false); else throw new RateLimitedException(Environment.TickCount64 + info.ResetAfter, RateLimitScope.Global); } @@ -227,16 +227,16 @@ private async ValueTask AcquireGlobalRateLimiterAsync(RestRe } } - private async ValueTask AcquireRouteRateLimiterAsync(Route route, RestRequestProperties properties) + private async ValueTask AcquireRouteRateLimiterAsync(Route route, RestRequestProperties properties, CancellationToken cancellationToken) { while (true) { - var rateLimiter = await _rateLimitManager.GetRouteRateLimiterAsync(route).ConfigureAwait(false); - var info = await rateLimiter.TryAcquireAsync().ConfigureAwait(false); + var rateLimiter = await _rateLimitManager.GetRouteRateLimiterAsync(route, cancellationToken).ConfigureAwait(false); + var info = await rateLimiter.TryAcquireAsync(cancellationToken).ConfigureAwait(false); if (info.RateLimited) { if (properties.RateLimitHandling.HasFlag(RestRateLimitHandling.RetryUser)) - await Task.Delay(info.ResetAfter).ConfigureAwait(false); + await Task.Delay(info.ResetAfter, cancellationToken).ConfigureAwait(false); else throw new RateLimitedException(Environment.TickCount64 + info.ResetAfter, RateLimitScope.User); } diff --git a/NetCord/Rest/RestException.cs b/NetCord/Rest/RestException.cs index 764fd8b6..17bcbbf6 100644 --- a/NetCord/Rest/RestException.cs +++ b/NetCord/Rest/RestException.cs @@ -4,34 +4,45 @@ namespace NetCord.Rest; public class RestException : Exception { - public RestException(HttpStatusCode statusCode, string reasonPhrase) : base(GetMessage(statusCode, reasonPhrase)) + public RestException(HttpStatusCode statusCode, string? reasonPhrase) : base(GetMessage(statusCode, reasonPhrase)) { StatusCode = statusCode; ReasonPhrase = reasonPhrase; } - public RestException(HttpStatusCode statusCode, string reasonPhrase, RestError error) : base(GetMessage(statusCode, reasonPhrase, error.Message)) + public RestException(HttpStatusCode statusCode, string? reasonPhrase, RestError error) : base(GetMessage(statusCode, reasonPhrase, error.Message)) { StatusCode = statusCode; - ReasonPhrase = reasonPhrase!; + ReasonPhrase = reasonPhrase; Error = error; } - private static string GetMessage(HttpStatusCode statusCode, string reasonPhrase) + private static string GetMessage(HttpStatusCode statusCode, string? reasonPhrase) { - return $"Response status code does not indicate success: {(int)statusCode} ({reasonPhrase})."; + return string.IsNullOrEmpty(reasonPhrase) + ? $"Response status code does not indicate success: {(int)statusCode}." + : $"Response status code does not indicate success: {(int)statusCode} ({reasonPhrase})."; } - private static string GetMessage(HttpStatusCode statusCode, string reasonPhrase, string errorMessage) + private static string GetMessage(HttpStatusCode statusCode, string? reasonPhrase, string errorMessage) { - return errorMessage.EndsWith('.') + if (string.IsNullOrEmpty(reasonPhrase)) + { + return errorMessage.EndsWith('.') + ? $"Response status code does not indicate success: {(int)statusCode}. {errorMessage}" + : $"Response status code does not indicate success: {(int)statusCode}. {errorMessage}."; + } + else + { + return errorMessage.EndsWith('.') ? $"Response status code does not indicate success: {(int)statusCode} ({reasonPhrase}). {errorMessage}" : $"Response status code does not indicate success: {(int)statusCode} ({reasonPhrase}). {errorMessage}."; + } } public HttpStatusCode StatusCode { get; } - public string ReasonPhrase { get; } + public string? ReasonPhrase { get; } public RestError? Error { get; } } diff --git a/NetCord/Rest/RestGuild.cs b/NetCord/Rest/RestGuild.cs index 976fa72d..3650301e 100644 --- a/NetCord/Rest/RestGuild.cs +++ b/NetCord/Rest/RestGuild.cs @@ -1,61 +1,22 @@ using System.Collections.Immutable; +using NetCord.Gateway; + namespace NetCord.Rest; +/// +/// Represents an isolated collection of users and channels, often referred to as a server in the UI. +/// public partial class RestGuild : ClientEntity, IJsonModel, IComparer { NetCord.JsonModels.JsonGuild IJsonModel.JsonModel => _jsonModel; internal readonly NetCord.JsonModels.JsonGuild _jsonModel; - public ImmutableDictionary Roles { get; set; } - public ImmutableDictionary Emojis { get; set; } - public ImmutableDictionary Stickers { get; set; } - - public override ulong Id => _jsonModel.Id; - public string Name => _jsonModel.Name; - public string? IconHash => _jsonModel.IconHash; - public string? SplashHash => _jsonModel.SplashHash; - public string? DiscoverySplashHash => _jsonModel.DiscoverySplashHash; - public virtual bool IsOwner => _jsonModel.IsOwner; - public ulong OwnerId => _jsonModel.OwnerId; - public Permissions? Permissions => _jsonModel.Permissions; - public ulong? AfkChannelId => _jsonModel.AfkChannelId; - public int AfkTimeout => _jsonModel.AfkTimeout; - public bool? WidgetEnabled => _jsonModel.WidgetEnabled; - public ulong? WidgetChannelId => _jsonModel.WidgetChannelId; - public VerificationLevel VerificationLevel => _jsonModel.VerificationLevel; - public DefaultMessageNotificationLevel DefaultMessageNotificationLevel => _jsonModel.DefaultMessageNotificationLevel; - public ContentFilter ContentFilter => _jsonModel.ContentFilter; - public Role EveryoneRole => Roles[Id]; - public IReadOnlyList Features => _jsonModel.Features; - public MfaLevel MfaLevel => _jsonModel.MfaLevel; - public ulong? ApplicationId => _jsonModel.ApplicationId; - public ulong? SystemChannelId => _jsonModel.SystemChannelId; - public SystemChannelFlags SystemChannelFlags => _jsonModel.SystemChannelFlags; - public ulong? RulesChannelId => _jsonModel.RulesChannelId; - public int? MaxPresences => _jsonModel.MaxPresences; - public int? MaxUsers => _jsonModel.MaxUsers; - public string? VanityUrlCode => _jsonModel.VanityUrlCode; - public string? Description => _jsonModel.Description; - public string? BannerHash => _jsonModel.BannerHash; - public int PremiumTier => _jsonModel.PremiumTier; - public int? PremiumSubscriptionCount => _jsonModel.PremiumSubscriptionCount; - public string PreferredLocale => _jsonModel.PreferredLocale; - public ulong? PublicUpdatesChannelId => _jsonModel.PublicUpdatesChannelId; - public int? MaxVideoChannelUsers => _jsonModel.MaxVideoChannelUsers; - public int? MaxStageVideoChannelUsers => _jsonModel.MaxStageVideoChannelUsers; - public int? ApproximateUserCount => _jsonModel.ApproximateUserCount; - public int? ApproximatePresenceCount => _jsonModel.ApproximatePresenceCount; - public GuildWelcomeScreen? WelcomeScreen { get; } - public NsfwLevel NsfwLevel => _jsonModel.NsfwLevel; - public bool PremiumProgressBarEnabled => _jsonModel.PremiumProgressBarEnabled; - public ulong? SafetyAlertsChannelId => _jsonModel.SafetyAlertsChannelId; - public RestGuild(NetCord.JsonModels.JsonGuild jsonModel, RestClient client) : base(client) { _jsonModel = jsonModel; Roles = jsonModel.Roles.ToImmutableDictionaryOrEmpty(r => new Role(r, Id, client)); - // guild emojis always have Id + // Guild emoji always have Id. Emojis = jsonModel.Emojis.ToImmutableDictionaryOrEmpty(e => e.Id.GetValueOrDefault(), e => new GuildEmoji(e, Id, client)); Stickers = jsonModel.Stickers.ToImmutableDictionaryOrEmpty(s => s.Id, s => new GuildSticker(s, client)); @@ -102,4 +63,274 @@ int GetHighestRolePosition(PartialGuildUser user) return highestPosition; } } + + /// + /// The 's ID. + /// + public override ulong Id => _jsonModel.Id; + + /// + /// The name of the . Must be between 2 and 100 characters. Leading and trailing whitespace are trimmed. + /// + public string Name => _jsonModel.Name; + + /// + /// Whether the has an icon set. + /// + public bool HasIcon => IconHash is not null; + + /// + /// The 's icon hash. + /// + public string? IconHash => _jsonModel.IconHash; + + /// + /// Gets the of the 's icon. + /// + /// The format of the returned . Defaults to (or for animated icons). + /// An pointing to the guild's icon. If the guild does not have one set, returns . + public ImageUrl? GetIconUrl(ImageFormat? format = null) => IconHash is string hash ? ImageUrl.GuildIcon(Id, hash, format) : null; + + /// + /// Whether the has a set splash. + /// + public bool HasSplash => SplashHash is not null; + + /// + /// The 's splash hash. + /// + public string? SplashHash => _jsonModel.SplashHash; + + /// + /// Gets the of the 's splash. + /// + /// The format of the returned . + /// An pointing to the guild's splash. If the guild does not have one set, returns . + public ImageUrl? GetSplashUrl(ImageFormat format) => SplashHash is string hash ? ImageUrl.GuildSplash(Id, hash, format) : null; + + /// + /// Whether the has a set discovery splash. + /// + public bool HasDiscoverySplash => DiscoverySplashHash is not null; + + /// + /// The 's discovery splash hash. + /// + public string? DiscoverySplashHash => _jsonModel.DiscoverySplashHash; + + /// + /// Gets the of the 's discovery splash. + /// + /// The format of the returned . + /// An pointing to the guild's discovery splash. If the guild does not have one set, returns . + public ImageUrl? GetDiscoverySplashUrl(ImageFormat format) => DiscoverySplashHash is string hash ? ImageUrl.GuildDiscoverySplash(Id, hash, format) : null; + + /// + /// if the user is the owner of the . + /// + public virtual bool IsOwner => _jsonModel.IsOwner; + + /// + /// The ID of the 's owner. + /// + public ulong OwnerId => _jsonModel.OwnerId; + + /// + /// Total permissions for the user in the (excludes overwrites and implicit permissions). + /// + /// + /// Only available in objects returned from . + /// + public Permissions? Permissions => _jsonModel.Permissions; + + /// + /// ID of the 's AFK channel. + /// + public ulong? AfkChannelId => _jsonModel.AfkChannelId; + + /// + /// How long in seconds to wait before moving users to the AFK channel. + /// + public int AfkTimeout => _jsonModel.AfkTimeout; + + /// + /// if the is enabled. + /// + public bool? WidgetEnabled => _jsonModel.WidgetEnabled; + + /// + /// The ID of the channel that the will generate an invite to, or if set to no invite. + /// + public ulong? WidgetChannelId => _jsonModel.WidgetChannelId; + + /// + /// The required for the . + /// + public VerificationLevel VerificationLevel => _jsonModel.VerificationLevel; + + /// + /// The 's . + /// + public DefaultMessageNotificationLevel DefaultMessageNotificationLevel => _jsonModel.DefaultMessageNotificationLevel; + + /// + /// The 's . + /// + public ContentFilter ContentFilter => _jsonModel.ContentFilter; + + /// + /// A dictionary of objects indexed by their IDs, representing the 's roles. + /// + public ImmutableDictionary Roles { get; set; } + + /// + /// A dictionary of objects, indexed by their IDs, representing the 's custom emojis. + /// + public ImmutableDictionary Emojis { get; set; } + + /// + /// A list of feature strings, representing what features are currently enabled. + /// + public IReadOnlyList Features => _jsonModel.Features; + + /// + /// The 's required Multi-Factor Authentication level. + /// + public MfaLevel MfaLevel => _jsonModel.MfaLevel; + + /// + /// The creator's application ID, if it was created by a bot. + /// + public ulong? ApplicationId => _jsonModel.ApplicationId; + + /// + /// The ID of the channel where notices such as welcome messages and boost events are posted. + /// + public ulong? SystemChannelId => _jsonModel.SystemChannelId; + + /// + /// Represents the 's current system channels settings. + /// + public SystemChannelFlags SystemChannelFlags => _jsonModel.SystemChannelFlags; + + /// + /// The ID of the channel where community guilds can display their rules and/or guidelines. + /// + public ulong? RulesChannelId => _jsonModel.RulesChannelId; + + /// + /// The maximum number of s for the . Always with the exception of the largest guilds. + /// + public int? MaxPresences => _jsonModel.MaxPresences; + + /// + /// The maximum number of s for the . + /// + public int? MaxUsers => _jsonModel.MaxUsers; + + /// + /// The 's vanity invite URL code. + /// + public string? VanityUrlCode => _jsonModel.VanityUrlCode; + + /// + /// The 's description, shown in the 'Discovery' tab. + /// + public string? Description => _jsonModel.Description; + + /// + /// Whether the has a set banner. + /// + public bool HasBanner => BannerHash is not null; + + /// + /// The 's banner hash. + /// + public string? BannerHash => _jsonModel.BannerHash; + + /// + /// Gets the of the 's banner. + /// + /// The format of the returned . Defaults to (or for animated icons). + /// An pointing to the guild's banner. If the guild does not have one set, returns . + public ImageUrl? GetBannerUrl(ImageFormat? format = null) => BannerHash is string hash ? ImageUrl.GuildBanner(Id, hash, format) : null; + + /// + /// The 's current server boost level. + /// + public int PremiumTier => _jsonModel.PremiumTier; + + /// + /// The number of boosts the currently has. + /// + public int? PremiumSubscriptionCount => _jsonModel.PremiumSubscriptionCount; + + /// + /// The preferred locale of a community , used for the 'Discovery' tab and in notices from Discord, also sent in interactions. Defaults to en-US. + /// + public string PreferredLocale => _jsonModel.PreferredLocale; + + /// + /// The ID of the channel where admins and moderators of community guilds receive notices from Discord. + /// + public ulong? PublicUpdatesChannelId => _jsonModel.PublicUpdatesChannelId; + + /// + /// The maximum amount of users in a video channel. + /// + public int? MaxVideoChannelUsers => _jsonModel.MaxVideoChannelUsers; + + /// + /// The maximum amount of users in a stage video channel. + /// + public int? MaxStageVideoChannelUsers => _jsonModel.MaxStageVideoChannelUsers; + + /// + /// The approximate number of s in the . + /// + /// + /// Only available in objects returned from and , where withCounts is true. + /// + public int? ApproximateUserCount => _jsonModel.ApproximateUserCount; + + /// + /// Approximate number of non-offline s in the . + /// + /// + /// Only available in objects returned from and , where withCounts is true. + /// + public int? ApproximatePresenceCount => _jsonModel.ApproximatePresenceCount; + + /// + /// The welcome screen shown to new members, returned in an invite's object. + /// + public GuildWelcomeScreen? WelcomeScreen { get; } + + /// + /// The 's set NSFW level. + /// + public NsfwLevel NsfwLevel => _jsonModel.NsfwLevel; + + /// + /// A dictionary of objects indexed by their IDs, representing the 's custom stickers. + /// + public ImmutableDictionary Stickers { get; set; } + + /// + /// Whether the has the boost progress bar enabled. + /// + public bool PremiumProgressBarEnabled => _jsonModel.PremiumProgressBarEnabled; + + /// + /// The ID of the channel where admins and moderators of community guilds receive safety alerts from Discord. + /// + public ulong? SafetyAlertsChannelId => _jsonModel.SafetyAlertsChannelId; + + /// + /// The guild's base role, applied to all users. + /// + /// The guild's base role, applied to all users or if the guild is partial. + public Role? EveryoneRole => Roles.GetValueOrDefault(Id); + + public ImageUrl GetWidgetUrl(GuildWidgetStyle? style = null, string? hostname = null, ApiVersion? version = null) => ImageUrl.GuildWidget(Id, style, hostname, version); } diff --git a/NetCord/Rest/RestGuildInvite.cs b/NetCord/Rest/RestGuildInvite.cs index 0b858918..1f35bb5f 100644 --- a/NetCord/Rest/RestGuildInvite.cs +++ b/NetCord/Rest/RestGuildInvite.cs @@ -1,12 +1,14 @@ namespace NetCord.Rest; -public partial class RestGuildInvite : IGuildInvite, IJsonModel +public partial class RestInvite : IInvite, IJsonModel { - JsonModels.JsonRestGuildInvite IJsonModel.JsonModel => _jsonModel; - private readonly JsonModels.JsonRestGuildInvite _jsonModel; + JsonModels.JsonRestInvite IJsonModel.JsonModel => _jsonModel; + private readonly JsonModels.JsonRestInvite _jsonModel; private readonly RestClient _client; + public InviteType Type => _jsonModel.Type; + public string Code => _jsonModel.Code; public RestGuild? Guild { get; } @@ -15,7 +17,7 @@ public partial class RestGuildInvite : IGuildInvite, IJsonModel _jsonModel.TargetType; + public InviteTargetType? TargetType => _jsonModel.TargetType; public User? TargetUser { get; } @@ -41,11 +43,11 @@ public partial class RestGuildInvite : IGuildInvite, IJsonModel _jsonModel.CreatedAt; - ulong? IGuildInvite.GuildId => Guild?.Id; + ulong? IInvite.GuildId => Guild?.Id; - ulong? IGuildInvite.ChannelId => Channel?.Id; + ulong? IInvite.ChannelId => Channel?.Id; - public RestGuildInvite(JsonModels.JsonRestGuildInvite jsonModel, RestClient client) + public RestInvite(JsonModels.JsonRestInvite jsonModel, RestClient client) { _jsonModel = jsonModel; diff --git a/NetCord/Rest/RestMessage.cs b/NetCord/Rest/RestMessage.cs index fbecfb88..62b8506f 100644 --- a/NetCord/Rest/RestMessage.cs +++ b/NetCord/Rest/RestMessage.cs @@ -1,5 +1,8 @@ namespace NetCord.Rest; +/// +/// Represents a message sent in a channel within Discord. +/// public partial class RestMessage : ClientEntity, IJsonModel { NetCord.JsonModels.JsonMessage IJsonModel.JsonModel => _jsonModel; @@ -18,7 +21,7 @@ public RestMessage(NetCord.JsonModels.JsonMessage jsonModel, RestClient client) Author = new GuildUser(guildUser, jsonModel.GuildId.GetValueOrDefault(), client); } - MentionedUsers = jsonModel.MentionedUsers!.ToDictionary(u => u.Id, u => + MentionedUsers = jsonModel.MentionedUsers!.Select(u => { var guildUser = u.GuildUser; if (guildUser is null) @@ -26,10 +29,10 @@ public RestMessage(NetCord.JsonModels.JsonMessage jsonModel, RestClient client) guildUser.User = u; return new GuildUser(guildUser, jsonModel.GuildId.GetValueOrDefault(), client); - }); + }).ToArray(); - MentionedChannels = jsonModel.MentionedChannels.ToDictionaryOrEmpty(c => c.Id, c => new GuildChannelMention(c)); - Attachments = jsonModel.Attachments!.ToDictionary(a => a.Id, Attachment.CreateFromJson); + MentionedChannels = jsonModel.MentionedChannels.SelectOrEmpty(c => new GuildChannelMention(c)).ToArray(); + Attachments = jsonModel.Attachments!.Select(Attachment.CreateFromJson).ToArray(); Embeds = jsonModel.Embeds!.Select(e => new Embed(e)).ToArray(); Reactions = jsonModel.Reactions.SelectOrEmpty(r => new MessageReaction(r)).ToArray(); @@ -43,8 +46,14 @@ public RestMessage(NetCord.JsonModels.JsonMessage jsonModel, RestClient client) var messageReference = jsonModel.MessageReference; if (messageReference is not null) + { MessageReference = new(messageReference); + MessageSnapshots = jsonModel.MessageSnapshots.SelectOrEmpty(s => new MessageSnapshot(s, messageReference.GuildId, client)).ToArray(); + } + else + MessageSnapshots = []; + var referencedMessage = jsonModel.ReferencedMessage; if (referencedMessage is not null) ReferencedMessage = new(referencedMessage, client); @@ -64,7 +73,7 @@ public RestMessage(NetCord.JsonModels.JsonMessage jsonModel, RestClient client) StartedThread = GuildThread.CreateFromJson(startedThread, client); Components = jsonModel.Components.SelectOrEmpty(IMessageComponent.CreateFromJson).ToArray(); - Stickers = jsonModel.Stickers.ToDictionaryOrEmpty(s => s.Id, s => new MessageSticker(s, client)); + Stickers = jsonModel.Stickers.SelectOrEmpty(s => new MessageSticker(s, client)).ToArray(); var roleSubscriptionData = jsonModel.RoleSubscriptionData; if (roleSubscriptionData is not null) @@ -77,73 +86,187 @@ public RestMessage(NetCord.JsonModels.JsonMessage jsonModel, RestClient client) var poll = jsonModel.Poll; if (poll is not null) Poll = new(poll); + + var call = jsonModel.Call; + if (call is not null) + Call = new(call); } + /// + /// The ID of the message. + /// public override ulong Id => _jsonModel.Id; + /// + /// The ID of the channel the message was sent in. + /// public ulong ChannelId => _jsonModel.ChannelId; + /// + /// The object of the message's author. + /// + /// + /// The author object follows the structure of the user object, but is only a valid user in the case where the message is generated by a user or bot user. If the message is generated by a , the author object corresponds to the webhook's id, username, and avatar. You can tell if a message is generated by a webhook by checking for the on the message object. + /// public User Author { get; } + /// + /// The text contents of the message. + /// public string Content => _jsonModel.Content!; + /// + /// When the message was edited (or null if never). + /// public DateTimeOffset? EditedAt => _jsonModel.EditedAt; + /// + /// Whether the message was a Text-To-Speech message. + /// public bool IsTts => _jsonModel.IsTts.GetValueOrDefault(); + /// + /// Whether the message mentions @everyone. + /// public bool MentionEveryone => _jsonModel.MentionEveryone.GetValueOrDefault(); - public IReadOnlyDictionary MentionedUsers { get; } + /// + /// A list of objects indexed by their IDs, containing users specifically mentioned in the message. + /// + public IReadOnlyList MentionedUsers { get; } + /// + /// A list of IDs corresponding to roles specifically mentioned in the message. + /// public IReadOnlyList MentionedRoleIds => _jsonModel.MentionedRoleIds!; - public IReadOnlyDictionary MentionedChannels { get; } - - public IReadOnlyDictionary Attachments { get; } - + /// + /// A list of objects indexed by their IDs, containing channels specifically mentioned in the message. + /// + /// + /// Not all channel mentions in a message will appear in . Only s visible to everyone in a lurkable guild will ever be included. Only crossposted messages (via Channel Following) currently include at all. If no s in the message meet these requirements, this field will not be sent. + /// + public IReadOnlyList MentionedChannels { get; } + + /// + /// A list of objects indexed by their IDs, containing any files attached in the message. + /// + public IReadOnlyList Attachments { get; } + + /// + /// A list of objects containing any embedded content present in the message. + /// public IReadOnlyList Embeds { get; } + /// + /// A list of objects containing all reactions to the message. + /// public IReadOnlyList Reactions { get; } + /// + /// Used for validating that a message was sent. + /// public string? Nonce => _jsonModel.Nonce; + /// + /// Whether this message is pinned in a channel. + /// public bool IsPinned => _jsonModel.IsPinned.GetValueOrDefault(); + /// + /// If the message was generated by a , this is the webhook's ID. + /// public ulong? WebhookId => _jsonModel.WebhookId; + /// + /// The type of the message. + /// public MessageType Type => _jsonModel.Type.GetValueOrDefault(); + /// + /// Sent with Rich Presence-related chat embeds. + /// public MessageActivity? Activity { get; } + /// + /// Sent with Rich Presence-related chat embeds. + /// public Application? Application { get; } + /// + /// If the message is an response/followup or an application-owned , the ID of the . + /// public ulong? ApplicationId => _jsonModel.ApplicationId; - public MessageReference? MessageReference { get; } - + /// + /// A object indicating the message's applied flags. + /// public MessageFlags Flags => _jsonModel.Flags.GetValueOrDefault(); + /// + /// Contains data showing the source of a crosspost, channel follow add, pin, or message reply. + /// + public MessageReference? MessageReference { get; } + + /// + /// A list of messages associated with the message reference. + /// + public IReadOnlyList MessageSnapshots { get; } + + /// + /// The message associated with the . + /// + /// + /// This field is only returned for messages with a of or . If the message is a reply but the field is not present, the backend did not attempt to fetch the message that was being replied to, so its state is unknown. If the field exists but is null, the referenced message was deleted. + /// public RestMessage? ReferencedMessage { get; } + /// + /// Sent if the message is sent as a result of an . + /// public MessageInteractionMetadata? InteractionMetadata { get; } + /// + /// Sent if the message is a response to an . + /// [Obsolete($"Replaced by '{nameof(InteractionMetadata)}'")] public MessageInteraction? Interaction { get; } + /// + /// The that was started from this message. + /// public GuildThread? StartedThread { get; } + /// + /// A list of objects, contains components like s, s, or other interactive components if any are present. + /// public IReadOnlyList Components { get; } - public IReadOnlyDictionary Stickers { get; } + /// + /// Contains stickers contained in the message, if any. + /// + public IReadOnlyList Stickers { get; } + /// + /// A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a . + /// It can be used to estimate the relative position of the message in a thread in tandem with the property of the parent thread. + /// public int? Position => _jsonModel.Position; + /// + /// Data of the role subscription purchase or renewal that prompted this message. + /// public RoleSubscriptionData? RoleSubscriptionData { get; } + /// + /// Data for s, s, s, and s in the message's auto-populated select s. + /// public InteractionResolvedData? ResolvedData { get; } public MessagePoll? Poll { get; } - public Task ReplyAsync(ReplyMessageProperties replyMessage, RestRequestProperties? properties = null) - => SendAsync(replyMessage.ToMessageProperties(Id), properties); + public MessageCall? Call { get; } + + public Task ReplyAsync(ReplyMessageProperties replyMessage, RestRequestProperties? properties = null, CancellationToken cancellationToken = default) + => SendAsync(replyMessage.ToMessageProperties(Id), properties, cancellationToken); } diff --git a/NetCord/Rest/RestRequestHandler.cs b/NetCord/Rest/RestRequestHandler.cs index 11574eb5..59fbaf52 100644 --- a/NetCord/Rest/RestRequestHandler.cs +++ b/NetCord/Rest/RestRequestHandler.cs @@ -19,7 +19,7 @@ public RestRequestHandler(HttpMessageHandler handler, bool disposeHandler) _httpClient = new(handler, disposeHandler); } - public Task SendAsync(HttpRequestMessage request) => _httpClient.SendAsync(request); + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) => _httpClient.SendAsync(request, cancellationToken); public void AddDefaultHeader(string name, IEnumerable values) { diff --git a/NetCord/Rest/Sku.cs b/NetCord/Rest/Sku.cs index 3dd29909..965d6324 100644 --- a/NetCord/Rest/Sku.cs +++ b/NetCord/Rest/Sku.cs @@ -12,7 +12,7 @@ public class Sku(JsonModels.JsonSku jsonModel) : Entity, IJsonModel jsonModel.Type; /// - /// Id of the parent application. + /// ID of the parent application. /// public ulong ApplicationId => jsonModel.ApplicationId; diff --git a/NetCord/Rest/TypingReminder.cs b/NetCord/Rest/TypingReminder.cs index 213d2b2a..ae96ac3a 100644 --- a/NetCord/Rest/TypingReminder.cs +++ b/NetCord/Rest/TypingReminder.cs @@ -4,20 +4,20 @@ internal sealed class TypingReminder(ulong channelId, RestClient client, RestReq { private readonly CancellationTokenSource _tokenSource = new(); - public async Task StartAsync() + public async Task StartAsync(CancellationToken cancellationToken) { - await client.TriggerTypingStateAsync(channelId).ConfigureAwait(false); + await client.TriggerTypingStateAsync(channelId, properties, cancellationToken).ConfigureAwait(false); _ = RunAsync(); } private async Task RunAsync() { - var token = _tokenSource.Token; + var cancellationToken = _tokenSource.Token; using PeriodicTimer timer = new(TimeSpan.FromSeconds(9.5)); while (true) { - await timer.WaitForNextTickAsync(token).ConfigureAwait(false); - await client.TriggerTypingStateAsync(channelId, properties).ConfigureAwait(false); + await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false); + await client.TriggerTypingStateAsync(channelId, properties, cancellationToken).ConfigureAwait(false); } } diff --git a/NetCord/Rest/WebhookMessageProperties.cs b/NetCord/Rest/WebhookMessageProperties.cs index 9550eeef..38cc920c 100644 --- a/NetCord/Rest/WebhookMessageProperties.cs +++ b/NetCord/Rest/WebhookMessageProperties.cs @@ -2,7 +2,7 @@ namespace NetCord.Rest; -public partial class WebhookMessageProperties : IHttpSerializable +public partial class WebhookMessageProperties : IHttpSerializable, IMessageProperties { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("content")] diff --git a/NetCord/Role.cs b/NetCord/Role.cs index e339ea14..51aa4b32 100644 --- a/NetCord/Role.cs +++ b/NetCord/Role.cs @@ -47,6 +47,8 @@ public Role(JsonRole jsonModel, ulong guildId, RestClient client) : base(client) GuildId = guildId; } + public ImageUrl? GetIconUrl(ImageFormat format) => IconHash is string hash ? ImageUrl.RoleIcon(Id, hash, format) : null; + public override string ToString() => $"<@&{Id}>"; public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => Mention.TryFormatRole(destination, out charsWritten, Id); diff --git a/NetCord/RoleMenuInteraction.cs b/NetCord/RoleMenuInteraction.cs index b8157b2a..33ab39dc 100644 --- a/NetCord/RoleMenuInteraction.cs +++ b/NetCord/RoleMenuInteraction.cs @@ -3,6 +3,6 @@ namespace NetCord; -public class RoleMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) +public class RoleMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) { } diff --git a/NetCord/RoleSubscriptionData.cs b/NetCord/RoleSubscriptionData.cs index 78da0418..9b881bf9 100644 --- a/NetCord/RoleSubscriptionData.cs +++ b/NetCord/RoleSubscriptionData.cs @@ -5,7 +5,7 @@ public class RoleSubscriptionData(JsonModels.JsonRoleSubscriptionData jsonModel) JsonModels.JsonRoleSubscriptionData IJsonModel.JsonModel => jsonModel; /// - /// The id of the sku and listing that the user is subscribed to. + /// The ID of the sku and listing that the user is subscribed to. /// public ulong RoleSubscriptionListingId => jsonModel.RoleSubscriptionListingId; diff --git a/NetCord/Serialization.cs b/NetCord/Serialization.cs index 89c160bd..42a33f23 100644 --- a/NetCord/Serialization.cs +++ b/NetCord/Serialization.cs @@ -39,8 +39,8 @@ namespace NetCord; [JsonSerializable(typeof(JsonMessageReactionAddEventArgs))] [JsonSerializable(typeof(JsonMessageDeleteBulkEventArgs))] [JsonSerializable(typeof(JsonMessageDeleteEventArgs))] -[JsonSerializable(typeof(JsonGuildInviteDeleteEventArgs))] -[JsonSerializable(typeof(JsonGuildInvite))] +[JsonSerializable(typeof(JsonInviteDeleteEventArgs))] +[JsonSerializable(typeof(JsonInvite))] [JsonSerializable(typeof(JsonInteraction))] [JsonSerializable(typeof(JsonGuildIntegrationDeleteEventArgs))] [JsonSerializable(typeof(JsonIntegration))] @@ -96,6 +96,7 @@ namespace NetCord; [JsonSerializable(typeof(MessageCommandProperties))] [JsonSerializable(typeof(ButtonProperties))] [JsonSerializable(typeof(LinkButtonProperties))] +[JsonSerializable(typeof(PremiumButtonProperties))] [JsonSerializable(typeof(IEnumerable))] [JsonSerializable(typeof(StringMenuProperties))] [JsonSerializable(typeof(UserMenuProperties))] @@ -125,9 +126,9 @@ namespace NetCord; [JsonSerializable(typeof(JsonUser[]))] [JsonSerializable(typeof(BulkDeleteMessagesProperties))] [JsonSerializable(typeof(PermissionOverwriteProperties))] -[JsonSerializable(typeof(JsonRestGuildInvite[]))] -[JsonSerializable(typeof(GuildInviteProperties))] -[JsonSerializable(typeof(JsonRestGuildInvite))] +[JsonSerializable(typeof(JsonRestInvite[]))] +[JsonSerializable(typeof(InviteProperties))] +[JsonSerializable(typeof(JsonRestInvite))] [JsonSerializable(typeof(FollowAnnouncementGuildChannelProperties))] [JsonSerializable(typeof(JsonFollowedChannel))] [JsonSerializable(typeof(GroupDMChannelUserAddProperties))] @@ -138,7 +139,9 @@ namespace NetCord; [JsonSerializable(typeof(JsonEmoji[]))] [JsonSerializable(typeof(JsonEmoji))] [JsonSerializable(typeof(GuildEmojiProperties))] +[JsonSerializable(typeof(ApplicationEmojiProperties))] [JsonSerializable(typeof(GuildEmojiOptions))] +[JsonSerializable(typeof(ApplicationEmojiOptions))] [JsonSerializable(typeof(JsonGateway))] [JsonSerializable(typeof(JsonGatewayBot))] [JsonSerializable(typeof(GuildProperties))] diff --git a/NetCord/SlashCommandInteraction.cs b/NetCord/SlashCommandInteraction.cs index 3494d3fd..88293d1d 100644 --- a/NetCord/SlashCommandInteraction.cs +++ b/NetCord/SlashCommandInteraction.cs @@ -3,7 +3,7 @@ namespace NetCord; -public class SlashCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : ApplicationCommandInteraction(jsonModel, guild, sendResponseAsync, client) +public class SlashCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : ApplicationCommandInteraction(jsonModel, guild, sendResponseAsync, client) { public override SlashCommandInteractionData Data { get; } = new(jsonModel.Data!, jsonModel.GuildId, client); } diff --git a/NetCord/Sticker.cs b/NetCord/Sticker.cs index 9d3e3df9..0d785045 100644 --- a/NetCord/Sticker.cs +++ b/NetCord/Sticker.cs @@ -20,4 +20,6 @@ private protected Sticker(JsonModels.JsonSticker jsonModel) _jsonModel = jsonModel; Tags = _jsonModel.Tags.Split(','); } + + public ImageUrl GetImageUrl(ImageFormat format) => ImageUrl.Sticker(Id, format); } diff --git a/NetCord/StringMenuInteraction.cs b/NetCord/StringMenuInteraction.cs index e373ce15..9feff9e3 100644 --- a/NetCord/StringMenuInteraction.cs +++ b/NetCord/StringMenuInteraction.cs @@ -3,7 +3,7 @@ namespace NetCord; -public class StringMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : MessageComponentInteraction(jsonModel, guild, sendResponseAsync, client) +public class StringMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : MessageComponentInteraction(jsonModel, guild, sendResponseAsync, client) { public override StringMenuInteractionData Data { get; } = new(jsonModel.Data!); } diff --git a/NetCord/Team.cs b/NetCord/Team.cs index b7dcdc79..768e93b9 100644 --- a/NetCord/Team.cs +++ b/NetCord/Team.cs @@ -15,4 +15,6 @@ public class Team(JsonModels.JsonTeam jsonModel, RestClient client) : Entity, IJ public string Name => jsonModel.Name; public ulong OwnerId => jsonModel.OwnerId; + + public ImageUrl? GetIconUrl(ImageFormat format) => IconHash is string hash ? ImageUrl.TeamIcon(Id, hash, format) : null; } diff --git a/NetCord/TeamRole.cs b/NetCord/TeamRole.cs index b9d63710..62a890c0 100644 --- a/NetCord/TeamRole.cs +++ b/NetCord/TeamRole.cs @@ -2,15 +2,30 @@ namespace NetCord; -[JsonConverter(typeof(JsonConverters.StringEnumConverterWithErrorHandling))] +/// +/// A can have one of four roles (Owner, , , and ), and each role inherits the access of those below it. +/// +/// +/// The Owner role is not represented in the enum, as it is not represented in 's field. Instead, owners can be identified using a 's field. They have the most permissive role, and can take destructive, irreversible actions like deleting team-owned apps or the team itself. Teams are limited to 1 owner. +/// +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum TeamRole { + /// + /// Admins have similar access to owners, except they cannot take destructive actions on the team or team-owned apps. + /// [JsonPropertyName("admin")] Admin, + /// + /// Developers can access information about team-owned apps, like the client secret or public key. They can also take limited actions on team-owned apps, like configuring interaction endpoints or resetting the bot token. s with the role cannot manage the or its users, or take destructive actions on team-owned s. + /// [JsonPropertyName("developer")] Developer, + /// + /// Read-only users can access information about a and any team-owned s. Some examples include getting the IDs of applications and exporting payout records. Members can also invite bots associated with team-owned apps that are marked private. + /// [JsonPropertyName("read-only")] ReadOnly, } diff --git a/NetCord/TeamUser.cs b/NetCord/TeamUser.cs index a156d344..d22e4152 100644 --- a/NetCord/TeamUser.cs +++ b/NetCord/TeamUser.cs @@ -2,11 +2,25 @@ namespace NetCord; +/// +/// Represents a user as a part of the team indicated by the . +/// public class TeamUser(JsonModels.JsonTeamUser jsonModel, RestClient client) : User(jsonModel.User, client), IJsonModel { JsonModels.JsonTeamUser IJsonModel.JsonModel => jsonModel; + /// + /// The membership state of the . + /// public MembershipState MembershipState => jsonModel.MembershipState; + + /// + /// The ID of the the belongs to. + /// public ulong TeamId => jsonModel.TeamId; + + /// + /// The role of the in the associated . + /// public TeamRole Role => jsonModel.Role; } diff --git a/NetCord/User.cs b/NetCord/User.cs index 8d84b25a..90c14cc4 100644 --- a/NetCord/User.cs +++ b/NetCord/User.cs @@ -2,43 +2,239 @@ namespace NetCord; -public partial class User(JsonModels.JsonUser jsonModel, RestClient client) : ClientEntity(client), IJsonModel +/// +/// Represents a user of any interactable resource on Discord. +/// +/// +/// Users in Discord are generally considered the base entity and can be members of guilds, participate in text and voice chat, and much more. Users are separated by a distinction of 'bot' vs 'normal'. Bot users are automated users that are 'owned' by another user. +/// +public partial class User : ClientEntity, IJsonModel { JsonModels.JsonUser IJsonModel.JsonModel => _jsonModel; - private protected readonly JsonModels.JsonUser _jsonModel = jsonModel; + private protected readonly JsonModels.JsonUser _jsonModel; + public User(JsonModels.JsonUser jsonModel, RestClient client) : base(client) + { + _jsonModel = jsonModel; + + var avatarDecorationData = jsonModel.AvatarDecorationData; + if (avatarDecorationData is not null) + AvatarDecorationData = new(avatarDecorationData); + } + + /// + /// The user's ID. + /// + /// + /// Requires the identify OAuth2 scope. + /// public override ulong Id => _jsonModel.Id; + + /// + /// The user's username, not unique across the platform. Restrictions: + /// + /// + /// + /// + /// + /// Must be between 2 and 32 characters long. + /// + /// + /// + /// + /// Usernames cannot contain the following substrings: @, #, :, ```, discord. + /// + /// + /// + /// + /// Usernames cannot be: everyone or here. + /// + /// + /// + /// Requires the identify OAuth2 scope. + /// public string Username => _jsonModel.Username; + + /// + /// The user's Discord-tag. + /// + /// + /// Requires the identify OAuth2 scope. + /// public ushort Discriminator => _jsonModel.Discriminator; + + /// + /// The user's display name, if it is set. For bots, this is the application name. Restrictions: + /// + /// + /// + /// + /// + /// Must be between 1 and 32 characters long. + /// + /// + /// + /// + /// Some zero-width and non-rendering characters are limited. + /// + /// + /// + /// + /// Names are sanitized and trimmed of leading, trailing, and excessive internal whitespace. + /// + /// + /// + /// Requires the identify OAuth2 scope. + /// public string? GlobalName => _jsonModel.GlobalName; + + /// + /// The user's avatar hash. + /// + /// + /// Requires the identify OAuth2 scope. + /// public string? AvatarHash => _jsonModel.AvatarHash; + + /// + /// Whether the user belongs to an application. + /// + /// + /// Requires the identify OAuth2 scope. + /// public bool IsBot => _jsonModel.IsBot; + + /// + /// Whether the user is an Official Discord System user (part of the urgent message system). + /// + /// + /// Requires the identify OAuth2 scope. + /// public bool? IsSystemUser => _jsonModel.IsSystemUser; + + /// + /// Whether the user has two factor enabled on their account. + /// + /// + /// Requires the identify OAuth2 scope. + /// public bool? MfaEnabled => _jsonModel.MfaEnabled; + + /// + /// The user's banner hash. + /// + /// + /// Requires the identify OAuth2 scope. + /// public string? BannerHash => _jsonModel.BannerHash; + + /// + /// The user's banner color. + /// + /// + /// Requires the identify OAuth2 scope. + /// public Color? AccentColor => _jsonModel.AccentColor; + + /// + /// The user's chosen language option. + /// + /// + /// Requires the identify OAuth2 scope. + /// public string? Locale => _jsonModel.Locale; + + /// + /// Whether the email on this account has been verified. + /// + /// + /// Requires the email OAuth2 scope. + /// public bool? Verified => _jsonModel.Verified; + + /// + /// The user's email. + /// + /// + /// Requires the email OAuth2 scope. + /// public string? Email => _jsonModel.Email; + + /// + /// The flags on a user's account. + /// + /// + /// Requires the identify OAuth2 scope. + /// public UserFlags? Flags => _jsonModel.Flags; + + /// + /// The type of Nitro subscription on a user's account. + /// + /// + /// Requires the identify OAuth2 scope. + /// public PremiumType? PremiumType => _jsonModel.PremiumType; + + /// + /// The public flags on a user's account. + /// + /// + /// Requires the identify OAuth2 scope. + /// public UserFlags? PublicFlags => _jsonModel.PublicFlags; - public string? AvatarDecorationHash => _jsonModel.AvatarDecorationHash; + /// + /// Data for the user's avatar decoration. + /// + /// + /// Requires the identify OAuth2 scope. + /// + public AvatarDecorationData? AvatarDecorationData { get; } + + /// + /// Whether the user has a set custom avatar. + /// public bool HasAvatar => AvatarHash is not null; - public ImageUrl GetAvatarUrl(ImageFormat? format = null) => ImageUrl.UserAvatar(Id, AvatarHash!, format); + /// + /// Gets the of the user's avatar. + /// + /// The format of the returned . Defaults to (or for animated avatars). + /// An pointing to the user's avatar. If the user does not have one set, returns . + public ImageUrl? GetAvatarUrl(ImageFormat? format = null) => AvatarHash is string hash ? ImageUrl.UserAvatar(Id, hash, format) : null; + /// + /// Whether the user has a set custom banner image. + /// public bool HasBanner => BannerHash is not null; - public ImageUrl GetBannerUrl(ImageFormat? format = null) => ImageUrl.UserBanner(Id, BannerHash!, format); + /// + /// Gets the of the user's banner. + /// + /// The format of the returned . Defaults to (or for animated banners). + /// An pointing to the user's banner. If the user does not have one set, returns . + public ImageUrl? GetBannerUrl(ImageFormat? format = null) => BannerHash is string hash ? ImageUrl.UserBanner(Id, hash, format) : null; - public bool HasAvatarDecoration => AvatarDecorationHash is not null; + /// + /// Whether the user has a set avatar decoration. + /// + public bool HasAvatarDecoration => AvatarDecorationData is not null; - public ImageUrl GetAvatarDecorationUrl() => ImageUrl.UserAvatarDecoration(Id, AvatarDecorationHash!); + /// + /// Gets the of the user's avatar decoration. + /// + /// An pointing to the user's avatar decoration. If the user does not have one set, returns . + public ImageUrl? GetAvatarDecorationUrl() => AvatarDecorationData is { Hash: var hash } ? ImageUrl.AvatarDecoration(hash) : null; + /// + /// Returns an object representing the user's default avatar. + /// public ImageUrl DefaultAvatarUrl => Discriminator is 0 ? ImageUrl.DefaultUserAvatar(Id) : ImageUrl.DefaultUserAvatar(Discriminator); + /// + /// Converts the ID of this user into its string representation, using Discord's mention syntax (<@803169206115237908>). + /// public override string ToString() => $"<@{Id}>"; public override bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format = default, IFormatProvider? provider = null) => Mention.TryFormatUser(destination, out charsWritten, Id); diff --git a/NetCord/UserCommandInteraction.cs b/NetCord/UserCommandInteraction.cs index 28032e0b..3cd7a6f8 100644 --- a/NetCord/UserCommandInteraction.cs +++ b/NetCord/UserCommandInteraction.cs @@ -3,7 +3,7 @@ namespace NetCord; -public class UserCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : ApplicationCommandInteraction(jsonModel, guild, sendResponseAsync, client) +public class UserCommandInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : ApplicationCommandInteraction(jsonModel, guild, sendResponseAsync, client) { public override UserCommandInteractionData Data { get; } = new(jsonModel.Data!, jsonModel.GuildId, client); } diff --git a/NetCord/UserFlags.cs b/NetCord/UserFlags.cs index 7ecf9e16..14b92fdf 100644 --- a/NetCord/UserFlags.cs +++ b/NetCord/UserFlags.cs @@ -1,5 +1,8 @@ namespace NetCord; +/// +/// The flags on a 's account. +/// [Flags] public enum UserFlags : ulong { diff --git a/NetCord/UserMenuInteraction.cs b/NetCord/UserMenuInteraction.cs index 8a413a32..c49fee21 100644 --- a/NetCord/UserMenuInteraction.cs +++ b/NetCord/UserMenuInteraction.cs @@ -3,6 +3,6 @@ namespace NetCord; -public class UserMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) +public class UserMenuInteraction(JsonModels.JsonInteraction jsonModel, Guild? guild, Func sendResponseAsync, RestClient client) : EntityMenuInteraction(jsonModel, guild, sendResponseAsync, client) { } diff --git a/NetCord/UserStatusType.cs b/NetCord/UserStatusType.cs index 9c103a32..75a5f90b 100644 --- a/NetCord/UserStatusType.cs +++ b/NetCord/UserStatusType.cs @@ -2,7 +2,7 @@ namespace NetCord; -[JsonConverter(typeof(JsonConverters.StringEnumConverterWithErrorHandling))] +[JsonConverter(typeof(JsonConverters.SafeStringEnumConverter))] public enum UserStatusType { [JsonPropertyName("online")] diff --git a/SourceGenerators/RestClientMethodAliasesGenerator/RestClientMethodAliasesGenerator.cs b/SourceGenerators/RestClientMethodAliasesGenerator/RestClientMethodAliasesGenerator.cs index 2a9ed6b5..c3c1c845 100644 --- a/SourceGenerators/RestClientMethodAliasesGenerator/RestClientMethodAliasesGenerator.cs +++ b/SourceGenerators/RestClientMethodAliasesGenerator/RestClientMethodAliasesGenerator.cs @@ -25,7 +25,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) namespace NetCord.Rest; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class GenerateAliasAttribute(Type[] types, params string?[] parameterAliases) : Attribute + internal class GenerateAliasAttribute(Type[] types, params string?[] parameterAliases) : Attribute { public Type[] Types { get; } = types; diff --git a/Tests/NetCord.Test.Hosting/CustomSlashCommandResultHandler.cs b/Tests/NetCord.Test.Hosting/CustomSlashCommandResultHandler.cs new file mode 100644 index 00000000..51b0dfce --- /dev/null +++ b/Tests/NetCord.Test.Hosting/CustomSlashCommandResultHandler.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Logging; + +using NetCord.Gateway; +using NetCord.Hosting.Services.ApplicationCommands; +using NetCord.Services; +using NetCord.Services.ApplicationCommands; + +namespace NetCord.Test.Hosting; + +internal class CustomSlashCommandResultHandler : IApplicationCommandResultHandler +{ + private static readonly ApplicationCommandResultHandler _defaultHandler = new(MessageFlags.Ephemeral); + + public ValueTask HandleResultAsync(IExecutionResult result, SlashCommandContext context, GatewayClient? client, ILogger logger, IServiceProvider services) + { + logger.LogInformation("Handling result of slash command"); + + return _defaultHandler.HandleResultAsync(result, context, client, logger, services); + } +} diff --git a/Tests/NetCord.Test.Hosting/Program.cs b/Tests/NetCord.Test.Hosting/Program.cs index 1536c37b..f5e546de 100644 --- a/Tests/NetCord.Test.Hosting/Program.cs +++ b/Tests/NetCord.Test.Hosting/Program.cs @@ -39,7 +39,10 @@ { Intents = GatewayIntents.All, }) - .AddApplicationCommands() + .AddApplicationCommands(options => + { + options.ResultHandler = new CustomSlashCommandResultHandler(); + }) .AddApplicationCommands() .AddApplicationCommands() .AddComponentInteractions() diff --git a/Tests/NetCord.Test/ApplicationCommands/VoiceCommands.cs b/Tests/NetCord.Test/ApplicationCommands/VoiceCommands.cs index 13508475..8c1c88f4 100644 --- a/Tests/NetCord.Test/ApplicationCommands/VoiceCommands.cs +++ b/Tests/NetCord.Test/ApplicationCommands/VoiceCommands.cs @@ -57,7 +57,6 @@ private async Task JoinAsync(VoiceEncryption encryption, Func text) [Command("reply")] public Task Reply([CommandParameter(Remainder = true)] string text) { - return SendAsync(new MessageProperties() { Content = text, AllowedMentions = AllowedMentionsProperties.None, MessageReference = new(Context.Message.Id) }); + return SendAsync(new MessageProperties() { Content = text, AllowedMentions = AllowedMentionsProperties.None, MessageReference = MessageReferenceProperties.Reply(Context.Message.Id) }); } [Command("roles")] @@ -97,7 +97,7 @@ public Task Avatar([CommandParameter(Remainder = true)] GuildUser? user = null) EmbedProperties embed = new() { Title = $"Avatar of {user.Username}#{user.Discriminator}", - Image = new(user.HasAvatar ? user.GetAvatarUrl().ToString(4096) : user.DefaultAvatarUrl.ToString()), + Image = new(user.HasAvatar ? user.GetAvatarUrl()!.ToString(4096) : user.DefaultAvatarUrl.ToString()), Color = new(0, 255, 0) }; MessageProperties message = new() @@ -106,7 +106,7 @@ public Task Avatar([CommandParameter(Remainder = true)] GuildUser? user = null) [ embed ], - MessageReference = new(Context.Message.Id, false), + MessageReference = MessageReferenceProperties.Reply(Context.Message.Id, false), AllowedMentions = new() { ReplyMention = false diff --git a/Tests/NetCord.Test/Commands/StrangeCommands.cs b/Tests/NetCord.Test/Commands/StrangeCommands.cs index f1ad7cfd..7d25d7ef 100644 --- a/Tests/NetCord.Test/Commands/StrangeCommands.cs +++ b/Tests/NetCord.Test/Commands/StrangeCommands.cs @@ -65,7 +65,7 @@ public Task Button() { Content = "This is button:", Components = [actionRow], - MessageReference = new(Context.Message.Id), + MessageReference = MessageReferenceProperties.Reply(Context.Message.Id), AllowedMentions = new() { ReplyMention = false @@ -89,7 +89,7 @@ public Task Link([CommandParameter(Remainder = true)] Uri url) actionRow ], Content = "This is the message with the link", - MessageReference = new(Context.Message.Id), + MessageReference = MessageReferenceProperties.Reply(Context.Message.Id), AllowedMentions = new() { ReplyMention = false @@ -114,7 +114,7 @@ public Task Dzejus() MessageProperties message = new() { Attachments = [file], - MessageReference = new(Context.Message.Id), + MessageReference = MessageReferenceProperties.Reply(Context.Message.Id), AllowedMentions = AllowedMentionsProperties.None }; return SendAsync(message); @@ -185,7 +185,7 @@ public Task Menu(params string[] values) { Content = "Here is your menu:", Components = [new StringMenuProperties("menu", values.Select(v => new StringMenuSelectOptionProperties(v, v))) { MaxValues = values.Length }], - MessageReference = new(Context.Message.Id) + MessageReference = MessageReferenceProperties.Reply(Context.Message.Id) }; return SendAsync(message); } @@ -199,8 +199,8 @@ public Task Timestamp([CommandParameter(Remainder = true)] DateTime time) [Command("bot-avatar")] public Task BotAvatar() { - var newAvatar = Context.Message.Attachments.Values.FirstOrDefault(); - return newAvatar is null ? throw new("Give an url or attachment") : BotAvatar(new(newAvatar.Url)); + var attachments = Context.Message.Attachments; + return attachments.Count is 0 ? throw new("Give an url or attachment") : BotAvatar(new(attachments[0].Url)); } [Command("bot-avatar")] @@ -269,7 +269,7 @@ public Task AttachmentAsync() return SendAsync(new() { Attachments = [attachment], - Embeds = [new() { Image = attachment }] + Embeds = [new() { Image = $"attachment://{attachment.FileName}" }] }); } diff --git a/Tests/NetCord.Test/Program.cs b/Tests/NetCord.Test/Program.cs index b91155d8..f84f3d8f 100644 --- a/Tests/NetCord.Test/Program.cs +++ b/Tests/NetCord.Test/Program.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using NetCord.Gateway; +using NetCord.Gateway.Compression; using NetCord.JsonModels; using NetCord.Rest; using NetCord.Services; @@ -20,6 +21,7 @@ internal static class Program { Intents = GatewayIntents.All, ConnectionProperties = ConnectionPropertiesProperties.IOS, + Compression = new ZstandardGatewayCompression(), }); private static readonly CommandService _commandService = new(); @@ -88,7 +90,6 @@ private static async Task Main() manager.AddService(_userCommandService); await _client.StartAsync(); - await _client.ReadyAsync; try { await manager.CreateCommandsAsync(_client.Rest, _client.Id, true); diff --git a/Tests/NetCord.Test/localizations/localization.pl.pl.pl.json b/Tests/NetCord.Test/localizations/localization.pl.pl.pl.json deleted file mode 100644 index e9cf1b27..00000000 --- a/Tests/NetCord.Test/localizations/localization.pl.pl.pl.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "commands": { - "ping": { - "description": "Sprawdza czy bot jest aktywny", - "parameters": { - "s": { - "name": "ś", - "description": "Wartość do zwrócenia" - } - } - }, - "permission-nested": { - "name": "permisja-zagnieżdżona", - "description": "Permisja", - "subcommands": { - "add": { - "name": "dodaj", - "description": "Dodaje permisję", - "parameters": { - "i": { - "name": "io" - }, - "permission": { - "name": "permisja", - "description": "Permisja do dodania" - } - } - }, - "remove": { - "name": "usuń", - "description": "Usuwa permisję" - }, - "list": { - "name": "lista", - "description": "Lista permisji", - "subcommands": { - "user": { - "name": "użytkownik", - "description": "Lista permisji użytkownika", - "parameters": { - "i": { - "name": "io" - }, - "permission": { - "name": "permisja", - "description": "Permisja do dodania" - } - } - }, - "role": { - "name": "rola", - "description": "Lista permisji roli" - } - } - } - } - } - }, - "enums": { - "NetCord.Test.SlashCommands.DeleteMessagesDays": { - "DontRemove": "Nie usuwaj", - "Last24Hours": "Ostatnie 24 godziny", - "Last2Days": "Ostatnie 2 dni", - "Last3Days": "Ostatnie 3 dni", - "Last4Days": "Ostatnie 4 dni", - "Last5Days": "Ostatnie 5 dni", - "Last6Days": "Ostatnie 6 dni", - "LastWeek": "Ostatni tydzień" - } - } -}