diff --git a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Sending/Program.cs b/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Sending/Program.cs deleted file mode 100644 index 1451d5c46..000000000 --- a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Sending/Program.cs +++ /dev/null @@ -1,13 +0,0 @@ -using NetCord; -using NetCord.Rest; - -using RestClient client = new(new BotToken("Token from Discord Developer Portal")); - -ulong channelId = 864636357821726730; // Note that it doesn't need to be the same channel id as channel id used to upload -var fileName = "file.txt"; -var uploadFileName = "cc7c13c1-a13d-4b3a-b978-e2c003466155/file.txt"; // It's returned when creating Google Cloud Platform Storage Bucket - -await client.SendMessageAsync(channelId, new() -{ - Attachments = [new GoogleCloudPlatformAttachmentProperties(fileName, uploadFileName)] -}); diff --git a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Uploading/Program.cs b/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Uploading/Program.cs deleted file mode 100644 index bec98022b..000000000 --- a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Uploading/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NetCord; -using NetCord.Rest; - -using RestClient client = new(new BotToken("Token from Discord Developer Portal")); - -ulong channelId = 857933275112800266; -var bucketName = "file.txt"; - -// Creating Google Cloud Platform Storage Bucket -var buckets = await client.CreateGoogleCloudPlatformStorageBucketsAsync(channelId, [new(bucketName)]); -var bucket = buckets.First(); - -using (HttpContent fileContent = new StringContent("File content")) -using (HttpClient httpClient = new()) -{ - // Uploading file content - var response = await httpClient.PutAsync(bucket.UploadUrl, fileContent); - response.EnsureSuccessStatusCode(); -} - -var uploadFileName = bucket.UploadFileName; -Console.WriteLine(uploadFileName); diff --git a/Documentation/guides/advanced/google-cloud-platform-attachments.md b/Documentation/guides/advanced/google-cloud-platform-attachments.md deleted file mode 100644 index 47a948181..000000000 --- a/Documentation/guides/advanced/google-cloud-platform-attachments.md +++ /dev/null @@ -1,14 +0,0 @@ -# Google Cloud Platform Attachments - -> [!WARNING] -> Discord does not offer any stability guarantees for this feature so it can break without notice. - -Discord supports uploading attachments directly on their Google Cloud Platform. You can then send the same file multiple times without sending its content multiple times. - -## Uploading File to Google Cloud Platform - -[!code-cs[Program.cs](GoogleCloudPlatformAttachments/Uploading/Program.cs)] - -## Sending Google Cloud Platform Attachment - -[!code-cs[Program.cs](GoogleCloudPlatformAttachments/Sending/Program.cs)] diff --git a/Documentation/guides/advanced/HttpInteractions/HttpInteractionHandler.cs b/Documentation/guides/basic-concepts/HttpInteractions/HttpInteractionHandler.cs similarity index 100% rename from Documentation/guides/advanced/HttpInteractions/HttpInteractionHandler.cs rename to Documentation/guides/basic-concepts/HttpInteractions/HttpInteractionHandler.cs diff --git a/Documentation/guides/advanced/HttpInteractions/HttpInteractions.csproj b/Documentation/guides/basic-concepts/HttpInteractions/HttpInteractions.csproj similarity index 100% rename from Documentation/guides/advanced/HttpInteractions/HttpInteractions.csproj rename to Documentation/guides/basic-concepts/HttpInteractions/HttpInteractions.csproj diff --git a/Documentation/guides/advanced/HttpInteractions/Program.cs b/Documentation/guides/basic-concepts/HttpInteractions/Program.cs similarity index 100% rename from Documentation/guides/advanced/HttpInteractions/Program.cs rename to Documentation/guides/basic-concepts/HttpInteractions/Program.cs diff --git a/Documentation/guides/advanced/HttpInteractions/Properties/launchSettings.json b/Documentation/guides/basic-concepts/HttpInteractions/Properties/launchSettings.json similarity index 100% rename from Documentation/guides/advanced/HttpInteractions/Properties/launchSettings.json rename to Documentation/guides/basic-concepts/HttpInteractions/Properties/launchSettings.json diff --git a/Documentation/guides/advanced/HttpInteractions/appsettings.json b/Documentation/guides/basic-concepts/HttpInteractions/appsettings.json similarity index 100% rename from Documentation/guides/advanced/HttpInteractions/appsettings.json rename to Documentation/guides/basic-concepts/HttpInteractions/appsettings.json diff --git a/Documentation/guides/basic-concepts/RespondingToInteractions/Program.cs b/Documentation/guides/basic-concepts/RespondingToInteractions/Program.cs new file mode 100644 index 000000000..b8987146f --- /dev/null +++ b/Documentation/guides/basic-concepts/RespondingToInteractions/Program.cs @@ -0,0 +1,104 @@ +using NetCord; +using NetCord.Rest; + +#pragma warning disable IDE0017, IDE0040, IDE0051, IDE0059, CS8321 + +static async Task RespondAsync() +{ + Interaction interaction = null!; + InteractionCallback callback = null!; + + await interaction.SendResponseAsync(callback); +} + +static void Callbacks() +{ + InteractionCallback callback; + + callback = InteractionCallback.Message("Here is a sample message interaction callback!"); + + callback = InteractionCallback.DeferredMessage(); + + callback = InteractionCallback.DeferredMessage(MessageFlags.Ephemeral); + + callback = InteractionCallback.DeferredModifyMessage; + + callback = InteractionCallback.ModifyMessage(message => message.Content = "New content!"); + + callback = InteractionCallback.ModifyMessage(message => message.WithContent("New content!")); + + callback = InteractionCallback.Modal(new("intro", "Introduce Yourself") + { + new TextInputProperties("name", TextInputStyle.Short, "First Name"), + new TextInputProperties("bio", TextInputStyle.Paragraph, "Your Bio"), + }); + + callback = InteractionCallback.Modal(new ModalProperties("intro", "Introduce Yourself") + .AddComponents( + new TextInputProperties("name", TextInputStyle.Short, "First Name"), + new TextInputProperties("bio", TextInputStyle.Paragraph, "Your Bio"))); + + callback = InteractionCallback.Modal(new("intro", "Introduce Yourself") + { + new TextInputProperties("name", TextInputStyle.Short, "First Name") + { + MinLength = 2, + MaxLength = 32, + Placeholder = "Enter your name", + }, + new TextInputProperties("bio", TextInputStyle.Paragraph, "Your Bio") + { + MinLength = 10, + Required = false, + Value = "I love programming!", + }, + }); + + callback = InteractionCallback.Modal(new ModalProperties("intro", "Introduce Yourself") + .AddComponents( + new TextInputProperties("name", TextInputStyle.Short, "First Name") + .WithMinLength(2) + .WithMaxLength(32) + .WithPlaceholder("Enter your name"), + new TextInputProperties("bio", TextInputStyle.Paragraph, "Your Bio") + .WithMinLength(10) + .WithRequired(false) + .WithValue("I love programming!"))); + + callback = InteractionCallback.Autocomplete([new("Dog", "dog"), new("Cat", "cat")]); + + callback = InteractionCallback.Autocomplete([new("Frog", 0), new("Duck", 1)]); + + callback = InteractionCallback.Autocomplete( + [ + new("Lion", "lion") + { + NameLocalizations = new Dictionary { ["pl"] = "Lew" }, + }, + new("Elephant", "elephant") + { + NameLocalizations = new Dictionary { ["pl"] = "Słoń" }, + }, + ]); + + callback = InteractionCallback.Autocomplete( + [ + new ApplicationCommandOptionChoiceProperties("Lion", "lion") + .WithNameLocalizations(new Dictionary { ["pl"] = "Lew" }), + new ApplicationCommandOptionChoiceProperties("Elephant", "elephant") + .WithNameLocalizations(new Dictionary { ["pl"] = "Słoń" }), + ]); + + callback = InteractionCallback.Pong; +} + +static async Task RespondDeferredAsync() +{ + Interaction interaction = null!; + + await interaction.ModifyResponseAsync(message => message.Content = "The response was modified!"); + + await interaction.ModifyResponseAsync(message => message.WithContent("The response was modified!")); + + await interaction.SendFollowupMessageAsync("The response was provided via follow-up!"); +} diff --git a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Uploading/Uploading.csproj b/Documentation/guides/basic-concepts/RespondingToInteractions/RespondingToInteractions.csproj similarity index 84% rename from Documentation/guides/advanced/GoogleCloudPlatformAttachments/Uploading/Uploading.csproj rename to Documentation/guides/basic-concepts/RespondingToInteractions/RespondingToInteractions.csproj index 5c6a0330c..075897b16 100644 --- a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Uploading/Uploading.csproj +++ b/Documentation/guides/basic-concepts/RespondingToInteractions/RespondingToInteractions.csproj @@ -11,7 +11,7 @@ - + diff --git a/Documentation/guides/basic-concepts/SendingMessages/Program.cs b/Documentation/guides/basic-concepts/SendingMessages/Program.cs new file mode 100644 index 000000000..84da35ba5 --- /dev/null +++ b/Documentation/guides/basic-concepts/SendingMessages/Program.cs @@ -0,0 +1,314 @@ +using NetCord; +using NetCord.Rest; + +#pragma warning disable IDE0017, IDE0040, IDE0051, IDE0059, CS8321 + +async static Task PropertiesAsync() +{ + IMessageProperties message = null!; + + message.Content = "Hello, World!"; + + message.WithContent("Hello, World!"); + + EmbedProperties embed; + + embed = new() + { + Title = "Welcome to the Baking Club!", + Description = "Join us for delicious recipes and baking tips!", + Url = "https://example.com", + Timestamp = DateTimeOffset.UtcNow, + Color = new(0xFFA500), + Footer = new() + { + Text = "Happy Baking!", + IconUrl = "https://example.com/images/baking-icon.png", + }, + Image = "https://example.com/images/cake.jpg", + Thumbnail = "https://example.com/images/rolling-pin.png", + Author = new() + { + Name = "Baking Club", + Url = "https://example.com", + IconUrl = "https://example.com/images/club-logo.png", + }, + Fields = + [ + new() + { + Name = "Today's Special Recipe", + Value = "Chocolate Lava Cake", + }, + new() + { + Name = "Next Meetup", + Value = "Sunday, 4 PM", + Inline = true, + }, + new() + { + Name = "Location", + Value = "123 Baker's Street", + Inline = true, + }, + new() + { + Name = "Membership Fee", + Value = "Free for the first month!", + Inline = true, + }, + ], + }; + + embed = new EmbedProperties() + .WithTitle("Welcome to the Baking Club!") + .WithDescription("Join us for delicious recipes and baking tips!") + .WithUrl("https://example.com") + .WithTimestamp(DateTimeOffset.UtcNow) + .WithColor(new(0xFFA500)) + .WithFooter(new EmbedFooterProperties() + .WithText("Happy Baking!") + .WithIconUrl("https://example.com/images/baking-icon.png")) + .WithImage("https://example/com/images/cake.jpg") + .WithThumbnail("https://example.com/images/rolling-pin.png") + .WithAuthor(new EmbedAuthorProperties() + .WithName("Baking Club") + .WithUrl("https://example.com") + .WithIconUrl("https://example.com/images/club-logo.png")) + .AddFields( + new EmbedFieldProperties() + .WithName("Today's Special Recipe") + .WithValue("Chocolate Lava Cake"), + new EmbedFieldProperties() + .WithName("Next Meetup") + .WithValue("Sunday, 4 PM") + .WithInline(), + new EmbedFieldProperties() + .WithName("Location") + .WithValue("123 Baker's Street") + .WithInline(), + new EmbedFieldProperties() + .WithName("Membership Fee") + .WithValue("Free for the first month!") + .WithInline()); + + message.Embeds = [embed]; + + message.AddEmbeds(embed); + + message.AllowedMentions = AllowedMentionsProperties.All; + + message.WithAllowedMentions(AllowedMentionsProperties.All); + + message.AllowedMentions = AllowedMentionsProperties.None; + + message.WithAllowedMentions(AllowedMentionsProperties.None); + + message.AllowedMentions = new() + { + Everyone = true, // Allow @everyone and @here + ReplyMention = true, // Allow reply mention + AllowedRoles = [988888771187581010], // Allow specific roles + AllowedUsers = [265546281693347841], // Allow specific users + }; + + message.WithAllowedMentions(new AllowedMentionsProperties() + .WithEveryone() // Allow @everyone and @here + .WithReplyMention() // Allow reply mention + .AddAllowedRoles(988888771187581010) // Allow specific roles + .AddAllowedUsers(265546281693347841)); // Allow specific users + + AttachmentProperties attachment; + + attachment = new AttachmentProperties("hello.txt", new MemoryStream("Hello!"u8.ToArray())); + + attachment = new Base64AttachmentProperties("hello.txt", new MemoryStream("SGVsbG8sIGJhc2U2NCE="u8.ToArray())); + + attachment = new QuotedPrintableAttachmentProperties("polish.txt", + new MemoryStream("R=C3=B3=C5=BCowy means pink"u8.ToArray())); + + TextChannel textChannel = null!; + + HttpClient httpClient = null!; + + // Create a bucket with a file named "hello.txt" + var buckets = await textChannel.CreateGoogleCloudPlatformStorageBucketsAsync([new("hello.txt")]); + + var bucket = buckets[0]; + + // Upload the file content to the bucket + var response = await httpClient.PutAsync(bucket.UploadUrl, new StringContent("Hello, Google!")); + response.EnsureSuccessStatusCode(); + + attachment = new GoogleCloudPlatformAttachmentProperties("hello.txt", bucket.UploadFileName); + + attachment.Title = "Hello, World!"; + attachment.Description = "This is a file named hello.txt"; + + attachment + .WithTitle("Hello, World!") + .WithDescription("This is a file named hello.txt"); + + message.Attachments = [attachment]; + + message.AddAttachments(attachment); + + ComponentProperties component; + + component = new ActionRowProperties + { + new ButtonProperties("welcome", "Welcome", new("👋"), ButtonStyle.Primary), + new ButtonProperties("hug", new EmojiProperties(356377264209920002), ButtonStyle.Success), + new ButtonProperties("goodbye", "Goodbye", ButtonStyle.Secondary) + { + Disabled = true, + }, + new LinkButtonProperties("https://netcord.dev", "Learn More"), + new PremiumButtonProperties(1271914991536312372), + }; + + component = new ActionRowProperties() + .AddButtons( + new ButtonProperties("welcome", "Welcome", new("👋"), ButtonStyle.Primary), + new ButtonProperties("hug", new EmojiProperties(356377264209920002), ButtonStyle.Success), + new ButtonProperties("goodbye", "Goodbye", ButtonStyle.Secondary) + .WithDisabled(), + new LinkButtonProperties("https://netcord.dev", "Learn More"), + new PremiumButtonProperties(1271914991536312372)); + + component = new StringMenuProperties("animal") + { + new("Dog", "dog") + { + Default = true, + Emoji = new("🐶"), + Description = "A loyal companion", + }, + new("Cat", "cat") + { + Emoji = new("🐱"), + Description = "A curious feline", + }, + new("Bird", "bird") + { + Emoji = new("🐦"), + Description = "A chirpy flyer", + }, + }; + + component = new StringMenuProperties("animal") + .AddOptions( + new StringMenuSelectOptionProperties("Dog", "dog") + .WithDefault() + .WithEmoji(new("🐶")) + .WithDescription("A loyal companion"), + new StringMenuSelectOptionProperties("Cat", "cat") + .WithEmoji(new("🐱")) + .WithDescription("A curious feline"), + new StringMenuSelectOptionProperties("Bird", "bird") + .WithEmoji(new("🐦")) + .WithDescription("A chirpy flyer")); + + component = new ChannelMenuProperties("channel") + { + DefaultValues = [1124777547687788626], + ChannelTypes = [ChannelType.ForumGuildChannel, ChannelType.PublicGuildThread], + }; + + component = new ChannelMenuProperties("channel") + .AddDefaultValues(1124777547687788626) + .AddChannelTypes(ChannelType.ForumGuildChannel, ChannelType.PublicGuildThread); + + component = new MentionableMenuProperties("mentionable") + { + DefaultValues = + [ + new(803324257194082314, MentionableValueType.User), + ], + }; + + component = new MentionableMenuProperties("mentionable") + .AddDefaultValues( + new MentionableValueProperties(803324257194082314, MentionableValueType.User)); + + component = new RoleMenuProperties("role") + { + DefaultValues = [803169206115237908], + }; + + component = new RoleMenuProperties("role") + .AddDefaultValues(803169206115237908); + + component = new UserMenuProperties("user") + { + DefaultValues = [233590074724319233], + }; + + component = new UserMenuProperties("user") + .AddDefaultValues(233590074724319233); + + message.Components = [component]; + + message.AddComponents(component); + + message.Flags = MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications; + + message.WithFlags(MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications); +} + +static void Menu() +{ + MenuProperties component = null!; + + component.Placeholder = "Select 2-5 animals"; + component.MinValues = 2; + component.MaxValues = 5; + component.Disabled = true; + + component + .WithPlaceholder("Select 2-5 animals") + .WithMinValues(2) + .WithMaxValues(5) + .WithDisabled(); +} + +static void ImplicitConversion() +{ + MessageProperties message = "Hello, World!"; +} + +static class Classic +{ + static T CreateMessage() where T : IMessageProperties, new() + { + return new() + { + Content = "Hello, World!", + Components = [], + }; + } +} + +static class Fluent +{ + static T CreateMessage() where T : IMessageProperties, new() + { + T message = new(); + + message + .WithContent("Hello, World!") + .WithComponents([]); + + return message; + } + + static void UseCreateMessage() + { + IMessageProperties message; + + message = CreateMessage(); + + message = CreateMessage(); + } +} diff --git a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Sending/Sending.csproj b/Documentation/guides/basic-concepts/SendingMessages/SendingMessages.csproj similarity index 76% rename from Documentation/guides/advanced/GoogleCloudPlatformAttachments/Sending/Sending.csproj rename to Documentation/guides/basic-concepts/SendingMessages/SendingMessages.csproj index f3097b9a8..075897b16 100644 --- a/Documentation/guides/advanced/GoogleCloudPlatformAttachments/Sending/Sending.csproj +++ b/Documentation/guides/basic-concepts/SendingMessages/SendingMessages.csproj @@ -1,4 +1,4 @@ - + Exe @@ -11,7 +11,7 @@ - + diff --git a/Documentation/guides/advanced/Sharding/Program.cs b/Documentation/guides/basic-concepts/Sharding/Program.cs similarity index 100% rename from Documentation/guides/advanced/Sharding/Program.cs rename to Documentation/guides/basic-concepts/Sharding/Program.cs diff --git a/Documentation/guides/advanced/Sharding/Sharding.csproj b/Documentation/guides/basic-concepts/Sharding/Sharding.csproj similarity index 100% rename from Documentation/guides/advanced/Sharding/Sharding.csproj rename to Documentation/guides/basic-concepts/Sharding/Sharding.csproj diff --git a/Documentation/guides/advanced/ShardingHosting/Program.cs b/Documentation/guides/basic-concepts/ShardingHosting/Program.cs similarity index 100% rename from Documentation/guides/advanced/ShardingHosting/Program.cs rename to Documentation/guides/basic-concepts/ShardingHosting/Program.cs diff --git a/Documentation/guides/advanced/ShardingHosting/ShardingHosting.csproj b/Documentation/guides/basic-concepts/ShardingHosting/ShardingHosting.csproj similarity index 100% rename from Documentation/guides/advanced/ShardingHosting/ShardingHosting.csproj rename to Documentation/guides/basic-concepts/ShardingHosting/ShardingHosting.csproj diff --git a/Documentation/guides/advanced/Voice/Program.cs b/Documentation/guides/basic-concepts/Voice/Program.cs similarity index 100% rename from Documentation/guides/advanced/Voice/Program.cs rename to Documentation/guides/basic-concepts/Voice/Program.cs diff --git a/Documentation/guides/advanced/Voice/Voice.csproj b/Documentation/guides/basic-concepts/Voice/Voice.csproj similarity index 100% rename from Documentation/guides/advanced/Voice/Voice.csproj rename to Documentation/guides/basic-concepts/Voice/Voice.csproj diff --git a/Documentation/guides/advanced/Voice/VoiceModule.cs b/Documentation/guides/basic-concepts/Voice/VoiceModule.cs similarity index 100% rename from Documentation/guides/advanced/Voice/VoiceModule.cs rename to Documentation/guides/basic-concepts/Voice/VoiceModule.cs diff --git a/Documentation/guides/advanced/http-interactions.md b/Documentation/guides/basic-concepts/http-interactions.md similarity index 100% rename from Documentation/guides/advanced/http-interactions.md rename to Documentation/guides/basic-concepts/http-interactions.md diff --git a/Documentation/guides/advanced/installing-native-dependencies.md b/Documentation/guides/basic-concepts/installing-native-dependencies.md similarity index 100% rename from Documentation/guides/advanced/installing-native-dependencies.md rename to Documentation/guides/basic-concepts/installing-native-dependencies.md diff --git a/Documentation/guides/basic-concepts/responding-to-interactions.md b/Documentation/guides/basic-concepts/responding-to-interactions.md new file mode 100644 index 000000000..761ff7584 --- /dev/null +++ b/Documentation/guides/basic-concepts/responding-to-interactions.md @@ -0,0 +1,134 @@ +--- +uid: responding-to-interactions +omitAppTitle: true +title: Responding to Interactions in C# Discord Bots with NetCord +description: Learn to handle interactions in your C# Discord bot with NetCord. Explore response types like messages, modals, and deferrals for building bots with .NET. +--- + +# Responding to Interactions + +This guide explains how to handle interactions and prepare appropriate responses. + +To create a response, use the @NetCord.Rest.InteractionCallback class. It supports various response types, such as messages, modals, and autocomplete suggestions. Once created, the callback must be passed to the @"NetCord.Interaction.SendResponseAsync*?text=Interaction.SendResponseAsync" method to send it to Discord. + +[!code-cs[Sample response](RespondingToInteractions/Program.cs#L11)] + +The [NetCord.Services](https://www.nuget.org/packages/NetCord.Services) package provides additional utilities to simplify this process. + +## Responding with a Message + +You can respond with a message by using @"NetCord.Rest.InteractionCallback.Message*?text=InteractionCallback.Message". + +[!code-cs[Responding with a message](RespondingToInteractions/Program.cs#L18)] + +For advanced message options, see @"sending-messages?text=Sending Messages". + +## Responding with Deferral + +Deferring is useful when performing long-running operations before sending a message. Deferrals give you up to 15 minutes to complete the operation. After deferring, you can either modify the initial response (the deferral) or send a follow-up message, both work the same way. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Responding after deferral](RespondingToInteractions/Program.cs#L99)] + +[!code-cs[Responding after deferral](RespondingToInteractions/Program.cs#L103)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Responding after deferral](RespondingToInteractions/Program.cs#L101)] + +[!code-cs[Responding after deferral](RespondingToInteractions/Program.cs#L103)] + +*** + +The [NetCord.Services](https://www.nuget.org/packages/NetCord.Services) package also provides shortcuts for deferral handling. + +For advanced message options, see @"sending-messages?text=Sending Messages". + +### Deferred Message + +For application commands, use @"NetCord.Rest.InteractionCallback.DeferredMessage*?text=InteractionCallback.DeferredMessage" to send a deferral response. This shows a loading state to the user while you prepare the message. + +[!code-cs[Responding with a deferred message](RespondingToInteractions/Program.cs#L20)] + +You can specify @"NetCord.MessageFlags.Ephemeral?text=MessageFlags.Ephemeral" to make the response visible only to the user who triggered the interaction. + +[!code-cs[Responding with a deferred message](RespondingToInteractions/Program.cs#L22)] + +### Deferred Modify Message + +For component interactions, use @"NetCord.Rest.InteractionCallback.DeferredModifyMessage?text=InteractionCallback.DeferredModifyMessage". This type of deferral doesn't display a loading state to the user. + +[!code-cs[Responding with a deferred modify message](RespondingToInteractions/Program.cs#L24)] + +## Responding with Message Modification + +For message component interactions, you can modify the message they are attached to using @"NetCord.Rest.InteractionCallback.ModifyMessage*?text=InteractionCallback.ModifyMessage". + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Responding with a modify message](RespondingToInteractions/Program.cs#L26)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Responding with a modify message](RespondingToInteractions/Program.cs#L28)] + +*** + +## Responding with a Modal + +Modals are interactive forms users can fill out. Use @"NetCord.Rest.InteractionCallback.Modal*?text=InteractionCallback.Modal" to create a modal callback. Each modal can include up to five text inputs, but no other component types are supported. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Responding with a modal](RespondingToInteractions/Program.cs#L30-L34)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Responding with a modal](RespondingToInteractions/Program.cs#L36-L39)] + +*** + +It is also supported to provide additional options for each input, such as placeholder text, default value, and whether the input is required. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Responding with a modal](RespondingToInteractions/Program.cs#L41-L55)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Responding with a modal](RespondingToInteractions/Program.cs#L57-L66)] + +*** + +## Responding with Autocomplete + +Autocomplete provides a list of options while the user types a slash command. Use @"NetCord.Rest.InteractionCallback.Autocomplete*?text=InteractionCallback.Autocomplete" to respond to autocomplete interactions. + +For string parameters: + +[!code-cs[Responding with autocomplete](RespondingToInteractions/Program.cs#L68)] + +For numeric parameters: + +[!code-cs[Responding with autocomplete](RespondingToInteractions/Program.cs#L70)] + +It is also supported to provide name localizations for each option. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Responding with autocomplete](RespondingToInteractions/Program.cs#L72-L82)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Responding with autocomplete](RespondingToInteractions/Program.cs#L84-L90)] + +*** + +## Responding with a Pong + +A "pong" callback is used to respond to ping HTTP interactions sent by Discord to verify the endpoint's availability. Use @"NetCord.Rest.InteractionCallback.Pong?text=InteractionCallback.Pong" to create this response. + +[!code-cs[Responding with a pong](RespondingToInteractions/Program.cs#L92)] + +If you're using the [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) package, these ping interactions are handled automatically, and you don't need to provide the callback manually. diff --git a/Documentation/guides/basic-concepts/sending-messages.md b/Documentation/guides/basic-concepts/sending-messages.md new file mode 100644 index 000000000..f6356f8f2 --- /dev/null +++ b/Documentation/guides/basic-concepts/sending-messages.md @@ -0,0 +1,319 @@ +--- +uid: sending-messages +omitAppTitle: true +title: Sending Messages in a C# Discord Bot with NetCord Library +description: Learn how to send messages in a C# Discord bot using NetCord, covering text, embeds, attachments, allowed mentions, and interactive components like buttons. +--- + +# Sending Messages + +This guide demonstrates how to send messages to a channel. + +Different types of message properties are available because various endpoints support different options. However, all these types implement @NetCord.Rest.IMessageProperties, which defines the properties supported by all endpoints. This guide will cover the properties defined in @NetCord.Rest.IMessageProperties. + +## Types of Message Properties + +The following message properties types are available: +- @NetCord.Rest.MessageProperties - Can be used with @"NetCord.Rest.RestClient.SendMessageAsync*?text=RestClient.SendMessageAsync" to send messages to channels. +- @NetCord.Rest.InteractionMessageProperties - Used for sending messages in response to interactions. Refer to @"responding-to-interactions?text=Responding to Interactions#responding-with-a-message" for more information. +- @NetCord.Rest.ReplyMessageProperties - Can be used with @"NetCord.Rest.RestMessage.ReplyAsync*?text=RestMessage.ReplyAsync" to reply to messages. +- @NetCord.Rest.WebhookMessageProperties - Can be used with @"NetCord.Rest.RestClient.ExecuteWebhookAsync*?text=RestClient.ExecuteWebhookAsync" to send messages via webhooks. +- @NetCord.Rest.ForumGuildThreadMessageProperties - Can be used with @"NetCord.Rest.RestClient.CreateForumGuildThreadAsync*?text=RestClient.CreateForumGuildThreadAsync" to create forum posts. + +All the @NetCord.Rest.RestClient methods mentioned above have their equivalent methods in appropriate objects, such as @NetCord.TextChannel and @NetCord.Rest.WebhookClient. + +## Using @NetCord.Rest.IMessageProperties + +If you need to build an API that supports creating messages for multiple endpoints, you can utilize the @NetCord.Rest.IMessageProperties interface. This allows you to define the properties common to all message properties types in a straightforward manner. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Using 'IMessageProperties'](SendingMessages/Program.cs#L283-L290)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Using 'IMessageProperties'](SendingMessages/Program.cs#L295-L304)] + +*** + +You can view sample usages of this method below: + +[!code-cs[Using our API for 'MessageProperties'](SendingMessages/Program.cs#L310)] +[!code-cs[Using our API for 'InteractionMessageProperties'](SendingMessages/Program.cs#L312)] + +## Defining the Content + +The content refers to the main text of the message. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Defining the content](SendingMessages/Program.cs#L10)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Defining the content](SendingMessages/Program.cs#L12)] + +*** + +All message properties types support an implicit conversion from a string. However, note that this does not apply to @NetCord.Rest.IMessageProperties, as it is an interface. + +[!code-cs[Implicit conversion from string](SendingMessages/Program.cs#L278)] + +## Adding Embeds + +Embeds are rich content elements that can be attached to a message. They support titles, descriptions, images, and more. A message can include up to 10 embeds. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Adding embeds](SendingMessages/Program.cs#L96)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Adding embeds](SendingMessages/Program.cs#L98)] + +*** + +### Customizing Embeds + +You can customize embeds by specifying properties such as the title, description, color, and image. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Customizing embeds](SendingMessages/Program.cs#L16-L62)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Customizing embeds](SendingMessages/Program.cs#L64-L94)] + +*** + +## Managing Allowed Mentions + +Allowed mentions define which users and roles can be mentioned within the message. + +To allow all mentions, use @"NetCord.Rest.AllowedMentionsProperties.All?text=AllowedMentionsProperties.All". + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Setting allowed mentions](SendingMessages/Program.cs#L100)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Setting allowed mentions](SendingMessages/Program.cs#L102)] + +*** + +To disable all mentions, use @"NetCord.Rest.AllowedMentionsProperties.None?text=AllowedMentionsProperties.None". + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Setting allowed mentions](SendingMessages/Program.cs#L104)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Setting allowed mentions](SendingMessages/Program.cs#L106)] + +*** + +You can also define custom allowed mentions. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Customizing allowed mentions](SendingMessages/Program.cs#L108-L114)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Customizing allowed mentions](SendingMessages/Program.cs#L116-L120)] + +*** + +## Attaching Files + +Attachments are files that can be added to a message. A message can contain up to 10 attachments. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Adding attachments](SendingMessages/Program.cs#L153)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Adding attachments](SendingMessages/Program.cs#L155)] + +*** + +### Customizing Attachments + +Attachments are files that can be added to a message, with up to 10 attachments per message. All attachment types support any readable stream, such as file streams. Below are examples of how you can create and customize attachments. + +#### Standard Attachments + +This is an example of a standard attachment. The following code creates an attachment with the content "Hello!". + +[!code-cs[Creating attachments](SendingMessages/Program.cs#L124)] + +#### Base64-Encoded Attachments + +You can create an attachment with base64 encoding. The following example demonstrates an attachment that will display "Hello, base64!" in the chat. + +[!code-cs[Base64 attachment](SendingMessages/Program.cs#L126)] + +#### Quoted-Printable Attachments + +Quoted-printable encoding is another supported format. The following example creates an attachment that will display "Różowy means pink" in the chat. + +[!code-cs[Quoted-printable attachment](SendingMessages/Program.cs#L128-L129)] + +#### Google Cloud Attachments + +Discord supports uploading attachments directly to Google Cloud Platform. This example creates an attachment uploaded directly to Google Cloud Platform with the content "Hello, Google!". + +[!code-cs[Google Cloud attachment](SendingMessages/Program.cs#L135-L144)] + +#### Adding Titles and Descriptions + +You can enhance your attachments by adding titles and descriptions. Use the `AttachmentProperties.Title` and `AttachmentProperties.Description` properties to set these values. Here's an example: + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Attachment properties](SendingMessages/Program.cs#L146-L147)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Attachment properties](SendingMessages/Program.cs#L149-L151)] + +*** + +## Adding Components + +Components are interactive elements that can be attached to a message. These include buttons, select menus, and more. A message can contain up to 5 components, including action rows and select menus. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Adding components](SendingMessages/Program.cs#L251)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Adding components](SendingMessages/Program.cs#L253)] + +*** + +### Action Rows + +Action rows contain buttons, and each can have up to 5 buttons. Available button types include: +- @NetCord.Rest.ButtonProperties, which triggers an interaction when clicked. +- @NetCord.Rest.LinkButtonProperties, which opens a URL when clicked. +- @NetCord.Rest.PremiumButtonProperties, which prompts the user to pay when clicked. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Defining action rows](SendingMessages/Program.cs#L159-L169)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Defining action rows](SendingMessages/Program.cs#L171-L178)] + +*** + +### Select Menus + +Select menus are dropdown menus containing up to 25 options. They support various types, such as strings, channels, and users. + +#### String Menus + +String menus allow you to include any string options. Each option in the menu can be customized with additional properties, such as setting a default selection, adding an emoji, or providing a description for better context. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Creating string menus](SendingMessages/Program.cs#L180-L198)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Creating string menus](SendingMessages/Program.cs#L200-L211)] + +*** + +#### Channel Menus + +Channel menus include channels as options, and support filtering by channel type and the ability to specify default channels. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Creating channel menus](SendingMessages/Program.cs#L213-L217)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Creating channel menus](SendingMessages/Program.cs#L219-L221)] + +*** + +#### Mentionable Menus + +Mentionable menus include users and roles as options, and support specifying default users and roles. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Creating mentionable menus](SendingMessages/Program.cs#L223-L229)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Creating mentionable menus](SendingMessages/Program.cs#L231-L233)] + +*** + +#### Role Menus + +Role menus contain roles as options, and support specifying default roles. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Creating role menus](SendingMessages/Program.cs#L235-L238)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Creating role menus](SendingMessages/Program.cs#L240-L241)] + +*** + +#### User Menus + +User menus allow selecting users as options, and support specifying default users. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Creating user menus](SendingMessages/Program.cs#L243-L246)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Creating user menus](SendingMessages/Program.cs#L248-L249)] + +*** + +#### Specifying Additional Properties + +Additionally, all select menus allow you to specify a placeholder, set a minimum and maximum number of selectable options, and disable the select menu if necessary. + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Configuring select menus](SendingMessages/Program.cs#L264-L267)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Configuring select menus](SendingMessages/Program.cs#L269-L273)] + +*** + +## Configuring Flags + +Flags control how a message behaves. You can combine multiple flags using the bitwise OR operator. + +For example, this configuration specifies that no embeds from URLs should be displayed, and the message should be silent (not triggering push or desktop notifications). + +# [Classic Syntax](#tab/classic-syntax) + +[!code-cs[Setting flags](SendingMessages/Program.cs#L255)] + +# [Fluent Syntax](#tab/fluent-syntax) + +[!code-cs[Setting flags](SendingMessages/Program.cs#L257)] + +*** diff --git a/Documentation/guides/advanced/sharding.md b/Documentation/guides/basic-concepts/sharding.md similarity index 100% rename from Documentation/guides/advanced/sharding.md rename to Documentation/guides/basic-concepts/sharding.md diff --git a/Documentation/guides/advanced/voice.md b/Documentation/guides/basic-concepts/voice.md similarity index 100% rename from Documentation/guides/advanced/voice.md rename to Documentation/guides/basic-concepts/voice.md diff --git a/Documentation/guides/services/component-interactions/Introduction/ModalModule.cs b/Documentation/guides/services/component-interactions/Introduction/ModalModule.cs index 32684f0ca..e2ec7c0fc 100644 --- a/Documentation/guides/services/component-interactions/Introduction/ModalModule.cs +++ b/Documentation/guides/services/component-interactions/Introduction/ModalModule.cs @@ -1,9 +1,11 @@ -using NetCord.Services.ComponentInteractions; +using NetCord; +using NetCord.Services.ComponentInteractions; namespace MyBot; public class ModalModule : ComponentInteractionModule { [ComponentInteraction("modal")] - public string Modal() => string.Join('\n', Context.Components.Select(c => $"{c.CustomId}: {c.Value}")); + public string Modal() => string.Join('\n', Context.Components.OfType() + .Select(i => $"{i.CustomId}: {i.Value}")); } diff --git a/Documentation/guides/services/component-interactions/IntroductionHosting/Program.cs b/Documentation/guides/services/component-interactions/IntroductionHosting/Program.cs index 50a21d6a7..db12b4190 100644 --- a/Documentation/guides/services/component-interactions/IntroductionHosting/Program.cs +++ b/Documentation/guides/services/component-interactions/IntroductionHosting/Program.cs @@ -27,7 +27,7 @@ .AddComponentInteraction("role", (RoleMenuInteractionContext context) => string.Join("\n", context.SelectedRoles)) .AddComponentInteraction("mentionable", (MentionableMenuInteractionContext context) => string.Join("\n", context.SelectedMentionables)) .AddComponentInteraction("channel", (ChannelMenuInteractionContext context) => string.Join("\n", context.SelectedChannels)) - .AddComponentInteraction("modal", (ModalInteractionContext context) => context.Components[0].Value); + .AddComponentInteraction("modal", (ModalInteractionContext context) => ((TextInput)context.Components[0]).Value); // Add component interactions from modules host.AddModules(typeof(Program).Assembly); diff --git a/Documentation/guides/toc.yml b/Documentation/guides/toc.yml index a03e72f76..82b0b304e 100644 --- a/Documentation/guides/toc.yml +++ b/Documentation/guides/toc.yml @@ -10,6 +10,18 @@ href: events/intents.md - name: First Events href: events/first-events.md +- name: Basic Concepts + items: + - name: Responding to Interactions + href: basic-concepts/responding-to-interactions.md + - name: Sending Messages + href: basic-concepts/sending-messages.md + - name: HTTP Interactions + href: basic-concepts/http-interactions.md + - name: Sharding + href: basic-concepts/sharding.md + - name: Voice + href: basic-concepts/voice.md - name: Services items: - name: Introduction @@ -50,13 +62,3 @@ href: services/preconditions.md - name: Custom Module Bases and Contexts href: services/custom-module-bases-and-contexts.md -- name: Advanced Concepts - items: - - name: HTTP Interactions - href: advanced/http-interactions.md - - name: Sharding - href: advanced/sharding.md - - name: Voice - href: advanced/voice.md - - name: Google Cloud Platform Attachments - href: advanced/google-cloud-platform-attachments.md \ No newline at end of file diff --git a/Documentation/templates-src/NetCord/src/main.css b/Documentation/templates-src/NetCord/src/main.css index 927412c37..9e8cb1634 100644 --- a/Documentation/templates-src/NetCord/src/main.css +++ b/Documentation/templates-src/NetCord/src/main.css @@ -95,6 +95,10 @@ article img { border-radius: 10px; } +.tabGroup > section > pre:last-of-type { + margin-bottom: 0; +} + article img { margin: 10px 0; box-shadow: 3px 3px 6px #111; diff --git a/Documentation/templates-src/NetCord/src/markdown.ts b/Documentation/templates-src/NetCord/src/markdown.ts index 6bbf02aee..a7ce997e5 100644 --- a/Documentation/templates-src/NetCord/src/markdown.ts +++ b/Documentation/templates-src/NetCord/src/markdown.ts @@ -456,7 +456,7 @@ function renderTabs() { notifyContentUpdated(); const top = info.anchor.getBoundingClientRect().top; if (top !== originalTop && event instanceof MouseEvent) { - window.scrollTo(0, window.pageYOffset + top - originalTop); + window.scrollTo({ top: window.pageYOffset + top - originalTop, behavior: "instant" }); } } diff --git a/NetCord.Services/ComponentInteractions/ComponentInteractionContexts.cs b/NetCord.Services/ComponentInteractions/ComponentInteractionContexts.cs index 1f0f72b88..424255451 100644 --- a/NetCord.Services/ComponentInteractions/ComponentInteractionContexts.cs +++ b/NetCord.Services/ComponentInteractions/ComponentInteractionContexts.cs @@ -342,7 +342,7 @@ public class ModalInteractionContext(ModalInteraction interaction, GatewayClient public User User => Interaction.User; public Guild? Guild => Interaction.Guild; public TextChannel Channel => Interaction.Channel; - public IReadOnlyList Components => Interaction.Data.Components; + public IReadOnlyList Components => Interaction.Data.Components; } public class HttpModalInteractionContext(ModalInteraction interaction, RestClient client) @@ -354,7 +354,7 @@ public class HttpModalInteractionContext(ModalInteraction interaction, RestClien public RestClient Client => client; public User User => Interaction.User; public TextChannel Channel => Interaction.Channel; - public IReadOnlyList Components => Interaction.Data.Components; + public IReadOnlyList Components => Interaction.Data.Components; } static file class Utils diff --git a/NetCord.sln b/NetCord.sln index 6f6149f56..056337269 100644 --- a/NetCord.sln +++ b/NetCord.sln @@ -49,15 +49,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Intents", "Documentation\gu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FirstEvents", "Documentation\guides\events\FirstEvents\FirstEvents.csproj", "{07CD40C3-245E-4303-8BC2-95F7D5AE68DE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "advanced", "advanced", "{6D8C2163-1B05-48A5-AF31-811A93D55FED}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Voice", "Documentation\guides\advanced\Voice\Voice.csproj", "{4FE771E4-C040-42CA-A4BC-8CDC15533309}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GoogleCloudPlatformAttachments", "GoogleCloudPlatformAttachments", "{15B64E0E-25C7-4696-88C9-9052A8156CE2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Uploading", "Documentation\guides\advanced\GoogleCloudPlatformAttachments\Uploading\Uploading.csproj", "{355740AA-B68C-4C76-8D99-D3E139DBB3ED}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sending", "Documentation\guides\advanced\GoogleCloudPlatformAttachments\Sending\Sending.csproj", "{53277F82-2B2C-4521-8FB1-FCBBC3965FBD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Voice", "Documentation\guides\basic-concepts\Voice\Voice.csproj", "{4FE771E4-C040-42CA-A4BC-8CDC15533309}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "services", "services", "{F69A383B-F073-4962-8EBC-29BB63F351CE}" EndProject @@ -93,7 +85,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomModuleBases", "Docume EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomContexts", "Documentation\guides\services\CustomModuleBasesAndContexts\CustomContexts\CustomContexts.csproj", "{1098C5F6-B9F1-4206-805C-5AC78315E946}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharding", "Documentation\guides\advanced\Sharding\Sharding.csproj", "{7FF73C26-720D-47FC-858C-74A5B545FE73}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharding", "Documentation\guides\basic-concepts\Sharding\Sharding.csproj", "{7FF73C26-720D-47FC-858C-74A5B545FE73}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Subcommands", "Documentation\guides\services\application-commands\Subcommands\Subcommands.csproj", "{9FD4FB24-2910-42B2-A89B-3E823145467E}" EndProject @@ -133,9 +125,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Introduction", "Documentati EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntroductionHosting", "Documentation\guides\services\text-commands\IntroductionHosting\IntroductionHosting.csproj", "{8EFD716E-8715-422D-BBC3-8FCA2ED9164A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShardingHosting", "Documentation\guides\advanced\ShardingHosting\ShardingHosting.csproj", "{D2D21E34-7F3D-4BAE-8CE7-0692C0E79094}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShardingHosting", "Documentation\guides\basic-concepts\ShardingHosting\ShardingHosting.csproj", "{D2D21E34-7F3D-4BAE-8CE7-0692C0E79094}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpInteractions", "Documentation\guides\advanced\HttpInteractions\HttpInteractions.csproj", "{311AF3BA-6AE3-4BBA-A4B1-8CD0901FFA9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpInteractions", "Documentation\guides\basic-concepts\HttpInteractions\HttpInteractions.csproj", "{311AF3BA-6AE3-4BBA-A4B1-8CD0901FFA9C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestClientMethodAliasesGenerator", "SourceGenerators\RestClientMethodAliasesGenerator\RestClientMethodAliasesGenerator.csproj", "{941F7A6C-313B-481E-9EBC-2B82972F8649}" EndProject @@ -145,6 +137,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoiceEncryptionTest", "Test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parameters", "Documentation\guides\services\component-interactions\Parameters\Parameters.csproj", "{C99E04D0-96EE-4671-980E-5183D2D6A72A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basic-concepts", "basic-concepts", "{EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RespondingToInteractions", "Documentation\guides\basic-concepts\RespondingToInteractions\RespondingToInteractions.csproj", "{0FAF1324-E5B2-4B4B-8B6F-7CE5AA175633}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SendingMessages", "Documentation\guides\basic-concepts\SendingMessages\SendingMessages.csproj", "{428D3B67-D0A7-4957-AB69-FE2CB5F99FBC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -207,14 +205,6 @@ Global {4FE771E4-C040-42CA-A4BC-8CDC15533309}.Debug|Any CPU.Build.0 = Debug|Any CPU {4FE771E4-C040-42CA-A4BC-8CDC15533309}.Release|Any CPU.ActiveCfg = Release|Any CPU {4FE771E4-C040-42CA-A4BC-8CDC15533309}.Release|Any CPU.Build.0 = Release|Any CPU - {355740AA-B68C-4C76-8D99-D3E139DBB3ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {355740AA-B68C-4C76-8D99-D3E139DBB3ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {355740AA-B68C-4C76-8D99-D3E139DBB3ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {355740AA-B68C-4C76-8D99-D3E139DBB3ED}.Release|Any CPU.Build.0 = Release|Any CPU - {53277F82-2B2C-4521-8FB1-FCBBC3965FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {53277F82-2B2C-4521-8FB1-FCBBC3965FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {53277F82-2B2C-4521-8FB1-FCBBC3965FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {53277F82-2B2C-4521-8FB1-FCBBC3965FBD}.Release|Any CPU.Build.0 = Release|Any CPU {DD2BD705-EA8B-4782-B513-83ABD8308F67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DD2BD705-EA8B-4782-B513-83ABD8308F67}.Debug|Any CPU.Build.0 = Debug|Any CPU {DD2BD705-EA8B-4782-B513-83ABD8308F67}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -359,6 +349,14 @@ Global {C99E04D0-96EE-4671-980E-5183D2D6A72A}.Debug|Any CPU.Build.0 = Debug|Any CPU {C99E04D0-96EE-4671-980E-5183D2D6A72A}.Release|Any CPU.ActiveCfg = Release|Any CPU {C99E04D0-96EE-4671-980E-5183D2D6A72A}.Release|Any CPU.Build.0 = Release|Any CPU + {0FAF1324-E5B2-4B4B-8B6F-7CE5AA175633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FAF1324-E5B2-4B4B-8B6F-7CE5AA175633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0FAF1324-E5B2-4B4B-8B6F-7CE5AA175633}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FAF1324-E5B2-4B4B-8B6F-7CE5AA175633}.Release|Any CPU.Build.0 = Release|Any CPU + {428D3B67-D0A7-4957-AB69-FE2CB5F99FBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {428D3B67-D0A7-4957-AB69-FE2CB5F99FBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {428D3B67-D0A7-4957-AB69-FE2CB5F99FBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {428D3B67-D0A7-4957-AB69-FE2CB5F99FBC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -378,11 +376,7 @@ Global {3678BAB0-28A1-4F41-86F7-AD1CC167AFD2} = {FDFF38F4-A459-4809-BA63-01A2BEC131ED} {F78C310B-4461-4CC3-94F8-86704FAE787A} = {3678BAB0-28A1-4F41-86F7-AD1CC167AFD2} {07CD40C3-245E-4303-8BC2-95F7D5AE68DE} = {3678BAB0-28A1-4F41-86F7-AD1CC167AFD2} - {6D8C2163-1B05-48A5-AF31-811A93D55FED} = {FDFF38F4-A459-4809-BA63-01A2BEC131ED} - {4FE771E4-C040-42CA-A4BC-8CDC15533309} = {6D8C2163-1B05-48A5-AF31-811A93D55FED} - {15B64E0E-25C7-4696-88C9-9052A8156CE2} = {6D8C2163-1B05-48A5-AF31-811A93D55FED} - {355740AA-B68C-4C76-8D99-D3E139DBB3ED} = {15B64E0E-25C7-4696-88C9-9052A8156CE2} - {53277F82-2B2C-4521-8FB1-FCBBC3965FBD} = {15B64E0E-25C7-4696-88C9-9052A8156CE2} + {4FE771E4-C040-42CA-A4BC-8CDC15533309} = {EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9} {F69A383B-F073-4962-8EBC-29BB63F351CE} = {FDFF38F4-A459-4809-BA63-01A2BEC131ED} {4EC1FB11-D5A6-48ED-B46A-0C5769E2F962} = {F69A383B-F073-4962-8EBC-29BB63F351CE} {DD2BD705-EA8B-4782-B513-83ABD8308F67} = {4EC1FB11-D5A6-48ED-B46A-0C5769E2F962} @@ -400,7 +394,7 @@ Global {9D513F65-7725-466C-B62E-0E988BF2CCDE} = {F69A383B-F073-4962-8EBC-29BB63F351CE} {91D34DA8-B753-4174-8F16-702E322BDCD8} = {9D513F65-7725-466C-B62E-0E988BF2CCDE} {1098C5F6-B9F1-4206-805C-5AC78315E946} = {9D513F65-7725-466C-B62E-0E988BF2CCDE} - {7FF73C26-720D-47FC-858C-74A5B545FE73} = {6D8C2163-1B05-48A5-AF31-811A93D55FED} + {7FF73C26-720D-47FC-858C-74A5B545FE73} = {EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9} {9FD4FB24-2910-42B2-A89B-3E823145467E} = {46E6F97B-83C3-4B66-8B0F-226D02DEC5A0} {E844293D-91EB-4E6E-9E57-8B9C109C6640} = {0A9D75D2-B760-43E3-8A18-1BF2F629D0FB} {624246E3-4194-4952-BDDD-52254E94D5D6} = {0A9D75D2-B760-43E3-8A18-1BF2F629D0FB} @@ -419,12 +413,15 @@ Global {8C7E6E04-0DA4-4F1F-88D1-4F4F74460BDD} = {5631D9FD-493B-40C6-85CC-D8742DF012CB} {9504529C-47DF-4F5C-AF2B-B3CFF3D051F2} = {4EC1FB11-D5A6-48ED-B46A-0C5769E2F962} {8EFD716E-8715-422D-BBC3-8FCA2ED9164A} = {4EC1FB11-D5A6-48ED-B46A-0C5769E2F962} - {D2D21E34-7F3D-4BAE-8CE7-0692C0E79094} = {6D8C2163-1B05-48A5-AF31-811A93D55FED} - {311AF3BA-6AE3-4BBA-A4B1-8CD0901FFA9C} = {6D8C2163-1B05-48A5-AF31-811A93D55FED} + {D2D21E34-7F3D-4BAE-8CE7-0692C0E79094} = {EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9} + {311AF3BA-6AE3-4BBA-A4B1-8CD0901FFA9C} = {EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9} {941F7A6C-313B-481E-9EBC-2B82972F8649} = {0A9D75D2-B760-43E3-8A18-1BF2F629D0FB} {9E6C8288-7762-4F98-85B1-A3ED29EE0DFF} = {46E6F97B-83C3-4B66-8B0F-226D02DEC5A0} {98A70693-9EC7-4965-BC8F-A5646723DAA2} = {5E156987-5CC6-4873-B774-F41CBD7D3F3D} {C99E04D0-96EE-4671-980E-5183D2D6A72A} = {5631D9FD-493B-40C6-85CC-D8742DF012CB} + {EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9} = {FDFF38F4-A459-4809-BA63-01A2BEC131ED} + {0FAF1324-E5B2-4B4B-8B6F-7CE5AA175633} = {EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9} + {428D3B67-D0A7-4957-AB69-FE2CB5F99FBC} = {EA1542CE-C8E4-4995-A2D1-D69A4AAF17D9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {945CA52C-0E28-4020-A241-BDE73D8384AA} diff --git a/NetCord/Components/ActionRow.cs b/NetCord/Components/ActionRow.cs index e12940135..6e00c62fc 100644 --- a/NetCord/Components/ActionRow.cs +++ b/NetCord/Components/ActionRow.cs @@ -2,7 +2,7 @@ namespace NetCord; -public class ActionRow(JsonComponent jsonModel) : IMessageComponent, IJsonModel +public class ActionRow(JsonComponent jsonModel) : IComponent, IJsonModel { JsonComponent IJsonModel.JsonModel => jsonModel; diff --git a/NetCord/Components/IComponent.cs b/NetCord/Components/IComponent.cs index 3f984b146..7bf130e9e 100644 --- a/NetCord/Components/IComponent.cs +++ b/NetCord/Components/IComponent.cs @@ -2,4 +2,29 @@ public interface IComponent { + public static IComponent CreateFromJson(JsonModels.JsonComponent jsonModel) + { + var components = jsonModel.Components; + + if (components.Length is 0) + return new UnknownComponent(jsonModel); + + var firstComponent = components[0]; + + return jsonModel.Type switch + { + ComponentType.ActionRow => firstComponent.Type switch + { + ComponentType.Button => new ActionRow(firstComponent), + ComponentType.StringMenu => new StringMenu(firstComponent), + ComponentType.UserMenu => new UserMenu(firstComponent), + ComponentType.ChannelMenu => new ChannelMenu(firstComponent), + ComponentType.RoleMenu => new RoleMenu(firstComponent), + ComponentType.MentionableMenu => new MentionableMenu(firstComponent), + ComponentType.TextInput => new TextInput(firstComponent), + _ => new UnknownComponent(jsonModel), + }, + _ => new UnknownComponent(jsonModel), + }; + } } diff --git a/NetCord/Components/IMessageComponent.cs b/NetCord/Components/IMessageComponent.cs deleted file mode 100644 index 9bfcc8be8..000000000 --- a/NetCord/Components/IMessageComponent.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NetCord; - -public interface IMessageComponent : IComponent -{ - public static IMessageComponent CreateFromJson(JsonModels.JsonComponent jsonModel) - { - var component = jsonModel.Components[0]; - return jsonModel.Type switch - { - ComponentType.ActionRow => component.Type switch - { - ComponentType.Button => new ActionRow(component), - ComponentType.StringMenu => new StringMenu(component), - ComponentType.UserMenu => new UserMenu(component), - ComponentType.ChannelMenu => new ChannelMenu(component), - ComponentType.RoleMenu => new RoleMenu(component), - ComponentType.MentionableMenu => new MentionableMenu(component), - _ => new UnknownMessageComponent(component), - }, - _ => new UnknownMessageComponent(component), - }; - } -} diff --git a/NetCord/Components/IUnknownComponent.cs b/NetCord/Components/IUnknownComponent.cs new file mode 100644 index 000000000..cddcf344f --- /dev/null +++ b/NetCord/Components/IUnknownComponent.cs @@ -0,0 +1,15 @@ +using NetCord.JsonModels; + +namespace NetCord; + +public interface IUnknownComponent : IComponent +{ + public ComponentType Type { get; } +} + +internal class UnknownComponent(JsonComponent jsonModel) : IUnknownComponent, IJsonModel +{ + JsonComponent IJsonModel.JsonModel => jsonModel; + + public ComponentType Type => jsonModel.Type; +} diff --git a/NetCord/Components/IUnknownMessageComponent.cs b/NetCord/Components/IUnknownMessageComponent.cs deleted file mode 100644 index 00c5c6a54..000000000 --- a/NetCord/Components/IUnknownMessageComponent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace NetCord; - -public interface IUnknownMessageComponent : IMessageComponent -{ - public ComponentType Type { get; } -} diff --git a/NetCord/Components/Menu.cs b/NetCord/Components/Menu.cs index 60066e8a1..f62074e8c 100644 --- a/NetCord/Components/Menu.cs +++ b/NetCord/Components/Menu.cs @@ -2,7 +2,7 @@ namespace NetCord; -public abstract class Menu(JsonComponent jsonModel) : IMessageComponent, IJsonModel +public abstract class Menu(JsonComponent jsonModel) : IComponent, IJsonModel { JsonComponent IJsonModel.JsonModel => _jsonModel; private protected readonly JsonComponent _jsonModel = jsonModel; diff --git a/NetCord/Components/TextInput.cs b/NetCord/Components/TextInput.cs index 7bf6018c7..8db7f6c23 100644 --- a/NetCord/Components/TextInput.cs +++ b/NetCord/Components/TextInput.cs @@ -4,9 +4,8 @@ namespace NetCord; public class TextInput(JsonComponent jsonModel) : IComponent, IJsonModel { - JsonComponent IJsonModel.JsonModel => _jsonModel; - private readonly JsonComponent _jsonModel = jsonModel.Components[0]; + JsonComponent IJsonModel.JsonModel => jsonModel; - public string CustomId => _jsonModel.CustomId!; - public string Value => _jsonModel.Value!; + public string CustomId => jsonModel.CustomId!; + public string Value => jsonModel.Value!; } diff --git a/NetCord/Components/UnknownMessageComponent.cs b/NetCord/Components/UnknownMessageComponent.cs deleted file mode 100644 index 1b280e6c0..000000000 --- a/NetCord/Components/UnknownMessageComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -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/JsonConverters/TextInputPropertiesIEnumerableConverter.cs b/NetCord/JsonConverters/TextInputPropertiesIEnumerableConverter.cs deleted file mode 100644 index b002a25c7..000000000 --- a/NetCord/JsonConverters/TextInputPropertiesIEnumerableConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -using NetCord.Rest; - -namespace NetCord.JsonConverters; - -public class TextInputPropertiesIEnumerableConverter : JsonConverter> -{ - private static readonly JsonEncodedText _type = JsonEncodedText.Encode("type"); - private static readonly JsonEncodedText _components = JsonEncodedText.Encode("components"); - - public override IEnumerable? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException(); - public override void Write(Utf8JsonWriter writer, IEnumerable value, JsonSerializerOptions options) - { - writer.WriteStartArray(); - foreach (var textInput in value) - { - writer.WriteStartObject(); - writer.WriteNumber(_type, 1); - writer.WriteStartArray(_components); - JsonSerializer.Serialize(writer, textInput, Serialization.Default.TextInputProperties); - writer.WriteEndArray(); - writer.WriteEndObject(); - } - writer.WriteEndArray(); - } -} diff --git a/NetCord/ModalInteractionData.cs b/NetCord/ModalInteractionData.cs index f162927d6..8a853fd4e 100644 --- a/NetCord/ModalInteractionData.cs +++ b/NetCord/ModalInteractionData.cs @@ -2,5 +2,5 @@ public class ModalInteractionData(JsonModels.JsonInteractionData jsonModel) : ComponentInteractionData(jsonModel) { - public IReadOnlyList Components { get; } = jsonModel.Components!.Select(c => new TextInput(c)).ToArray(); + public IReadOnlyList Components { get; } = jsonModel.Components!.Select(IComponent.CreateFromJson).ToArray(); } diff --git a/NetCord/Rest/ComponentProperties/ActionRowProperties.cs b/NetCord/Rest/ComponentProperties/ActionRowProperties.cs index 07cc0e5e7..23ca2c13f 100644 --- a/NetCord/Rest/ComponentProperties/ActionRowProperties.cs +++ b/NetCord/Rest/ComponentProperties/ActionRowProperties.cs @@ -10,7 +10,7 @@ namespace NetCord.Rest; /// /// Buttons of the action row (max 5). [CollectionBuilder(typeof(ActionRowProperties), nameof(Create))] -public partial class ActionRowProperties(IEnumerable buttons) : MessageComponentProperties, IEnumerable +public partial class ActionRowProperties(IEnumerable buttons) : ComponentProperties, IEnumerable { public ActionRowProperties() : this([]) { diff --git a/NetCord/Rest/ComponentProperties/ComponentProperties.cs b/NetCord/Rest/ComponentProperties/ComponentProperties.cs index 4dd6ee237..e065a3663 100644 --- a/NetCord/Rest/ComponentProperties/ComponentProperties.cs +++ b/NetCord/Rest/ComponentProperties/ComponentProperties.cs @@ -1,7 +1,9 @@ -using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; namespace NetCord.Rest; +[JsonConverter(typeof(ComponentPropertiesConverter))] public abstract partial class ComponentProperties { /// @@ -9,4 +11,57 @@ public abstract partial class ComponentProperties /// [JsonPropertyName("type")] public abstract ComponentType ComponentType { get; } + + public class ComponentPropertiesConverter : JsonConverter + { + private static readonly JsonEncodedText _type = JsonEncodedText.Encode("type"); + private static readonly JsonEncodedText _components = JsonEncodedText.Encode("components"); + + public override ComponentProperties Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException(); + + public override void Write(Utf8JsonWriter writer, ComponentProperties component, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteNumber(_type, 1); + + if (component is ActionRowProperties actionRowProperties) + { + writer.WritePropertyName(_components); + JsonSerializer.Serialize(writer, actionRowProperties.Buttons, Serialization.Default.IEnumerableIButtonProperties); + } + else + { + writer.WriteStartArray(_components); + + switch (component) + { + case StringMenuProperties stringMenuProperties: + JsonSerializer.Serialize(writer, stringMenuProperties, Serialization.Default.IStringMenuProperties); + break; + case UserMenuProperties userMenuProperties: + JsonSerializer.Serialize(writer, userMenuProperties, Serialization.Default.UserMenuProperties); + break; + case RoleMenuProperties roleMenuProperties: + JsonSerializer.Serialize(writer, roleMenuProperties, Serialization.Default.RoleMenuProperties); + break; + case MentionableMenuProperties mentionableMenuProperties: + JsonSerializer.Serialize(writer, mentionableMenuProperties, Serialization.Default.MentionableMenuProperties); + break; + case ChannelMenuProperties channelMenuProperties: + JsonSerializer.Serialize(writer, channelMenuProperties, Serialization.Default.ChannelMenuProperties); + break; + case TextInputProperties textInputProperties: + JsonSerializer.Serialize(writer, textInputProperties, Serialization.Default.TextInputProperties); + break; + default: + throw new InvalidOperationException($"Invalid '{nameof(ComponentProperties)}' value."); + } + + writer.WriteEndArray(); + } + + writer.WriteEndObject(); + } + } } diff --git a/NetCord/Rest/ComponentProperties/MenuProperties.cs b/NetCord/Rest/ComponentProperties/MenuProperties.cs index 0427c2a0d..4269e61a8 100644 --- a/NetCord/Rest/ComponentProperties/MenuProperties.cs +++ b/NetCord/Rest/ComponentProperties/MenuProperties.cs @@ -6,7 +6,7 @@ namespace NetCord.Rest; /// /// /// ID for the menu (max 100 characters). -public abstract partial class MenuProperties(string customId) : MessageComponentProperties +public abstract partial class MenuProperties(string customId) : ComponentProperties { /// /// ID for the menu (max 100 characters). diff --git a/NetCord/Rest/ComponentProperties/MessageComponentProperties.cs b/NetCord/Rest/ComponentProperties/MessageComponentProperties.cs deleted file mode 100644 index 50a33ee22..000000000 --- a/NetCord/Rest/ComponentProperties/MessageComponentProperties.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace NetCord.Rest; - -[JsonConverter(typeof(MessageComponentConverter))] -public abstract partial class MessageComponentProperties : ComponentProperties -{ - public class MessageComponentConverter : JsonConverter - { - private static readonly JsonEncodedText _type = JsonEncodedText.Encode("type"); - private static readonly JsonEncodedText _components = JsonEncodedText.Encode("components"); - - public override MessageComponentProperties Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException(); - - public override void Write(Utf8JsonWriter writer, MessageComponentProperties component, JsonSerializerOptions options) - { - writer.WriteStartObject(); - - writer.WriteNumber(_type, 1); - - if (component is ActionRowProperties actionRowProperties) - { - writer.WritePropertyName(_components); - JsonSerializer.Serialize(writer, actionRowProperties.Buttons, Serialization.Default.IEnumerableIButtonProperties); - } - else - { - writer.WriteStartArray(_components); - - switch (component) - { - case StringMenuProperties stringMenuProperties: - JsonSerializer.Serialize(writer, stringMenuProperties, Serialization.Default.StringMenuProperties); - break; - case UserMenuProperties userMenuProperties: - JsonSerializer.Serialize(writer, userMenuProperties, Serialization.Default.UserMenuProperties); - break; - case RoleMenuProperties roleMenuProperties: - JsonSerializer.Serialize(writer, roleMenuProperties, Serialization.Default.RoleMenuProperties); - break; - case MentionableMenuProperties mentionableMenuProperties: - JsonSerializer.Serialize(writer, mentionableMenuProperties, Serialization.Default.MentionableMenuProperties); - break; - case ChannelMenuProperties channelMenuProperties: - JsonSerializer.Serialize(writer, channelMenuProperties, Serialization.Default.ChannelMenuProperties); - break; - default: - throw new InvalidOperationException($"Invalid {nameof(MessageComponentProperties)} value."); - } - - writer.WriteEndArray(); - } - - writer.WriteEndObject(); - } - } -} diff --git a/NetCord/Rest/ComponentProperties/StringMenuProperties.cs b/NetCord/Rest/ComponentProperties/StringMenuProperties.cs index 4414e2131..922ba4163 100644 --- a/NetCord/Rest/ComponentProperties/StringMenuProperties.cs +++ b/NetCord/Rest/ComponentProperties/StringMenuProperties.cs @@ -1,11 +1,53 @@ -using System.Text.Json.Serialization; +using System.Collections; +using System.ComponentModel; +using System.Text.Json.Serialization; namespace NetCord.Rest; -public partial class StringMenuProperties(string customId, IEnumerable options) : MenuProperties(customId) +public partial class StringMenuProperties(string customId, IEnumerable options) : MenuProperties(customId), IStringMenuProperties, IEnumerable { + public StringMenuProperties(string customId) : this(customId, []) + { + } + public override ComponentType ComponentType => ComponentType.StringMenu; [JsonPropertyName("options")] public IEnumerable Options { get; set; } = options; + + [EditorBrowsable(EditorBrowsableState.Never)] + public void Add(StringMenuSelectOptionProperties option) => AddOptions(option); + + IEnumerator IEnumerable.GetEnumerator() => Options.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Options).GetEnumerator(); +} + +// Required not to serialize 'StringMenuProperties' as 'IEnumerable' +// https://github.com/dotnet/runtime/issues/63791 +internal interface IStringMenuProperties +{ + [JsonPropertyName("type")] + public ComponentType ComponentType { get; } + + [JsonPropertyName("custom_id")] + public string CustomId { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("placeholder")] + public string? Placeholder { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("min_values")] + public int? MinValues { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("max_values")] + public int? MaxValues { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + [JsonPropertyName("disabled")] + public bool Disabled { get; set; } + + [JsonPropertyName("options")] + public IEnumerable Options { get; set; } } diff --git a/NetCord/Rest/ForumGuildThreadMessageProperties.cs b/NetCord/Rest/ForumGuildThreadMessageProperties.cs index f5451880f..c4ddc57bc 100644 --- a/NetCord/Rest/ForumGuildThreadMessageProperties.cs +++ b/NetCord/Rest/ForumGuildThreadMessageProperties.cs @@ -18,7 +18,7 @@ public partial class ForumGuildThreadMessageProperties : IMessageProperties [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("components")] - public IEnumerable? Components { get; set; } + public IEnumerable? Components { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("sticker_ids")] diff --git a/NetCord/Rest/IMessageProperties.cs b/NetCord/Rest/IMessageProperties.cs index c4d3947bd..474fcfd1a 100644 --- a/NetCord/Rest/IMessageProperties.cs +++ b/NetCord/Rest/IMessageProperties.cs @@ -10,7 +10,7 @@ public partial interface IMessageProperties public IEnumerable? Attachments { get; set; } - public IEnumerable? Components { get; set; } + public IEnumerable? Components { get; set; } public MessageFlags? Flags { get; set; } } diff --git a/NetCord/Rest/InteractionCallback.cs b/NetCord/Rest/InteractionCallback.cs index aa01f2526..ebab80114 100644 --- a/NetCord/Rest/InteractionCallback.cs +++ b/NetCord/Rest/InteractionCallback.cs @@ -90,7 +90,7 @@ public HttpContent Serialize() return new JsonContent>(interactionCallback, Serialization.Default.InteractionCallbackInteractionCallbackChoicesDataProperties); case InteractionCallback interactionCallback: - return new JsonContent>(interactionCallback, Serialization.Default.InteractionCallbackModalProperties); + return new JsonContent>(interactionCallback, Serialization.Default.IInteractionCallbackIModalProperties); default: return new JsonContent(this, Serialization.Default.InteractionCallback); @@ -98,7 +98,7 @@ public HttpContent Serialize() } } -public class InteractionCallback : InteractionCallback +public class InteractionCallback : InteractionCallback, IInteractionCallback { internal InteractionCallback(InteractionCallbackType type, T data) : base(type) { @@ -108,3 +108,12 @@ internal InteractionCallback(InteractionCallbackType type, T data) : base(type) [JsonPropertyName("data")] public T Data { get; } } + +internal interface IInteractionCallback +{ + [JsonPropertyName("type")] + public InteractionCallbackType Type { get; } + + [JsonPropertyName("data")] + public T Data { get; } +} diff --git a/NetCord/Rest/InteractionMessageProperties.cs b/NetCord/Rest/InteractionMessageProperties.cs index 6fa8bb554..275cc3d56 100644 --- a/NetCord/Rest/InteractionMessageProperties.cs +++ b/NetCord/Rest/InteractionMessageProperties.cs @@ -26,7 +26,7 @@ public partial class InteractionMessageProperties : IHttpSerializable, IMessageP [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("components")] - public IEnumerable? Components { get; set; } + public IEnumerable? Components { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonConverter(typeof(JsonConverters.AttachmentPropertiesIEnumerableConverter))] diff --git a/NetCord/Rest/MessageOptions.cs b/NetCord/Rest/MessageOptions.cs index 92022c687..c7123cc33 100644 --- a/NetCord/Rest/MessageOptions.cs +++ b/NetCord/Rest/MessageOptions.cs @@ -26,7 +26,7 @@ internal MessageOptions() [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("components")] - public IEnumerable? Components { get; set; } + public IEnumerable? Components { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonConverter(typeof(JsonConverters.AttachmentPropertiesIEnumerableConverter))] diff --git a/NetCord/Rest/MessageProperties.cs b/NetCord/Rest/MessageProperties.cs index b7773825a..3472c2a00 100644 --- a/NetCord/Rest/MessageProperties.cs +++ b/NetCord/Rest/MessageProperties.cs @@ -35,7 +35,7 @@ public partial class MessageProperties : IHttpSerializable, IMessageProperties [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("components")] - public IEnumerable? Components { get; set; } + public IEnumerable? Components { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("sticker_ids")] diff --git a/NetCord/Rest/ModalProperties.cs b/NetCord/Rest/ModalProperties.cs index ea6836fa6..5e50a4bb4 100644 --- a/NetCord/Rest/ModalProperties.cs +++ b/NetCord/Rest/ModalProperties.cs @@ -1,16 +1,41 @@ -using System.Text.Json.Serialization; +using System.Collections; +using System.ComponentModel; +using System.Text.Json.Serialization; namespace NetCord.Rest; -public partial class ModalProperties(string customId, string title, IEnumerable components) +public partial class ModalProperties(string customId, string title, IEnumerable components) : IModalProperties, IEnumerable { + public ModalProperties(string customId, string title) : this(customId, title, []) + { + } + [JsonPropertyName("custom_id")] public string CustomId { get; set; } = customId; [JsonPropertyName("title")] public string Title { get; set; } = title; - [JsonConverter(typeof(JsonConverters.TextInputPropertiesIEnumerableConverter))] [JsonPropertyName("components")] - public IEnumerable Components { get; set; } = components; + public IEnumerable Components { get; set; } = components; + + [EditorBrowsable(EditorBrowsableState.Never)] + public void Add(ComponentProperties component) => AddComponents(component); + + IEnumerator IEnumerable.GetEnumerator() => Components.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Components).GetEnumerator(); +} + +// Required not to serialize 'ModalProperties' as 'IEnumerable' +// https://github.com/dotnet/runtime/issues/63791 +internal interface IModalProperties +{ + [JsonPropertyName("custom_id")] + public string CustomId { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } + + [JsonPropertyName("components")] + public IEnumerable Components { get; set; } } diff --git a/NetCord/Rest/ReplyMessageProperties.cs b/NetCord/Rest/ReplyMessageProperties.cs index 709511178..cb8c73bc9 100644 --- a/NetCord/Rest/ReplyMessageProperties.cs +++ b/NetCord/Rest/ReplyMessageProperties.cs @@ -9,7 +9,7 @@ public partial class ReplyMessageProperties : IMessageProperties public IEnumerable? Embeds { get; set; } public AllowedMentionsProperties? AllowedMentions { get; set; } public bool? FailIfNotExists { get; set; } - public IEnumerable? Components { get; set; } + public IEnumerable? Components { get; set; } public IEnumerable? StickerIds { get; set; } public MessageFlags? Flags { get; set; } public MessagePollProperties? Poll { get; set; } diff --git a/NetCord/Rest/RestClient.Undocumented.cs b/NetCord/Rest/RestClient.Undocumented.cs index 302fccd8b..efba595b2 100644 --- a/NetCord/Rest/RestClient.Undocumented.cs +++ b/NetCord/Rest/RestClient.Undocumented.cs @@ -9,10 +9,10 @@ public async Task GetApplicationAsync(ulong applicationId, RestRequ => 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, CancellationToken cancellationToken = default) + 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, cancellationToken: cancellationToken).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)).ToArray(); } [GenerateAlias([typeof(RestGuild)], nameof(RestGuild.Id), TypeNameOverride = nameof(Guild))] diff --git a/NetCord/Rest/RestMessage.cs b/NetCord/Rest/RestMessage.cs index 62b8506fb..94c915d1e 100644 --- a/NetCord/Rest/RestMessage.cs +++ b/NetCord/Rest/RestMessage.cs @@ -72,7 +72,7 @@ public RestMessage(NetCord.JsonModels.JsonMessage jsonModel, RestClient client) if (startedThread is not null) StartedThread = GuildThread.CreateFromJson(startedThread, client); - Components = jsonModel.Components.SelectOrEmpty(IMessageComponent.CreateFromJson).ToArray(); + Components = jsonModel.Components.SelectOrEmpty(IComponent.CreateFromJson).ToArray(); Stickers = jsonModel.Stickers.SelectOrEmpty(s => new MessageSticker(s, client)).ToArray(); var roleSubscriptionData = jsonModel.RoleSubscriptionData; @@ -238,9 +238,9 @@ public RestMessage(NetCord.JsonModels.JsonMessage jsonModel, RestClient client) public GuildThread? StartedThread { get; } /// - /// A list of objects, contains components like s, s, or other interactive components if any are present. + /// A list of objects, contains components like s, s, or other interactive components if any are present. /// - public IReadOnlyList Components { get; } + public IReadOnlyList Components { get; } /// /// Contains stickers contained in the message, if any. diff --git a/NetCord/Rest/WebhookMessageProperties.cs b/NetCord/Rest/WebhookMessageProperties.cs index 38cc920c1..e83fd9f7c 100644 --- a/NetCord/Rest/WebhookMessageProperties.cs +++ b/NetCord/Rest/WebhookMessageProperties.cs @@ -30,7 +30,7 @@ public partial class WebhookMessageProperties : IHttpSerializable, IMessagePrope [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonPropertyName("components")] - public IEnumerable? Components { get; set; } + public IEnumerable? Components { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonConverter(typeof(JsonConverters.AttachmentPropertiesIEnumerableConverter))] diff --git a/NetCord/Serialization.cs b/NetCord/Serialization.cs index f20662a5a..a03b97493 100644 --- a/NetCord/Serialization.cs +++ b/NetCord/Serialization.cs @@ -101,7 +101,7 @@ namespace NetCord; [JsonSerializable(typeof(LinkButtonProperties))] [JsonSerializable(typeof(PremiumButtonProperties))] [JsonSerializable(typeof(IEnumerable))] -[JsonSerializable(typeof(StringMenuProperties))] +[JsonSerializable(typeof(IStringMenuProperties))] [JsonSerializable(typeof(UserMenuProperties))] [JsonSerializable(typeof(RoleMenuProperties))] [JsonSerializable(typeof(MentionableMenuProperties))] @@ -110,7 +110,7 @@ namespace NetCord; [JsonSerializable(typeof(InteractionCallback))] [JsonSerializable(typeof(InteractionCallback))] [JsonSerializable(typeof(InteractionCallback))] -[JsonSerializable(typeof(InteractionCallback))] +[JsonSerializable(typeof(IInteractionCallback))] [JsonSerializable(typeof(InteractionCallback))] [JsonSerializable(typeof(InteractionMessageProperties))] [JsonSerializable(typeof(MessageOptions))] diff --git a/Tests/NetCord.Test/ApplicationCommands/Commands.cs b/Tests/NetCord.Test/ApplicationCommands/Commands.cs index 8626c6f9a..959b57651 100644 --- a/Tests/NetCord.Test/ApplicationCommands/Commands.cs +++ b/Tests/NetCord.Test/ApplicationCommands/Commands.cs @@ -2,7 +2,7 @@ using NetCord.Services; using NetCord.Services.ApplicationCommands; -namespace NetCord.Test.SlashCommands; +namespace NetCord.Test.ApplicationCommands; [SlashCommand("pn", "PN")] [SlashCommand("permission-nested", "Permission")] diff --git a/Tests/NetCord.Test/ApplicationCommands/DDGAutocomplete.cs b/Tests/NetCord.Test/ApplicationCommands/DDGAutocomplete.cs index 6dbf7a223..d9008c39e 100644 --- a/Tests/NetCord.Test/ApplicationCommands/DDGAutocomplete.cs +++ b/Tests/NetCord.Test/ApplicationCommands/DDGAutocomplete.cs @@ -3,7 +3,7 @@ using NetCord.Rest; using NetCord.Services.ApplicationCommands; -namespace NetCord.Test.SlashCommands; +namespace NetCord.Test.ApplicationCommands; public class DDGAutocomplete(HttpClient client) : IAutocompleteProvider { diff --git a/Tests/NetCord.Test/ApplicationCommands/ModalCommand.cs b/Tests/NetCord.Test/ApplicationCommands/ModalCommand.cs index 0ae268fe2..03a776e7c 100644 --- a/Tests/NetCord.Test/ApplicationCommands/ModalCommand.cs +++ b/Tests/NetCord.Test/ApplicationCommands/ModalCommand.cs @@ -1,7 +1,7 @@ using NetCord.Rest; using NetCord.Services.ApplicationCommands; -namespace NetCord.Test.SlashCommands; +namespace NetCord.Test.ApplicationCommands; public class ModalCommand : ApplicationCommandModule { @@ -10,7 +10,7 @@ public Task WziumAsync(User user) { return RespondAsync(InteractionCallback.Modal(new($"wzium:{user.Id}", "Wzium user", [ - new("reason", TextInputStyle.Paragraph, "Reason") + new TextInputProperties("reason", TextInputStyle.Paragraph, "Reason") { Placeholder = "Because of not wziumming", Required = false, diff --git a/Tests/NetCord.Test/ApplicationCommands/MustContainAttribute.cs b/Tests/NetCord.Test/ApplicationCommands/MustContainAttribute.cs index 7099c9314..ca9d53b78 100644 --- a/Tests/NetCord.Test/ApplicationCommands/MustContainAttribute.cs +++ b/Tests/NetCord.Test/ApplicationCommands/MustContainAttribute.cs @@ -1,7 +1,7 @@ using NetCord.Services; using NetCord.Services.ApplicationCommands; -namespace NetCord.Test.SlashCommands; +namespace NetCord.Test.ApplicationCommands; internal class MustContainAttribute(string value) : ParameterPreconditionAttribute { diff --git a/Tests/NetCord.Test/ApplicationCommands/PercentageTypeReader.cs b/Tests/NetCord.Test/ApplicationCommands/PercentageTypeReader.cs index d1f688935..120ca3a38 100644 --- a/Tests/NetCord.Test/ApplicationCommands/PercentageTypeReader.cs +++ b/Tests/NetCord.Test/ApplicationCommands/PercentageTypeReader.cs @@ -3,7 +3,7 @@ using NetCord.Services; using NetCord.Services.ApplicationCommands; -namespace NetCord.Test.SlashCommands; +namespace NetCord.Test.ApplicationCommands; internal class PercentageTypeReader : SlashCommandTypeReader { diff --git a/Tests/NetCord.Test/ApplicationCommands/PermissionsTypeReader.cs b/Tests/NetCord.Test/ApplicationCommands/PermissionsTypeReader.cs index a052a7916..981304792 100644 --- a/Tests/NetCord.Test/ApplicationCommands/PermissionsTypeReader.cs +++ b/Tests/NetCord.Test/ApplicationCommands/PermissionsTypeReader.cs @@ -4,7 +4,7 @@ using NetCord.Services; using NetCord.Services.ApplicationCommands; -namespace NetCord.Test.SlashCommands; +namespace NetCord.Test.ApplicationCommands; internal class PermissionsTypeReader : SlashCommandTypeReader { diff --git a/Tests/NetCord.Test/ButtonInteractions.cs b/Tests/NetCord.Test/ButtonInteractions.cs index dfc84d5a2..4a6087f01 100644 --- a/Tests/NetCord.Test/ButtonInteractions.cs +++ b/Tests/NetCord.Test/ButtonInteractions.cs @@ -17,7 +17,7 @@ public Task ClickIt() return RespondAsync(InteractionCallback.Modal(new($"wzium:{Context.User.Id}", $"Wzium user {wzium}", [ - new("reason", TextInputStyle.Paragraph, "Reason") + new TextInputProperties("reason", TextInputStyle.Paragraph, "Reason") { Placeholder = "Because of not wziumming", Required = false, diff --git a/Tests/NetCord.Test/ModalInteractions.cs b/Tests/NetCord.Test/ModalInteractions.cs index 29f48a423..18bb9bc72 100644 --- a/Tests/NetCord.Test/ModalInteractions.cs +++ b/Tests/NetCord.Test/ModalInteractions.cs @@ -9,6 +9,6 @@ public class ModalInteractions : ComponentInteractionModule.Default; configuration = configuration with { - TypeReaders = configuration.TypeReaders.Add(typeof(Permissions), new SlashCommands.PermissionsTypeReader()), + TypeReaders = configuration.TypeReaders.Add(typeof(Permissions), new ApplicationCommands.PermissionsTypeReader()), ParameterNameProcessor = new SnakeCaseSlashCommandParameterNameProcessor(), LocalizationsProvider = new JsonLocalizationsProvider(new() { FileNameFormat = "localization.*.*.*.json" }), DefaultIntegrationTypes = [ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall],