diff --git a/docs/guides/int_framework/intro.md b/docs/guides/int_framework/intro.md index d1e0026242..ccec38ac6e 100644 --- a/docs/guides/int_framework/intro.md +++ b/docs/guides/int_framework/intro.md @@ -164,6 +164,8 @@ Interaction service complex parameter constructors are prioritized in the follow 3. Type's only public constuctor. #### DM Permissions +> [!WARNING] +> [EnabledInDmAttribute] is being deprecated in favor of [CommandContextTypes] attribute. You can use the [EnabledInDmAttribute] to configure whether a globally-scoped top level command should be enabled in Dms or not. Only works on top level commands. @@ -419,6 +421,12 @@ Discord Slash Commands support name/description localization. Localization is av } ``` +## User Apps + +User apps are the kind of Discord applications that are installed onto a user instead of a guild, thus making commands usable anywhere on Discord. Note that only users who have installed the application will see the commands. This sample shows you how to create a simple user install command. + +[!code-csharp[Registering Commands Example](samples/intro/userapps.cs)] + [AutocompleteHandlers]: xref:Guides.IntFw.AutoCompletion [DependencyInjection]: xref:Guides.DI.Intro @@ -447,6 +455,8 @@ Discord Slash Commands support name/description localization. Localization is av [ChannelTypesAttribute]: xref:Discord.Interactions.ChannelTypesAttribute [MaxValueAttribute]: xref:Discord.Interactions.MaxValueAttribute [MinValueAttribute]: xref:Discord.Interactions.MinValueAttribute +[EnabledInDmAttribute]: xref:Discord.Interactions.EnabledInDmAttribute +[CommandContextTypes]: xref:Discord.Interactions.CommandContextTypesAttribute [IChannel]: xref:Discord.IChannel [IRole]: xref:Discord.IRole diff --git a/docs/guides/int_framework/samples/intro/userapps.cs b/docs/guides/int_framework/samples/intro/userapps.cs new file mode 100644 index 0000000000..9e258400ad --- /dev/null +++ b/docs/guides/int_framework/samples/intro/userapps.cs @@ -0,0 +1,19 @@ + +// This parameteres can be configured on the module level +// Set supported command context types to Bot DMs and Private Channels (regular DM & GDM) +[CommandContextType(InteractionContextType.BotDm, InteractionContextType.PrivateChannel)] +// Set supported integration installation type to User Install +[IntegrationType(ApplicationIntegrationType.UserInstall)] +public class CommandModule() : InteractionModuleBase +{ + [SlashCommand("test", "Just a test command")] + public async Task TestCommand() + => await RespondAsync("Hello There"); + + // But can also be overridden on the command level + [CommandContextType(InteractionContextType.BotDm, InteractionContextType.PrivateChannel, InteractionContextType.Guild)] + [IntegrationType(ApplicationIntegrationType.GuildInstall)] + [SlashCommand("echo", "Echo the input")] + public async Task EchoCommand(string input) + => await RespondAsync($"You said: {input}"); +} diff --git a/docs/toc.yml b/docs/toc.yml index 3a39c22024..56c64f14d1 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,11 +1,11 @@ - name: Home href: index.md -- name: Documentation - href: api/ - topicUid: API.Docs - name: Guides href: guides/ topicUid: Guides.Introduction +- name: API Reference + href: api/ + topicUid: API.Docs - name: FAQ href: faq/ topicUid: FAQ.Basics.GetStarted diff --git a/src/Discord.Net.Core/Entities/Applications/ApplicationIntegrationType.cs b/src/Discord.Net.Core/Entities/Applications/ApplicationIntegrationType.cs new file mode 100644 index 0000000000..b96370e394 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Applications/ApplicationIntegrationType.cs @@ -0,0 +1,17 @@ +namespace Discord; + +/// +/// Defines where an application can be installed. +/// +public enum ApplicationIntegrationType +{ + /// + /// The application can be installed to a guild. + /// + GuildInstall = 0, + + /// + /// The application can be installed to a user. + /// + UserInstall = 1, +} diff --git a/src/Discord.Net.Core/Entities/Applications/IApplication.cs b/src/Discord.Net.Core/Entities/Applications/IApplication.cs index f936ad32d5..37057178dc 100644 --- a/src/Discord.Net.Core/Entities/Applications/IApplication.cs +++ b/src/Discord.Net.Core/Entities/Applications/IApplication.cs @@ -154,5 +154,10 @@ public interface IApplication : ISnowflakeEntity /// Gets the application's verification state. /// ApplicationVerificationState VerificationState { get; } + + /// + /// Gets application install params configured for integration install types. + /// + IReadOnlyDictionary IntegrationTypesConfig { get; } } } diff --git a/src/Discord.Net.Core/Entities/Applications/ModifyApplicationProperties.cs b/src/Discord.Net.Core/Entities/Applications/ModifyApplicationProperties.cs index 2306752c85..fec519d472 100644 --- a/src/Discord.Net.Core/Entities/Applications/ModifyApplicationProperties.cs +++ b/src/Discord.Net.Core/Entities/Applications/ModifyApplicationProperties.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Discord; /// @@ -54,4 +56,9 @@ public class ModifyApplicationProperties /// public Optional Flags { get; set; } + /// + /// Gets or sets application install params configured for integration install types. + /// + public Optional> IntegrationTypesConfig { get; set; } + } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandOption.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOption.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandOption.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandOptionChoice.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionChoice.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandOptionChoice.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandOptionType.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/ApplicationCommandOptionType.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandOptionType.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandProperties.cs similarity index 90% rename from src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandProperties.cs index 78182c404e..916616973a 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandProperties.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandProperties.cs @@ -93,6 +93,16 @@ public IReadOnlyDictionary DescriptionLocalizations /// public Optional DefaultMemberPermissions { get; set; } + /// + /// Gets or sets the install method for this command. + /// + public Optional> IntegrationTypes { get; set; } + + /// + /// Gets or sets context types this command can be executed in. + /// + public Optional> ContextTypes { get; set; } + internal ApplicationCommandProperties() { } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandTypes.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/ApplicationCommandTypes.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/ApplicationCommandTypes.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommand.cs similarity index 88% rename from src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommand.cs index afab93500c..60a288a3a3 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommand.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommand.cs @@ -40,6 +40,7 @@ public interface IApplicationCommand : ISnowflakeEntity, IDeletable /// /// Only for globally-scoped commands. /// + [Obsolete("This property will be deprecated soon. Use ContextTypes instead.")] bool IsEnabledInDm { get; } /// @@ -83,6 +84,16 @@ public interface IApplicationCommand : ISnowflakeEntity, IDeletable /// string DescriptionLocalized { get; } + /// + /// Gets context types the command can be used in; if not specified. + /// + IReadOnlyCollection ContextTypes { get; } + + /// + /// Gets the install method for the command; if not specified. + /// + IReadOnlyCollection IntegrationTypes { get; } + /// /// Modifies the current application command. /// diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandInteraction.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteraction.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandInteraction.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandInteractionData.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionData.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandInteractionData.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandInteractionDataOption.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/IApplicationCommandInteractionDataOption.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandInteractionDataOption.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandOption.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOption.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandOption.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandOptionChoice.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/IApplicationCommandOptionChoice.cs rename to src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/IApplicationCommandOptionChoice.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/InteractionContextType.cs b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/InteractionContextType.cs new file mode 100644 index 0000000000..ac4e0b2a37 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/ApplicationCommands/InteractionContextType.cs @@ -0,0 +1,22 @@ +namespace Discord; + +/// +/// Represents a context in Discord where an interaction can be used. +/// +public enum InteractionContextType +{ + /// + /// The command can be used in guilds. + /// + Guild = 0, + + /// + /// The command can be used in DM channel with the bot. + /// + BotDm = 1, + + /// + /// The command can be used in private channels. + /// + PrivateChannel = 2 +} diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs b/src/Discord.Net.Core/Entities/Interactions/Autocomplete/AutocompleteOption.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/AutocompleteOption.cs rename to src/Discord.Net.Core/Entities/Interactions/Autocomplete/AutocompleteOption.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs b/src/Discord.Net.Core/Entities/Interactions/Autocomplete/AutocompleteResult.cs similarity index 100% rename from src/Discord.Net.Core/Entities/Interactions/AutocompleteResult.cs rename to src/Discord.Net.Core/Entities/Interactions/Autocomplete/AutocompleteResult.cs diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs index 406328ad7f..2aebee6f26 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/MessageCommandBuilder.cs @@ -44,6 +44,7 @@ public string Name /// /// Gets or sets whether or not this command can be used in DMs. /// + [Obsolete("This property will be deprecated soon. Configure with ContextTypes instead.")] public bool IsDMEnabled { get; set; } = true; /// @@ -56,6 +57,16 @@ public string Name /// public GuildPermission? DefaultMemberPermissions { get; set; } + /// + /// Gets the install method for this command; if not specified. + /// + public HashSet IntegrationTypes { get; set; } = null; + + /// + /// Gets the context types this command can be executed in; if not specified. + /// + public HashSet ContextTypes { get; set; } = null; + private string _name; private Dictionary _nameLocalizations; @@ -71,10 +82,14 @@ public MessageCommandProperties Build() { Name = Name, IsDefaultPermission = IsDefaultPermission, +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = IsDMEnabled, +#pragma warning restore CS0618 // Type or member is obsolete DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified, NameLocalizations = NameLocalizations, IsNsfw = IsNsfw, + IntegrationTypes = IntegrationTypes, + ContextTypes = ContextTypes }; return props; @@ -133,6 +148,7 @@ public MessageCommandBuilder WithNameLocalizations(IDictionary n /// /// if the command is available in dms, otherwise . /// The current builder. + [Obsolete("This method will be deprecated soon. Configure with WithContextTypes instead.")] public MessageCommandBuilder WithDMPermission(bool permission) { IsDMEnabled = permission; @@ -187,5 +203,31 @@ public MessageCommandBuilder WithDefaultMemberPermissions(GuildPermission? permi DefaultMemberPermissions = permissions; return this; } + + /// + /// Sets the install method for this command. + /// + /// Install types for this command. + /// The builder instance. + public MessageCommandBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes) + { + IntegrationTypes = integrationTypes is not null + ? new HashSet(integrationTypes) + : null; + return this; + } + + /// + /// Sets context types this command can be executed in. + /// + /// Context types the command can be executed in. + /// The builder instance. + public MessageCommandBuilder WithContextTypes(params InteractionContextType[] contextTypes) + { + ContextTypes = contextTypes is not null + ? new HashSet(contextTypes) + : null; + return this; + } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs index 2fdfa7bec8..6c4b4bda96 100644 --- a/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/ContextMenus/UserCommandBuilder.cs @@ -44,6 +44,7 @@ public string Name /// /// Gets or sets whether or not this command can be used in DMs. /// + [Obsolete("This property will be deprecated soon. Configure with ContextTypes instead.")] public bool IsDMEnabled { get; set; } = true; /// @@ -56,6 +57,16 @@ public string Name /// public GuildPermission? DefaultMemberPermissions { get; set; } + /// + /// Gets the installation method for this command. if not set. + /// + public HashSet IntegrationTypes { get; set; } + + /// + /// Gets the context types this command can be executed in. if not set. + /// + public HashSet ContextTypes { get; set; } + private string _name; private Dictionary _nameLocalizations; @@ -69,10 +80,14 @@ public UserCommandProperties Build() { Name = Name, IsDefaultPermission = IsDefaultPermission, +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = IsDMEnabled, +#pragma warning restore CS0618 // Type or member is obsolete DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified, NameLocalizations = NameLocalizations, IsNsfw = IsNsfw, + ContextTypes = ContextTypes, + IntegrationTypes = IntegrationTypes }; return props; @@ -131,6 +146,7 @@ public UserCommandBuilder WithNameLocalizations(IDictionary name /// /// if the command is available in dms, otherwise . /// The current builder. + [Obsolete("This method will be deprecated soon. Configure with WithContextTypes instead.")] public UserCommandBuilder WithDMPermission(bool permission) { IsDMEnabled = permission; @@ -185,5 +201,31 @@ public UserCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissi DefaultMemberPermissions = permissions; return this; } + + /// + /// Sets the installation method for this command. + /// + /// Installation types for this command. + /// The builder instance. + public UserCommandBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes) + { + IntegrationTypes = integrationTypes is not null + ? new HashSet(integrationTypes) + : null; + return this; + } + + /// + /// Sets context types this command can be executed in. + /// + /// Context types the command can be executed in. + /// The builder instance. + public UserCommandBuilder WithContextTypes(params InteractionContextType[] contextTypes) + { + ContextTypes = contextTypes is not null + ? new HashSet(contextTypes) + : null; + return this; + } } } diff --git a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs index 9519c30d42..73b7db9ff1 100644 --- a/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs +++ b/src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs @@ -96,6 +96,21 @@ public interface IDiscordInteraction : ISnowflakeEntity /// IReadOnlyCollection Entitlements { get; } + /// + /// Gets which integrations authorized the interaction. + /// + IReadOnlyDictionary IntegrationOwners { get; } + + /// + /// Gets the context this interaction was created in. if context type is unknown. + /// + InteractionContextType? ContextType { get; } + + /// + /// Gets the permissions the app or bot has within the channel the interaction was sent from. + /// + GuildPermissions Permissions { get; } + /// /// Responds to an Interaction with type . /// diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/ApplicationCommandInteractionMetadata.cs b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/ApplicationCommandInteractionMetadata.cs new file mode 100644 index 0000000000..11e5d480a3 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/ApplicationCommandInteractionMetadata.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Discord; + +/// +/// Represents the metadata of an application command interaction. +/// +public readonly struct ApplicationCommandInteractionMetadata : IMessageInteractionMetadata +{ + /// + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + /// + public ulong Id { get; } + + /// + public InteractionType Type { get; } + + /// + public ulong UserId { get; } + + /// + public IReadOnlyDictionary IntegrationOwners { get; } + + /// + public ulong? OriginalResponseMessageId { get; } + + /// + /// Gets the name of the command. + /// + public string Name { get; } + + internal ApplicationCommandInteractionMetadata(ulong id, InteractionType type, ulong userId, IReadOnlyDictionary integrationOwners, + ulong? originalResponseMessageId, string name) + { + Id = id; + Type = type; + UserId = userId; + IntegrationOwners = integrationOwners; + OriginalResponseMessageId = originalResponseMessageId; + Name = name; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/IMessageInteractionData.cs b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/IMessageInteractionData.cs new file mode 100644 index 0000000000..61c3231b47 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/IMessageInteractionData.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Discord; + +/// +/// Represents the metadata of an interaction. +/// +public interface IMessageInteractionMetadata : ISnowflakeEntity +{ + /// + /// Gets the type of the interaction. + /// + InteractionType Type { get; } + + /// + /// Gets the ID of the user who triggered the interaction. + /// + ulong UserId { get; } + + /// + /// Gets the Ids for installation contexts related to the interaction. + /// + IReadOnlyDictionary IntegrationOwners { get; } + + /// + /// Gets the ID of the original response message if the message is a followup. + /// on original response messages. + /// + ulong? OriginalResponseMessageId { get; } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/MessageComponentInteractionMetadata.cs b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/MessageComponentInteractionMetadata.cs new file mode 100644 index 0000000000..f9f4455f29 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/MessageComponentInteractionMetadata.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace Discord; + +/// +/// Represents the metadata of a component interaction. +/// +public readonly struct MessageComponentInteractionMetadata : IMessageInteractionMetadata +{ + /// + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + /// + public ulong Id { get; } + + /// + public InteractionType Type { get; } + + /// + public ulong UserId { get; } + + /// + public IReadOnlyDictionary IntegrationOwners { get; } + + /// + public ulong? OriginalResponseMessageId { get; } + + /// + /// Gets the ID of the message that was interacted with to trigger the interaction. + /// + public ulong InteractedMessageId { get; } + + internal MessageComponentInteractionMetadata(ulong id, InteractionType type, ulong userId, IReadOnlyDictionary integrationOwners, + ulong? originalResponseMessageId, ulong interactedMessageId) + { + Id = id; + Type = type; + UserId = userId; + IntegrationOwners = integrationOwners; + OriginalResponseMessageId = originalResponseMessageId; + InteractedMessageId = interactedMessageId; + } +} + diff --git a/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/ModalInteractionMetadata.cs b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/ModalInteractionMetadata.cs new file mode 100644 index 0000000000..12306e6f62 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Interactions/MessageInteractionData/ModalInteractionMetadata.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Discord; + +/// +/// Represents the metadata of a modal interaction. +/// +public readonly struct ModalSubmitInteractionMetadata :IMessageInteractionMetadata +{ + /// + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + /// + public ulong Id { get; } + + /// + public InteractionType Type { get; } + + /// + public ulong UserId { get; } + + /// + public IReadOnlyDictionary IntegrationOwners { get; } + + /// + public ulong? OriginalResponseMessageId { get; } + + /// + /// Gets the interaction metadata of the interaction that responded with the modal. + /// + public IMessageInteractionMetadata TriggeringInteractionMetadata { get; } + + internal ModalSubmitInteractionMetadata(ulong id, InteractionType type, ulong userId, IReadOnlyDictionary integrationOwners, + ulong? originalResponseMessageId, IMessageInteractionMetadata triggeringInteractionMetadata) + { + Id = id; + Type = type; + UserId = userId; + IntegrationOwners = integrationOwners; + OriginalResponseMessageId = originalResponseMessageId; + TriggeringInteractionMetadata = triggeringInteractionMetadata; + } +} diff --git a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs index e9b7892598..c6fa663c3f 100644 --- a/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs +++ b/src/Discord.Net.Core/Entities/Interactions/SlashCommands/SlashCommandBuilder.cs @@ -75,6 +75,16 @@ public List Options /// public IReadOnlyDictionary DescriptionLocalizations => _descriptionLocalizations; + /// + /// Gets the context types this command can be executed in. if not set. + /// + public HashSet ContextTypes { get; set; } + + /// + /// Gets the installation method for this command. if not set. + /// + public HashSet IntegrationTypes { get; set; } + /// /// Gets or sets whether the command is enabled by default when the app is added to a guild /// @@ -83,6 +93,7 @@ public List Options /// /// Gets or sets whether or not this command can be used in DMs. /// + [Obsolete("This property will be deprecated soon. Configure with ContextTypes instead.")] public bool IsDMEnabled { get; set; } = true; /// @@ -107,6 +118,10 @@ public List Options /// A that can be used to create slash commands. public SlashCommandProperties Build() { + // doing ?? 1 for now until we know default values (if these become non-optional) + Preconditions.AtLeast(ContextTypes?.Count ?? 1, 1, nameof(ContextTypes), "At least 1 context type must be specified"); + Preconditions.AtLeast(IntegrationTypes?.Count ?? 1, 1, nameof(IntegrationTypes), "At least 1 integration type must be specified"); + var props = new SlashCommandProperties { Name = Name, @@ -114,9 +129,13 @@ public SlashCommandProperties Build() IsDefaultPermission = IsDefaultPermission, NameLocalizations = _nameLocalizations, DescriptionLocalizations = _descriptionLocalizations, +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = IsDMEnabled, +#pragma warning restore CS0618 // Type or member is obsolete DefaultMemberPermissions = DefaultMemberPermissions ?? Optional.Unspecified, IsNsfw = IsNsfw, + ContextTypes = ContextTypes ?? Optional>.Unspecified, + IntegrationTypes = IntegrationTypes ?? Optional>.Unspecified }; if (Options != null && Options.Any()) @@ -171,6 +190,7 @@ public SlashCommandBuilder WithDefaultPermission(bool value) /// /// if the command is available in dms, otherwise . /// The current builder. + [Obsolete("This method will be deprecated soon. Configure using WithContextTypes instead.")] public SlashCommandBuilder WithDMPermission(bool permission) { IsDMEnabled = permission; @@ -199,6 +219,32 @@ public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission? permiss return this; } + /// + /// Sets the installation method for this command. + /// + /// Installation types for this command. + /// The builder instance. + public SlashCommandBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes) + { + IntegrationTypes = integrationTypes is not null + ? new HashSet(integrationTypes) + : null; + return this; + } + + /// + /// Sets context types this command can be executed in. + /// + /// Context types the command can be executed in. + /// The builder instance. + public SlashCommandBuilder WithContextTypes(params InteractionContextType[] contextTypes) + { + ContextTypes = contextTypes is not null + ? new HashSet(contextTypes) + : null; + return this; + } + /// /// Adds an option to the current slash command. /// diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 034c0e276f..7043fecdd8 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -215,6 +215,7 @@ public interface IMessage : ISnowflakeEntity, IDeletable /// /// A if the message is a response to an interaction; otherwise . /// + [Obsolete("This property will be deprecated soon. Use IUserMessage.InteractionMetadata instead.")] IMessageInteraction Interaction { get; } /// diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 727708997a..757c3e4a03 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -21,6 +21,14 @@ public interface IUserMessage : IMessage /// IUserMessage ReferencedMessage { get; } + /// + /// Gets the interaction metadata for the interaction this message is a response to. + /// + /// + /// Will be if the message is not a response to an interaction. + /// + IMessageInteractionMetadata InteractionMetadata { get; } + /// /// Modifies this message. /// diff --git a/src/Discord.Net.Interactions/Attributes/CommandContextTypeAttribute.cs b/src/Discord.Net.Interactions/Attributes/CommandContextTypeAttribute.cs new file mode 100644 index 0000000000..1d4c687b4a --- /dev/null +++ b/src/Discord.Net.Interactions/Attributes/CommandContextTypeAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Discord.Interactions; + +/// +/// Specifies context types this command can be executed in. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class CommandContextTypeAttribute : Attribute +{ + /// + /// Gets context types this command can be executed in. + /// + public IReadOnlyCollection ContextTypes { get; } + + /// + /// Sets the property of an application command or module. + /// + /// Context types set for the command. + public CommandContextTypeAttribute(params InteractionContextType[] contextTypes) + { + ContextTypes = contextTypes?.Distinct().ToImmutableArray() + ?? throw new ArgumentNullException(nameof(contextTypes)); + + if (ContextTypes.Count == 0) + throw new ArgumentException("A command must have at least one supported context type.", nameof(contextTypes)); + } +} diff --git a/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs b/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs index a97f85a25a..dedab9ee9f 100644 --- a/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs +++ b/src/Discord.Net.Interactions/Attributes/EnabledInDmAttribute.cs @@ -6,6 +6,7 @@ namespace Discord.Interactions /// Sets the property of an application command or module. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + [Obsolete("This attribute will be deprecated soon. Configure with CommandContextTypes attribute instead.")] public class EnabledInDmAttribute : Attribute { /// diff --git a/src/Discord.Net.Interactions/Attributes/IntegrationTypeAttribute.cs b/src/Discord.Net.Interactions/Attributes/IntegrationTypeAttribute.cs new file mode 100644 index 0000000000..2fa0d9cb4c --- /dev/null +++ b/src/Discord.Net.Interactions/Attributes/IntegrationTypeAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Discord.Interactions; + +/// +/// Specifies install method for the command. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class IntegrationTypeAttribute : Attribute +{ + /// + /// Gets integration install types for this command. + /// + public IReadOnlyCollection IntegrationTypes { get; } + + /// + /// Sets the property of an application command or module. + /// + /// Integration install types set for the command. + public IntegrationTypeAttribute(params ApplicationIntegrationType[] integrationTypes) + { + IntegrationTypes = integrationTypes?.Distinct().ToImmutableArray() + ?? throw new ArgumentNullException(nameof(integrationTypes)); + + if (integrationTypes.Length == 0) + throw new ArgumentException("A command must have at least one integration type.", nameof(integrationTypes)); + } +} diff --git a/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs index ee08d879fc..44891b6483 100644 --- a/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Commands/ContextCommandBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Discord.Interactions.Builders { @@ -35,7 +36,24 @@ public sealed class ContextCommandBuilder : CommandBuilder public GuildPermission? DefaultMemberPermissions { get; set; } = null; - internal ContextCommandBuilder(ModuleBuilder module) : base(module) { } + /// + /// Gets the install method for this command. + /// + public HashSet IntegrationTypes { get; set; } = null; + + /// + /// Gets the context types this command can be executed in. + /// + public HashSet ContextTypes { get; set; } = null; + + internal ContextCommandBuilder(ModuleBuilder module) : base(module) + { + IntegrationTypes = module.IntegrationTypes; + ContextTypes = module.ContextTypes; +#pragma warning disable CS0618 // Type or member is obsolete + IsEnabledInDm = module.IsEnabledInDm; +#pragma warning restore CS0618 // Type or member is obsolete + } /// /// Initializes a new . @@ -126,6 +144,28 @@ public ContextCommandBuilder WithDefaultMemberPermissions(GuildPermission permis return this; } + /// + /// Sets the of this . + /// + /// Install types for this command. + /// The builder instance. + public ContextCommandBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes) + { + IntegrationTypes = new HashSet(integrationTypes); + return this; + } + + /// + /// Sets the of this . + /// + /// Context types the command can be executed in. + /// The builder instance. + public ContextCommandBuilder WithContextTypes(params InteractionContextType[] contextTypes) + { + ContextTypes = new HashSet(contextTypes); + return this; + } + internal override ContextCommandInfo Build(ModuleInfo module, InteractionService commandService) => ContextCommandInfo.Create(this, module, commandService); } diff --git a/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs index fa1c1de286..8771138722 100644 --- a/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/Commands/SlashCommandBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Discord.Interactions.Builders { @@ -35,7 +36,24 @@ public sealed class SlashCommandBuilder : CommandBuilder public GuildPermission? DefaultMemberPermissions { get; set; } = null; - internal SlashCommandBuilder(ModuleBuilder module) : base(module) { } + /// + /// Gets or sets the install method for this command. + /// + public HashSet IntegrationTypes { get; set; } = null; + + /// + /// Gets or sets the context types this command can be executed in. + /// + public HashSet ContextTypes { get; set; } = null; + + internal SlashCommandBuilder(ModuleBuilder module) : base(module) + { + IntegrationTypes = module.IntegrationTypes; + ContextTypes = module.ContextTypes; +#pragma warning disable CS0618 // Type or member is obsolete + IsEnabledInDm = module.IsEnabledInDm; +#pragma warning restore CS0618 // Type or member is obsolete + } /// /// Initializes a new . @@ -126,6 +144,32 @@ public SlashCommandBuilder WithDefaultMemberPermissions(GuildPermission permissi return this; } + /// + /// Sets the on this . + /// + /// Install types for this command. + /// The builder instance. + public SlashCommandBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes) + { + IntegrationTypes = integrationTypes is not null + ? new HashSet(integrationTypes) + : null; + return this; + } + + /// + /// Sets the on this . + /// + /// Context types the command can be executed in. + /// The builder instance. + public SlashCommandBuilder WithContextTypes(params InteractionContextType[] contextTypes) + { + ContextTypes = contextTypes is not null + ? new HashSet(contextTypes) + : null; + return this; + } + internal override SlashCommandInfo Build(ModuleInfo module, InteractionService commandService) => new SlashCommandInfo(this, module, commandService); } diff --git a/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs index 26402762e6..2fd28927d8 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleBuilder.cs @@ -51,12 +51,13 @@ public class ModuleBuilder /// /// Gets and sets the default permission of this module. /// - [Obsolete($"To be deprecated soon, use {nameof(IsEnabledInDm)} and {nameof(DefaultMemberPermissions)} instead.")] + [Obsolete($"To be deprecated soon, use {nameof(ContextTypes)} and {nameof(DefaultMemberPermissions)} instead.")] public bool DefaultPermission { get; set; } = true; /// /// Gets whether this command can be used in DMs. /// + [Obsolete("This property will be deprecated soon. Use ContextTypes instead.")] public bool IsEnabledInDm { get; set; } = true; /// @@ -114,6 +115,16 @@ public class ModuleBuilder /// public IReadOnlyList ModalCommands => _modalCommands; + /// + /// Gets or sets the install method for this command. + /// + public HashSet IntegrationTypes { get; set; } = null; + + /// + /// Gets or sets the context types this command can be executed in. + /// + public HashSet ContextTypes { get; set; } = null; + internal TypeInfo TypeInfo { get; set; } internal ModuleBuilder(InteractionService interactionService, ModuleBuilder parent = null) @@ -189,6 +200,7 @@ public ModuleBuilder WithDefaultPermission(bool permission) /// /// The builder instance. /// + [Obsolete("This method will be deprecated soon. Use WithContextTypes instead.")] public ModuleBuilder SetEnabledInDm(bool isEnabled) { IsEnabledInDm = isEnabled; @@ -423,6 +435,28 @@ public ModuleBuilder AddModule(Action configure) return this; } + /// + /// Sets the on this . + /// + /// Install types for this command. + /// The builder instance. + public ModuleBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes) + { + IntegrationTypes = new HashSet(integrationTypes); + return this; + } + + /// + /// Sets the on this . + /// + /// Context types the command can be executed in. + /// The builder instance. + public ModuleBuilder WithContextTypes(params InteractionContextType[] contextTypes) + { + ContextTypes = new HashSet(contextTypes); + return this; + } + internal ModuleInfo Build(InteractionService interactionService, IServiceProvider services, ModuleInfo parent = null) { if (TypeInfo is not null && ModuleClassBuilder.IsValidModuleDefinition(TypeInfo)) diff --git a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs index 4043e363de..7a06c050ed 100644 --- a/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Interactions/Builders/ModuleClassBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; @@ -87,11 +88,13 @@ private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, Intera } break; #pragma warning restore CS0618 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete case EnabledInDmAttribute enabledInDm: - { + { builder.IsEnabledInDm = enabledInDm.IsEnabled; } break; +#pragma warning restore CS0618 // Type or member is obsolete case DefaultMemberPermissionsAttribute memberPermission: { builder.DefaultMemberPermissions = memberPermission.Permissions; @@ -106,6 +109,12 @@ private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, Intera case NsfwCommandAttribute nsfwCommand: builder.SetNsfw(nsfwCommand.IsNsfw); break; + case CommandContextTypeAttribute contextType: + builder.WithContextTypes(contextType.ContextTypes?.ToArray()); + break; + case IntegrationTypeAttribute integrationType: + builder.WithIntegrationTypes(integrationType.IntegrationTypes?.ToArray()); + break; default: builder.AddAttributes(attribute); break; @@ -185,12 +194,12 @@ private static void BuildSlashCommand(SlashCommandBuilder builder, Func, IA /// public GuildPermission? DefaultMemberPermissions { get; } + /// + public IReadOnlyCollection ContextTypes { get; } + + /// + public IReadOnlyCollection IntegrationTypes { get; } + /// public override IReadOnlyList Parameters { get; } @@ -46,6 +52,8 @@ internal ContextCommandInfo(Builders.ContextCommandBuilder builder, ModuleInfo m IsEnabledInDm = builder.IsEnabledInDm; DefaultMemberPermissions = builder.DefaultMemberPermissions; Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); + ContextTypes = builder.ContextTypes?.ToImmutableArray(); + IntegrationTypes = builder.IntegrationTypes?.ToImmutableArray(); } internal static ContextCommandInfo Create(Builders.ContextCommandBuilder builder, ModuleInfo module, InteractionService commandService) diff --git a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs index ad04649e4b..63935d9280 100644 --- a/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/Commands/SlashCommandInfo.cs @@ -46,6 +46,12 @@ public class SlashCommandInfo : CommandInfo, IApplica /// public IReadOnlyList FlattenedParameters { get; } + /// + public IReadOnlyCollection ContextTypes { get; } + + /// + public IReadOnlyCollection IntegrationTypes { get; } + internal SlashCommandInfo(Builders.SlashCommandBuilder builder, ModuleInfo module, InteractionService commandService) : base(builder, module, commandService) { Description = builder.Description; @@ -57,6 +63,8 @@ internal SlashCommandInfo(Builders.SlashCommandBuilder builder, ModuleInfo modul DefaultMemberPermissions = builder.DefaultMemberPermissions; Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); FlattenedParameters = FlattenParameters(Parameters).ToImmutableArray(); + ContextTypes = builder.ContextTypes?.ToImmutableArray(); + IntegrationTypes = builder.IntegrationTypes?.ToImmutableArray(); for (var i = 0; i < FlattenedParameters.Count - 1; i++) if (!FlattenedParameters.ElementAt(i).IsRequired && FlattenedParameters.ElementAt(i + 1).IsRequired) diff --git a/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs b/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs index d5bcdd3a33..972c43dc84 100644 --- a/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs +++ b/src/Discord.Net.Interactions/Info/IApplicationCommandInfo.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Discord.Interactions { @@ -37,5 +38,15 @@ public interface IApplicationCommandInfo /// Gets the default permissions needed for executing this command. /// public GuildPermission? DefaultMemberPermissions { get; } + + /// + /// Gets the context types this command can be executed in. + /// + public IReadOnlyCollection ContextTypes { get; } + + /// + /// Gets the install methods for this command. + /// + public IReadOnlyCollection IntegrationTypes { get; } } } diff --git a/src/Discord.Net.Interactions/Info/ModuleInfo.cs b/src/Discord.Net.Interactions/Info/ModuleInfo.cs index f42cdaefd1..bec1c02afa 100644 --- a/src/Discord.Net.Interactions/Info/ModuleInfo.cs +++ b/src/Discord.Net.Interactions/Info/ModuleInfo.cs @@ -117,6 +117,16 @@ public class ModuleInfo /// public bool DontAutoRegister { get; } + /// + /// Gets the context types commands in this module can be executed in. + /// + public IReadOnlyCollection ContextTypes { get; } + + /// + /// Gets the install method for commands in this module. + /// + public IReadOnlyCollection IntegrationTypes { get; } + internal ModuleInfo(ModuleBuilder builder, InteractionService commandService, IServiceProvider services, ModuleInfo parent = null) { CommandService = commandService; @@ -129,7 +139,9 @@ internal ModuleInfo(ModuleBuilder builder, InteractionService commandService, IS DefaultPermission = builder.DefaultPermission; #pragma warning restore CS0618 // Type or member is obsolete IsNsfw = builder.IsNsfw; +#pragma warning disable CS0618 // Type or member is obsolete IsEnabledInDm = builder.IsEnabledInDm; +#pragma warning restore CS0618 // Type or member is obsolete DefaultMemberPermissions = BuildDefaultMemberPermissions(builder); SlashCommands = BuildSlashCommands(builder).ToImmutableArray(); ContextCommands = BuildContextCommands(builder).ToImmutableArray(); @@ -141,6 +153,8 @@ internal ModuleInfo(ModuleBuilder builder, InteractionService commandService, IS Preconditions = BuildPreconditions(builder).ToImmutableArray(); IsTopLevelGroup = IsSlashGroup && CheckTopLevel(parent); DontAutoRegister = builder.DontAutoRegister; + ContextTypes = builder.ContextTypes?.ToImmutableArray(); + IntegrationTypes = builder.IntegrationTypes?.ToImmutableArray(); GroupedPreconditions = Preconditions.ToLookup(x => x.Group, x => x, StringComparer.Ordinal); } diff --git a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs index 32784fadd9..8cee68fd44 100644 --- a/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs +++ b/src/Discord.Net.Interactions/Utilities/ApplicationCommandRestUtil.cs @@ -53,9 +53,17 @@ public static SlashCommandProperties ToApplicationCommandProps(this SlashCommand Name = commandInfo.Name, Description = commandInfo.Description, IsDefaultPermission = commandInfo.DefaultPermission, +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = commandInfo.IsEnabledInDm, +#pragma warning restore CS0618 // Type or member is obsolete IsNsfw = commandInfo.IsNsfw, DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(), + IntegrationTypes = commandInfo.IntegrationTypes is not null + ? new HashSet(commandInfo.IntegrationTypes) + : null, + ContextTypes = commandInfo.ContextTypes is not null + ? new HashSet(commandInfo.ContextTypes) + : null, }.WithNameLocalizations(localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary.Empty) .WithDescriptionLocalizations(localizationManager?.GetAllDescriptions(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary.Empty) .Build(); @@ -82,7 +90,7 @@ public static ApplicationCommandOptionProperties ToApplicationCommandOptionProps Options = commandInfo.FlattenedParameters?.Select(x => x.ToApplicationCommandOptionProps()) ?.ToList(), NameLocalizations = localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary.Empty, - DescriptionLocalizations = localizationManager?.GetAllDescriptions(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary.Empty + DescriptionLocalizations = localizationManager?.GetAllDescriptions(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary.Empty, }; } @@ -98,8 +106,16 @@ public static ApplicationCommandProperties ToApplicationCommandProps(this Contex Name = commandInfo.Name, IsDefaultPermission = commandInfo.DefaultPermission, DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(), +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = commandInfo.IsEnabledInDm, +#pragma warning restore CS0618 // Type or member is obsolete IsNsfw = commandInfo.IsNsfw, + IntegrationTypes = commandInfo.IntegrationTypes is not null + ? new HashSet(commandInfo.IntegrationTypes) + : null, + ContextTypes = commandInfo.ContextTypes is not null + ? new HashSet(commandInfo.ContextTypes) + : null, } .WithNameLocalizations(localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary.Empty) .Build(), @@ -109,7 +125,15 @@ public static ApplicationCommandProperties ToApplicationCommandProps(this Contex IsDefaultPermission = commandInfo.DefaultPermission, DefaultMemberPermissions = ((commandInfo.DefaultMemberPermissions ?? 0) | (commandInfo.Module.DefaultMemberPermissions ?? 0)).SanitizeGuildPermissions(), IsNsfw = commandInfo.IsNsfw, - IsDMEnabled = commandInfo.IsEnabledInDm +#pragma warning disable CS0618 // Type or member is obsolete + IsDMEnabled = commandInfo.IsEnabledInDm, +#pragma warning restore CS0618 // Type or member is obsolete + IntegrationTypes = commandInfo.IntegrationTypes is not null + ? new HashSet(commandInfo.IntegrationTypes) + : null, + ContextTypes = commandInfo.ContextTypes is not null + ? new HashSet(commandInfo.ContextTypes) + : null, } .WithNameLocalizations(localizationManager?.GetAllNames(commandPath, LocalizationTarget.Command) ?? ImmutableDictionary.Empty) .Build(), @@ -165,10 +189,16 @@ private static void ParseModuleModel(this ModuleInfo moduleInfo, List(moduleInfo.IntegrationTypes) + : null, + ContextTypes = moduleInfo.ContextTypes is not null + ? new HashSet(moduleInfo.ContextTypes) + : null, } .WithNameLocalizations(localizationManager?.GetAllNames(modulePath, LocalizationTarget.Group) ?? ImmutableDictionary.Empty) .WithDescriptionLocalizations(localizationManager?.GetAllDescriptions(modulePath, LocalizationTarget.Group) ?? ImmutableDictionary.Empty) @@ -230,11 +260,19 @@ public static ApplicationCommandProperties ToApplicationCommandProps(this IAppli Description = command.Description, IsDefaultPermission = command.IsDefaultPermission, DefaultMemberPermissions = command.DefaultMemberPermissions.RawValue == 0 ? new Optional() : (GuildPermission)command.DefaultMemberPermissions.RawValue, +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = command.IsEnabledInDm, +#pragma warning restore CS0618 // Type or member is obsolete IsNsfw = command.IsNsfw, Options = command.Options?.Select(x => x.ToApplicationCommandOptionProps())?.ToList() ?? Optional>.Unspecified, NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, + ContextTypes = command.ContextTypes is not null + ? new HashSet(command.ContextTypes) + : Optional>.Unspecified, + IntegrationTypes = command.IntegrationTypes is not null + ? new HashSet(command.IntegrationTypes) + : Optional>.Unspecified, }, ApplicationCommandType.User => new UserCommandProperties { @@ -242,9 +280,17 @@ public static ApplicationCommandProperties ToApplicationCommandProps(this IAppli IsDefaultPermission = command.IsDefaultPermission, DefaultMemberPermissions = command.DefaultMemberPermissions.RawValue == 0 ? new Optional() : (GuildPermission)command.DefaultMemberPermissions.RawValue, IsNsfw = command.IsNsfw, +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = command.IsEnabledInDm, +#pragma warning restore CS0618 // Type or member is obsolete NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, - DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty + DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, + ContextTypes = command.ContextTypes is not null + ? new HashSet(command.ContextTypes) + : Optional>.Unspecified, + IntegrationTypes = command.IntegrationTypes is not null + ? new HashSet(command.IntegrationTypes) + : Optional>.Unspecified, }, ApplicationCommandType.Message => new MessageCommandProperties { @@ -252,9 +298,17 @@ public static ApplicationCommandProperties ToApplicationCommandProps(this IAppli IsDefaultPermission = command.IsDefaultPermission, DefaultMemberPermissions = command.DefaultMemberPermissions.RawValue == 0 ? new Optional() : (GuildPermission)command.DefaultMemberPermissions.RawValue, IsNsfw = command.IsNsfw, +#pragma warning disable CS0618 // Type or member is obsolete IsDMEnabled = command.IsEnabledInDm, +#pragma warning restore CS0618 // Type or member is obsolete NameLocalizations = command.NameLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, - DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty + DescriptionLocalizations = command.DescriptionLocalizations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty, + ContextTypes = command.ContextTypes is not null + ? new HashSet(command.ContextTypes) + : Optional>.Unspecified, + IntegrationTypes = command.IntegrationTypes is not null + ? new HashSet(command.IntegrationTypes) + : Optional>.Unspecified, }, _ => throw new InvalidOperationException($"Cannot create command properties for command type {command.Type}"), }; diff --git a/src/Discord.Net.Rest/API/Common/Application.cs b/src/Discord.Net.Rest/API/Common/Application.cs index d446b4440f..70c762565c 100644 --- a/src/Discord.Net.Rest/API/Common/Application.cs +++ b/src/Discord.Net.Rest/API/Common/Application.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using System.Collections.Generic; namespace Discord.API; @@ -94,4 +95,10 @@ internal class Application [JsonProperty("verification_state")] public Optional VerificationState { get; set; } + + [JsonProperty("integration_types")] + public Optional IntegrationTypes { get; set; } + + [JsonProperty("integration_types_config")] + public Optional> IntegrationTypesConfig { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs index 1b27e6e7cd..24a1e360bb 100644 --- a/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs +++ b/src/Discord.Net.Rest/API/Common/ApplicationCommand.cs @@ -50,5 +50,11 @@ internal class ApplicationCommand [JsonProperty("nsfw")] public Optional Nsfw { get; set; } + + [JsonProperty("contexts")] + public Optional ContextTypes { get; set; } + + [JsonProperty("integration_types")] + public Optional IntegrationTypes { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InstallParams.cs b/src/Discord.Net.Rest/API/Common/InstallParams.cs index d610dd9200..f4ca60e1c7 100644 --- a/src/Discord.Net.Rest/API/Common/InstallParams.cs +++ b/src/Discord.Net.Rest/API/Common/InstallParams.cs @@ -8,5 +8,5 @@ internal class InstallParams public string[] Scopes { get; set; } [JsonProperty("permissions")] - public ulong Permission { get; set; } + public GuildPermission Permission { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/Interaction.cs b/src/Discord.Net.Rest/API/Common/Interaction.cs index 14148ee787..8ca83991f5 100644 --- a/src/Discord.Net.Rest/API/Common/Interaction.cs +++ b/src/Discord.Net.Rest/API/Common/Interaction.cs @@ -1,53 +1,63 @@ using Newtonsoft.Json; -namespace Discord.API +using System.Collections.Generic; + +namespace Discord.API; + +[JsonConverter(typeof(Net.Converters.InteractionConverter))] +internal class Interaction { - [JsonConverter(typeof(Net.Converters.InteractionConverter))] - internal class Interaction - { - [JsonProperty("id")] - public ulong Id { get; set; } + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("application_id")] + public ulong ApplicationId { get; set; } + + [JsonProperty("type")] + public InteractionType Type { get; set; } + + [JsonProperty("data")] + public Optional Data { get; set; } - [JsonProperty("application_id")] - public ulong ApplicationId { get; set; } + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } - [JsonProperty("type")] - public InteractionType Type { get; set; } + [JsonProperty("channel")] + public Optional Channel { get; set; } - [JsonProperty("data")] - public Optional Data { get; set; } + [JsonProperty("channel_id")] + public Optional ChannelId { get; set; } - [JsonProperty("guild_id")] - public Optional GuildId { get; set; } + [JsonProperty("member")] + public Optional Member { get; set; } - [JsonProperty("channel_id")] - public Optional ChannelId { get; set; } + [JsonProperty("user")] + public Optional User { get; set; } - [JsonProperty("channel")] - public Optional Channel { get; set; } + [JsonProperty("token")] + public string Token { get; set; } - [JsonProperty("member")] - public Optional Member { get; set; } + [JsonProperty("version")] + public int Version { get; set; } - [JsonProperty("user")] - public Optional User { get; set; } + [JsonProperty("message")] + public Optional Message { get; set; } - [JsonProperty("token")] - public string Token { get; set; } + [JsonProperty("locale")] + public Optional UserLocale { get; set; } - [JsonProperty("version")] - public int Version { get; set; } + [JsonProperty("guild_locale")] + public Optional GuildLocale { get; set; } - [JsonProperty("message")] - public Optional Message { get; set; } + [JsonProperty("entitlements")] + public Entitlement[] Entitlements { get; set; } - [JsonProperty("locale")] - public Optional UserLocale { get; set; } + [JsonProperty("authorizing_integration_owners")] + public Dictionary IntegrationOwners { get; set; } - [JsonProperty("guild_locale")] - public Optional GuildLocale { get; set; } + [JsonProperty("context")] + public Optional ContextType { get; set; } - [JsonProperty("entitlements")] - public Entitlement[] Entitlements { get; set; } - } + [JsonProperty("app_permissions")] + public GuildPermission ApplicationPermissions { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index 8cde11c85a..c9cc61a203 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -1,74 +1,104 @@ using Newtonsoft.Json; using System; -namespace Discord.API +namespace Discord.API; + +internal class Message { - internal class Message - { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("type")] - public MessageType Type { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } - // ALWAYS sent on WebSocket messages - [JsonProperty("guild_id")] - public Optional GuildId { get; set; } - [JsonProperty("webhook_id")] - public Optional WebhookId { get; set; } - [JsonProperty("author")] - public Optional Author { get; set; } - // ALWAYS sent on WebSocket messages - [JsonProperty("member")] - public Optional Member { get; set; } - [JsonProperty("content")] - public Optional Content { get; set; } - [JsonProperty("timestamp")] - public Optional Timestamp { get; set; } - [JsonProperty("edited_timestamp")] - public Optional EditedTimestamp { get; set; } - [JsonProperty("tts")] - public Optional IsTextToSpeech { get; set; } - [JsonProperty("mention_everyone")] - public Optional MentionEveryone { get; set; } - [JsonProperty("mentions")] - public Optional UserMentions { get; set; } - [JsonProperty("mention_roles")] - public Optional RoleMentions { get; set; } - [JsonProperty("attachments")] - public Optional Attachments { get; set; } - [JsonProperty("embeds")] - public Optional Embeds { get; set; } - [JsonProperty("pinned")] - public Optional Pinned { get; set; } - [JsonProperty("reactions")] - public Optional Reactions { get; set; } - // sent with Rich Presence-related chat embeds - [JsonProperty("activity")] - public Optional Activity { get; set; } - // sent with Rich Presence-related chat embeds - [JsonProperty("application")] - public Optional Application { get; set; } - [JsonProperty("message_reference")] - public Optional Reference { get; set; } - [JsonProperty("flags")] - public Optional Flags { get; set; } - [JsonProperty("allowed_mentions")] - public Optional AllowedMentions { get; set; } - [JsonProperty("referenced_message")] - public Optional ReferencedMessage { get; set; } - [JsonProperty("components")] - public Optional Components { get; set; } - public Optional Interaction { get; set; } - [JsonProperty("sticker_items")] - public Optional StickerItems { get; set; } - [JsonProperty("role_subscription_data")] - public Optional RoleSubscriptionData { get; set; } - - [JsonProperty("thread")] - public Optional Thread { get; set; } - - [JsonProperty("resolved")] - public Optional Resolved { get; set; } - } + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("type")] + public MessageType Type { get; set; } + + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + // ALWAYS sent on WebSocket messages + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + + [JsonProperty("webhook_id")] + public Optional WebhookId { get; set; } + + [JsonProperty("author")] + public Optional Author { get; set; } + + // ALWAYS sent on WebSocket messages + [JsonProperty("member")] + public Optional Member { get; set; } + + [JsonProperty("content")] + public Optional Content { get; set; } + + [JsonProperty("timestamp")] + public Optional Timestamp { get; set; } + + [JsonProperty("edited_timestamp")] + public Optional EditedTimestamp { get; set; } + + [JsonProperty("tts")] + public Optional IsTextToSpeech { get; set; } + + [JsonProperty("mention_everyone")] + public Optional MentionEveryone { get; set; } + + [JsonProperty("mentions")] + public Optional UserMentions { get; set; } + + [JsonProperty("mention_roles")] + public Optional RoleMentions { get; set; } + + [JsonProperty("attachments")] + public Optional Attachments { get; set; } + + [JsonProperty("embeds")] + public Optional Embeds { get; set; } + + [JsonProperty("pinned")] + public Optional Pinned { get; set; } + + [JsonProperty("reactions")] + public Optional Reactions { get; set; } + + // sent with Rich Presence-related chat embeds + [JsonProperty("activity")] + public Optional Activity { get; set; } + + // sent with Rich Presence-related chat embeds + [JsonProperty("application")] + public Optional Application { get; set; } + + [JsonProperty("message_reference")] + public Optional Reference { get; set; } + + [JsonProperty("flags")] + public Optional Flags { get; set; } + + [JsonProperty("allowed_mentions")] + public Optional AllowedMentions { get; set; } + + [JsonProperty("referenced_message")] + public Optional ReferencedMessage { get; set; } + + [JsonProperty("components")] + public Optional Components { get; set; } + + [JsonProperty("interaction")] + public Optional Interaction { get; set; } + + [JsonProperty("sticker_items")] + public Optional StickerItems { get; set; } + + [JsonProperty("role_subscription_data")] + public Optional RoleSubscriptionData { get; set; } + + [JsonProperty("thread")] + public Optional Thread { get; set; } + + [JsonProperty("resolved")] + public Optional Resolved { get; set; } + + [JsonProperty("interaction_metadata")] + public Optional InteractionMetadata { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/MessageInteractionMetadata.cs b/src/Discord.Net.Rest/API/Common/MessageInteractionMetadata.cs new file mode 100644 index 0000000000..f5f88665a0 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MessageInteractionMetadata.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Discord.API; + +internal class MessageInteractionMetadata +{ + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("type")] + public InteractionType Type { get; set; } + + [JsonProperty("user_id")] + public ulong UserId { get; set; } + + [JsonProperty("authorizing_integration_owners")] + public Dictionary IntegrationOwners { get; set; } + + [JsonProperty("original_response_message_id")] + public Optional OriginalResponseMessageId { get; set; } + + [JsonProperty("name")] + public Optional Name { get; set; } + + [JsonProperty("interacted_message_id")] + public Optional InteractedMessageId { get; set; } + + [JsonProperty("triggering_interaction_metadata")] + public Optional TriggeringInteractionMetadata { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs index 36ea0270a9..4964db25fb 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateApplicationCommandParams.cs @@ -38,9 +38,17 @@ internal class CreateApplicationCommandParams [JsonProperty("nsfw")] public Optional Nsfw { get; set; } + [JsonProperty("contexts")] + public Optional> ContextTypes { get; set; } + + [JsonProperty("integration_types")] + public Optional> IntegrationTypes { get; set; } + public CreateApplicationCommandParams() { } + public CreateApplicationCommandParams(string name, string description, ApplicationCommandType type, ApplicationCommandOption[] options = null, - IDictionary nameLocalizations = null, IDictionary descriptionLocalizations = null, bool nsfw = false) + IDictionary nameLocalizations = null, IDictionary descriptionLocalizations = null, bool nsfw = false, + HashSet contextTypes = null, HashSet integrationTypes = null) { Name = name; Description = description; @@ -49,6 +57,8 @@ public CreateApplicationCommandParams(string name, string description, Applicati NameLocalizations = nameLocalizations?.ToDictionary(x => x.Key, x => x.Value) ?? Optional>.Unspecified; DescriptionLocalizations = descriptionLocalizations?.ToDictionary(x => x.Key, x => x.Value) ?? Optional>.Unspecified; Nsfw = nsfw; + ContextTypes = contextTypes ?? Optional.Create>(); + IntegrationTypes = integrationTypes ?? Optional.Create>(); } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs index 222e16b843..ddc30e6b82 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyApplicationCommandParams.cs @@ -28,5 +28,11 @@ internal class ModifyApplicationCommandParams [JsonProperty("description_localizations")] public Optional> DescriptionLocalizations { get; set; } + + [JsonProperty("contexts")] + public Optional> ContextTypes { get; set; } + + [JsonProperty("integration_types")] + public Optional> IntegrationTypes { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentApplicationBotParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentApplicationBotParams.cs index 3284827fc0..dfa95db6cb 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentApplicationBotParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentApplicationBotParams.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using System.Collections.Generic; namespace Discord.API.Rest; @@ -30,4 +31,7 @@ internal class ModifyCurrentApplicationBotParams [JsonProperty("tags")] public Optional Tags { get; set; } -} + + [JsonProperty("integration_types_config")] + public Optional> IntegrationTypesConfig { get; set; } +} diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 7d0c7b48ef..3d7d3c97b7 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -55,10 +55,17 @@ public static async Task GetCurrentBotApplicationAsync(BaseDisc ? null : new InstallParams { - Permission = (ulong)args.InstallParams.Value.Permission, + Permission = args.InstallParams.Value.Permission, Scopes = args.InstallParams.Value.Scopes.ToArray() } : Optional.Unspecified, + IntegrationTypesConfig = args.IntegrationTypesConfig.IsSpecified + ? args.IntegrationTypesConfig.Value?.ToDictionary(x => x.Key, x => new InstallParams + { + Permission = x.Value.Permission, + Scopes = x.Value.Scopes.ToArray() + }) + : Optional>.Unspecified }, options); } diff --git a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs index 0a2278e02a..f1d47cc041 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/InteractionHelper.cs @@ -106,6 +106,8 @@ public static Task CreateGlobalCommandAsync(BaseDiscordClien DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(), DmPermission = arg.IsDMEnabled.ToNullable(), Nsfw = arg.IsNsfw.GetValueOrDefault(false), + IntegrationTypes = arg.IntegrationTypes, + ContextTypes = arg.ContextTypes }; if (arg is SlashCommandProperties slashProps) @@ -146,7 +148,9 @@ public static Task BulkOverwriteGlobalCommandsAsync(BaseDi // TODO: better conversion to nullable optionals DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(), DmPermission = arg.IsDMEnabled.ToNullable(), - Nsfw = arg.IsNsfw.GetValueOrDefault(false) + Nsfw = arg.IsNsfw.GetValueOrDefault(false), + IntegrationTypes = arg.IntegrationTypes, + ContextTypes = arg.ContextTypes }; if (arg is SlashCommandProperties slashProps) @@ -190,7 +194,9 @@ public static async Task> BulkOverwriteG // TODO: better conversion to nullable optionals DefaultMemberPermission = arg.DefaultMemberPermissions.ToNullable(), DmPermission = arg.IsDMEnabled.ToNullable(), - Nsfw = arg.IsNsfw.GetValueOrDefault(false) + Nsfw = arg.IsNsfw.GetValueOrDefault(false), + IntegrationTypes = arg.IntegrationTypes, + ContextTypes = arg.ContextTypes }; if (arg is SlashCommandProperties slashProps) @@ -254,7 +260,9 @@ public static Task ModifyGlobalCommandAsync(BaseDiscordClien NameLocalizations = args.NameLocalizations?.ToDictionary(), DescriptionLocalizations = args.DescriptionLocalizations?.ToDictionary(), Nsfw = args.IsNsfw.GetValueOrDefault(false), - DefaultMemberPermission = args.DefaultMemberPermissions.ToNullable() + DefaultMemberPermission = args.DefaultMemberPermissions.ToNullable(), + IntegrationTypes = args.IntegrationTypes, + ContextTypes = args.ContextTypes }; if (args is SlashCommandProperties slashProps) diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs index 84ce9628e2..79008dc835 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestApplicationCommand.cs @@ -28,6 +28,7 @@ public abstract class RestApplicationCommand : RestEntity, IApplicationCo public bool IsDefaultPermission { get; private set; } /// + [Obsolete("This property will be deprecated soon. Use ContextTypes instead.")] public bool IsEnabledInDm { get; private set; } /// @@ -67,6 +68,12 @@ public abstract class RestApplicationCommand : RestEntity, IApplicationCo /// public string DescriptionLocalized { get; private set; } + /// + public IReadOnlyCollection IntegrationTypes { get; private set; } + + /// + public IReadOnlyCollection ContextTypes { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -102,9 +109,14 @@ internal virtual void Update(Model model) NameLocalized = model.NameLocalized.GetValueOrDefault(); DescriptionLocalized = model.DescriptionLocalized.GetValueOrDefault(); +#pragma warning disable CS0618 // Type or member is obsolete IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true); +#pragma warning restore CS0618 // Type or member is obsolete DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0)); IsNsfw = model.Nsfw.GetValueOrDefault(false).GetValueOrDefault(false); + + IntegrationTypes = model.IntegrationTypes.GetValueOrDefault(null)?.ToImmutableArray(); + ContextTypes = model.ContextTypes.GetValueOrDefault(null)?.ToImmutableArray(); } /// diff --git a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs index 52fb67262b..0360df97f6 100644 --- a/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs +++ b/src/Discord.Net.Rest/Entities/Interactions/RestInteraction.cs @@ -90,9 +90,18 @@ public bool IsValidToken /// public ulong ApplicationId { get; private set; } + /// + public InteractionContextType? ContextType { get; private set; } + + /// + public GuildPermissions Permissions { get; private set; } + /// public IReadOnlyCollection Entitlements { get; private set; } + /// + public IReadOnlyDictionary IntegrationOwners { get; private set; } + internal RestInteraction(BaseDiscordClient discord, ulong id) : base(discord, id) { @@ -232,6 +241,13 @@ ChannelType.Media or : null; Entitlements = model.Entitlements.Select(x => RestEntitlement.Create(discord, x)).ToImmutableArray(); + + IntegrationOwners = model.IntegrationOwners; + ContextType = model.ContextType.IsSpecified + ? model.ContextType.Value + : null; + + Permissions = new GuildPermissions((ulong)model.ApplicationPermissions); } internal string SerializePayload(object payload) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 9dcbb496d6..fd9aa07c96 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -308,6 +308,7 @@ public Task DeleteAsync(RequestOptions options = null) IReadOnlyCollection IMessage.Components => Components; /// + [Obsolete("This property will be deprecated soon. Use IUserMessage.InteractionMetadata instead.")] IMessageInteraction IMessage.Interaction => Interaction; /// diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index dc5c1f590a..2312f92c44 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -48,6 +48,9 @@ public class RestUserMessage : RestMessage, IUserMessage /// public IUserMessage ReferencedMessage => _referencedMessage; + /// + public IMessageInteractionMetadata InteractionMetadata { get; internal set; } + /// public MessageResolvedData ResolvedData { get; internal set; } @@ -162,6 +165,8 @@ internal override void Update(Model model) ResolvedData = new MessageResolvedData(users, members, roles, channels); } + if (model.InteractionMetadata.IsSpecified) + InteractionMetadata = model.InteractionMetadata.Value.ToInteractionMetadata(); } /// diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 6b253cf62d..2e2fcce5e4 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -6,183 +6,195 @@ using Model = Discord.API.Application; -namespace Discord.Rest +namespace Discord.Rest; + +/// +/// Represents a REST-based entity that contains information about a Discord application created via the developer portal. +/// +[DebuggerDisplay(@"{DebuggerDisplay,nq}")] +public class RestApplication : RestEntity, IApplication { - /// - /// Represents a REST-based entity that contains information about a Discord application created via the developer portal. - /// - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestApplication : RestEntity, IApplication - { - protected string _iconId; + protected string _iconId; - /// - public string Name { get; private set; } - /// - public string Description { get; private set; } - /// - public IReadOnlyCollection RPCOrigins { get; private set; } - /// - public ApplicationFlags Flags { get; private set; } - /// - public bool? IsBotPublic { get; private set; } - /// - public bool? BotRequiresCodeGrant { get; private set; } - /// - public ITeam Team { get; private set; } - /// - public IUser Owner { get; private set; } - /// - public string TermsOfService { get; private set; } - /// - public string PrivacyPolicy { get; private set; } + /// + public string Name { get; private set; } + /// + public string Description { get; private set; } + /// + public IReadOnlyCollection RPCOrigins { get; private set; } + /// + public ApplicationFlags Flags { get; private set; } + /// + public bool? IsBotPublic { get; private set; } + /// + public bool? BotRequiresCodeGrant { get; private set; } + /// + public ITeam Team { get; private set; } + /// + public IUser Owner { get; private set; } + /// + public string TermsOfService { get; private set; } + /// + public string PrivacyPolicy { get; private set; } - /// - public string VerifyKey { get; private set; } - /// - public string CustomInstallUrl { get; private set; } - /// - public string RoleConnectionsVerificationUrl { get; private set; } + /// + public string VerifyKey { get; private set; } + /// + public string CustomInstallUrl { get; private set; } + /// + public string RoleConnectionsVerificationUrl { get; private set; } - /// - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - /// - public string IconUrl => CDN.GetApplicationIconUrl(Id, _iconId); + /// + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + /// + public string IconUrl => CDN.GetApplicationIconUrl(Id, _iconId); - /// - public PartialGuild Guild { get; private set; } + /// + public PartialGuild Guild { get; private set; } - /// - public int? ApproximateGuildCount { get; private set; } + /// + public int? ApproximateGuildCount { get; private set; } - /// - public IReadOnlyCollection RedirectUris { get; private set; } + /// + public IReadOnlyCollection RedirectUris { get; private set; } - /// - public string InteractionsEndpointUrl { get; private set; } + /// + public string InteractionsEndpointUrl { get; private set; } - /// - public ApplicationInstallParams InstallParams { get; private set; } + /// + public ApplicationInstallParams InstallParams { get; private set; } - /// - public ApplicationDiscoverabilityState DiscoverabilityState { get; private set; } + /// + public ApplicationDiscoverabilityState DiscoverabilityState { get; private set; } - /// - public DiscoveryEligibilityFlags DiscoveryEligibilityFlags { get; private set; } + /// + public DiscoveryEligibilityFlags DiscoveryEligibilityFlags { get; private set; } - /// - public ApplicationExplicitContentFilterLevel ExplicitContentFilterLevel { get; private set; } + /// + public ApplicationExplicitContentFilterLevel ExplicitContentFilterLevel { get; private set; } - /// - public bool IsHook { get; private set; } + /// + public bool IsHook { get; private set; } - /// - public IReadOnlyCollection InteractionEventTypes { get; private set; } + /// + public IReadOnlyCollection InteractionEventTypes { get; private set; } - /// - public ApplicationInteractionsVersion InteractionsVersion { get; private set; } + /// + public ApplicationInteractionsVersion InteractionsVersion { get; private set; } - /// - public bool IsMonetized { get; private set; } + /// + public bool IsMonetized { get; private set; } - /// - public ApplicationMonetizationEligibilityFlags MonetizationEligibilityFlags { get; private set; } + /// + public ApplicationMonetizationEligibilityFlags MonetizationEligibilityFlags { get; private set; } - /// - public ApplicationMonetizationState MonetizationState { get; private set; } + /// + public ApplicationMonetizationState MonetizationState { get; private set; } - /// - public ApplicationRpcState RpcState { get; private set; } + /// + public ApplicationRpcState RpcState { get; private set; } - /// - public ApplicationStoreState StoreState { get; private set; } + /// + public ApplicationStoreState StoreState { get; private set; } - /// - public ApplicationVerificationState VerificationState { get; private set; } + /// + public ApplicationVerificationState VerificationState { get; private set; } - /// - public IReadOnlyCollection Tags { get; private set; } + /// + public IReadOnlyCollection Tags { get; private set; } - internal RestApplication(BaseDiscordClient discord, ulong id) - : base(discord, id) - { - } - internal static RestApplication Create(BaseDiscordClient discord, Model model) - { - var entity = new RestApplication(discord, model.Id); - entity.Update(model); - return entity; - } - internal void Update(Model model) - { - Description = model.Description; - RPCOrigins = model.RPCOrigins.IsSpecified ? model.RPCOrigins.Value.ToImmutableArray() : ImmutableArray.Empty; - Name = model.Name; - _iconId = model.Icon; - IsBotPublic = model.IsBotPublic.IsSpecified ? model.IsBotPublic.Value : null; - BotRequiresCodeGrant = model.BotRequiresCodeGrant.IsSpecified ? model.BotRequiresCodeGrant.Value : null; - Tags = model.Tags.GetValueOrDefault(null)?.ToImmutableArray() ?? ImmutableArray.Empty; - PrivacyPolicy = model.PrivacyPolicy; - TermsOfService = model.TermsOfService; - - InstallParams = model.InstallParams.IsSpecified - ? new ApplicationInstallParams(model.InstallParams.Value.Scopes, (GuildPermission)model.InstallParams.Value.Permission) - : null; - - if (model.Flags.IsSpecified) - Flags = model.Flags.Value; - if (model.Owner.IsSpecified) - Owner = RestUser.Create(Discord, model.Owner.Value); - if (model.Team != null) - Team = RestTeam.Create(Discord, model.Team); - - CustomInstallUrl = model.CustomInstallUrl.IsSpecified ? model.CustomInstallUrl.Value : null; - RoleConnectionsVerificationUrl = model.RoleConnectionsUrl.IsSpecified ? model.RoleConnectionsUrl.Value : null; - VerifyKey = model.VerifyKey; - - if (model.PartialGuild.IsSpecified) - Guild = PartialGuildExtensions.Create(model.PartialGuild.Value); - - InteractionsEndpointUrl = model.InteractionsEndpointUrl.IsSpecified ? model.InteractionsEndpointUrl.Value : null; - - if (model.RedirectUris.IsSpecified) - RedirectUris = model.RedirectUris.Value.ToImmutableArray(); - - ApproximateGuildCount = model.ApproximateGuildCount.IsSpecified ? model.ApproximateGuildCount.Value : null; - - DiscoverabilityState = model.DiscoverabilityState.GetValueOrDefault(ApplicationDiscoverabilityState.None); - DiscoveryEligibilityFlags = model.DiscoveryEligibilityFlags.GetValueOrDefault(DiscoveryEligibilityFlags.None); - ExplicitContentFilterLevel = model.ExplicitContentFilter.GetValueOrDefault(ApplicationExplicitContentFilterLevel.Disabled); - IsHook = model.IsHook; - - InteractionEventTypes = model.InteractionsEventTypes.GetValueOrDefault(Array.Empty()).ToImmutableArray(); - InteractionsVersion = model.InteractionsVersion.GetValueOrDefault(ApplicationInteractionsVersion.Version1); - - IsMonetized = model.IsMonetized; - MonetizationEligibilityFlags = model.MonetizationEligibilityFlags.GetValueOrDefault(ApplicationMonetizationEligibilityFlags.None); - MonetizationState = model.MonetizationState.GetValueOrDefault(ApplicationMonetizationState.None); - - RpcState = model.RpcState.GetValueOrDefault(ApplicationRpcState.Disabled); - StoreState = model.StoreState.GetValueOrDefault(ApplicationStoreState.None); - VerificationState = model.VerificationState.GetValueOrDefault(ApplicationVerificationState.Ineligible); - } + /// + public IReadOnlyDictionary IntegrationTypesConfig { get; private set; } - /// Unable to update this object from a different application token. - public async Task UpdateAsync() + internal RestApplication(BaseDiscordClient discord, ulong id) + : base(discord, id) + { + } + internal static RestApplication Create(BaseDiscordClient discord, Model model) + { + var entity = new RestApplication(discord, model.Id); + entity.Update(model); + return entity; + } + internal void Update(Model model) + { + Description = model.Description; + RPCOrigins = model.RPCOrigins.IsSpecified ? model.RPCOrigins.Value.ToImmutableArray() : ImmutableArray.Empty; + Name = model.Name; + _iconId = model.Icon; + IsBotPublic = model.IsBotPublic.IsSpecified ? model.IsBotPublic.Value : null; + BotRequiresCodeGrant = model.BotRequiresCodeGrant.IsSpecified ? model.BotRequiresCodeGrant.Value : null; + Tags = model.Tags.GetValueOrDefault(null)?.ToImmutableArray() ?? ImmutableArray.Empty; + PrivacyPolicy = model.PrivacyPolicy; + TermsOfService = model.TermsOfService; + + InstallParams = model.InstallParams.IsSpecified + ? new ApplicationInstallParams(model.InstallParams.Value.Scopes, (GuildPermission)model.InstallParams.Value.Permission) + : null; + + if (model.Flags.IsSpecified) + Flags = model.Flags.Value; + if (model.Owner.IsSpecified) + Owner = RestUser.Create(Discord, model.Owner.Value); + if (model.Team != null) + Team = RestTeam.Create(Discord, model.Team); + + CustomInstallUrl = model.CustomInstallUrl.IsSpecified ? model.CustomInstallUrl.Value : null; + RoleConnectionsVerificationUrl = model.RoleConnectionsUrl.IsSpecified ? model.RoleConnectionsUrl.Value : null; + VerifyKey = model.VerifyKey; + + if (model.PartialGuild.IsSpecified) + Guild = PartialGuildExtensions.Create(model.PartialGuild.Value); + + InteractionsEndpointUrl = model.InteractionsEndpointUrl.IsSpecified ? model.InteractionsEndpointUrl.Value : null; + + if (model.RedirectUris.IsSpecified) + RedirectUris = model.RedirectUris.Value.ToImmutableArray(); + + ApproximateGuildCount = model.ApproximateGuildCount.IsSpecified ? model.ApproximateGuildCount.Value : null; + + DiscoverabilityState = model.DiscoverabilityState.GetValueOrDefault(ApplicationDiscoverabilityState.None); + DiscoveryEligibilityFlags = model.DiscoveryEligibilityFlags.GetValueOrDefault(DiscoveryEligibilityFlags.None); + ExplicitContentFilterLevel = model.ExplicitContentFilter.GetValueOrDefault(ApplicationExplicitContentFilterLevel.Disabled); + IsHook = model.IsHook; + + InteractionEventTypes = model.InteractionsEventTypes.GetValueOrDefault(Array.Empty()).ToImmutableArray(); + InteractionsVersion = model.InteractionsVersion.GetValueOrDefault(ApplicationInteractionsVersion.Version1); + + IsMonetized = model.IsMonetized; + MonetizationEligibilityFlags = model.MonetizationEligibilityFlags.GetValueOrDefault(ApplicationMonetizationEligibilityFlags.None); + MonetizationState = model.MonetizationState.GetValueOrDefault(ApplicationMonetizationState.None); + + RpcState = model.RpcState.GetValueOrDefault(ApplicationRpcState.Disabled); + StoreState = model.StoreState.GetValueOrDefault(ApplicationStoreState.None); + VerificationState = model.VerificationState.GetValueOrDefault(ApplicationVerificationState.Ineligible); + + var dict = new Dictionary(); + if (model.IntegrationTypesConfig.IsSpecified) { - var response = await Discord.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); - if (response.Id != Id) - throw new InvalidOperationException("Unable to update this object from a different application token."); - Update(response); + foreach (var p in model.IntegrationTypesConfig.Value) + { + dict.Add(p.Key, new ApplicationInstallParams(p.Value.Scopes ?? Array.Empty(), p.Value.Permission)); + } } + IntegrationTypesConfig = dict.ToImmutableDictionary(); + } - /// - /// Gets the name of the application. - /// - /// - /// Name of the application. - /// - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; + /// Unable to update this object from a different application token. + public async Task UpdateAsync() + { + var response = await Discord.ApiClient.GetMyApplicationAsync().ConfigureAwait(false); + if (response.Id != Id) + throw new InvalidOperationException("Unable to update this object from a different application token."); + Update(response); } + + /// + /// Gets the name of the application. + /// + /// + /// The name of the application. + /// + public override string ToString() => Name; + private string DebuggerDisplay => $"{Name} ({Id})"; } diff --git a/src/Discord.Net.Rest/Extensions/InteractionMetadataExtensions.cs b/src/Discord.Net.Rest/Extensions/InteractionMetadataExtensions.cs new file mode 100644 index 0000000000..547bd3f909 --- /dev/null +++ b/src/Discord.Net.Rest/Extensions/InteractionMetadataExtensions.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; + +namespace Discord.Rest; + +internal static class InteractionMetadataExtensions +{ + public static IMessageInteractionMetadata ToInteractionMetadata(this API.MessageInteractionMetadata metadata) + { + switch (metadata.Type) + { + case InteractionType.ApplicationCommand: + return new ApplicationCommandInteractionMetadata( + metadata.Id, + metadata.Type, + metadata.UserId, + metadata.IntegrationOwners.ToImmutableDictionary(), + metadata.OriginalResponseMessageId.IsSpecified ? metadata.OriginalResponseMessageId.Value : null, + metadata.Name.GetValueOrDefault(null)); + + case InteractionType.MessageComponent: + return new MessageComponentInteractionMetadata( + metadata.Id, + metadata.Type, + metadata.UserId, + metadata.IntegrationOwners.ToImmutableDictionary(), + metadata.OriginalResponseMessageId.IsSpecified ? metadata.OriginalResponseMessageId.Value : null, + metadata.InteractedMessageId.GetValueOrDefault(0)); + + case InteractionType.ModalSubmit: + return new ModalSubmitInteractionMetadata( + metadata.Id, + metadata.Type, + metadata.UserId, + metadata.IntegrationOwners.ToImmutableDictionary(), + metadata.OriginalResponseMessageId.IsSpecified ? metadata.OriginalResponseMessageId.Value : null, + metadata.TriggeringInteractionMetadata.GetValueOrDefault(null)?.ToInteractionMetadata()); + + default: + return null; + } + } +} diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs index cdefd32609..d32c424503 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketApplicationCommand.cs @@ -37,6 +37,7 @@ public bool IsGlobalCommand public bool IsDefaultPermission { get; private set; } /// + [Obsolete("This property will be deprecated soon. Use ContextTypes instead.")] public bool IsEnabledInDm { get; private set; } /// @@ -79,6 +80,12 @@ public bool IsGlobalCommand /// public string DescriptionLocalized { get; private set; } + /// + public IReadOnlyCollection IntegrationTypes { get; private set; } + + /// + public IReadOnlyCollection ContextTypes { get; private set; } + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -131,9 +138,14 @@ internal void Update(Model model) NameLocalized = model.NameLocalized.GetValueOrDefault(); DescriptionLocalized = model.DescriptionLocalized.GetValueOrDefault(); +#pragma warning disable CS0618 // Type or member is obsolete IsEnabledInDm = model.DmPermission.GetValueOrDefault(true).GetValueOrDefault(true); +#pragma warning restore CS0618 // Type or member is obsolete DefaultMemberPermissions = new GuildPermissions((ulong)model.DefaultMemberPermission.GetValueOrDefault(0).GetValueOrDefault(0)); IsNsfw = model.Nsfw.GetValueOrDefault(false).GetValueOrDefault(false); + + IntegrationTypes = model.IntegrationTypes.GetValueOrDefault(null)?.ToImmutableArray(); + ContextTypes = model.ContextTypes.GetValueOrDefault(null)?.ToImmutableArray(); } /// diff --git a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs index d4ee366139..54a00b8a1e 100644 --- a/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs +++ b/src/Discord.Net.WebSocket/Entities/Interaction/SocketInteraction.cs @@ -73,9 +73,18 @@ public bool IsValidToken /// public ulong ApplicationId { get; private set; } + /// + public InteractionContextType? ContextType { get; private set; } + + /// + public GuildPermissions Permissions { get; private set; } + /// public IReadOnlyCollection Entitlements { get; private set; } + /// + public IReadOnlyDictionary IntegrationOwners { get; private set; } + internal SocketInteraction(DiscordSocketClient client, ulong id, ISocketMessageChannel channel, SocketUser user) : base(client, id) { @@ -149,6 +158,13 @@ internal virtual void Update(Model model) : null; Entitlements = model.Entitlements.Select(x => RestEntitlement.Create(Discord, x)).ToImmutableArray(); + + IntegrationOwners = model.IntegrationOwners; + ContextType = model.ContextType.IsSpecified + ? model.ContextType.Value + : null; + + Permissions = new GuildPermissions((ulong)model.ApplicationPermissions); } /// diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 8d9647267c..930c6ca177 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -334,6 +334,7 @@ public Task DeleteAsync(RequestOptions options = null) IReadOnlyCollection IMessage.Components => Components; /// + [Obsolete("This property will be deprecated soon. Use IUserMessage.InteractionMetadata instead.")] IMessageInteraction IMessage.Interaction => Interaction; /// diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 0a78066003..74d82fdb58 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -49,6 +49,9 @@ public class SocketUserMessage : SocketMessage, IUserMessage /// public IUserMessage ReferencedMessage => _referencedMessage; + /// + public IMessageInteractionMetadata InteractionMetadata { get; internal set; } + /// public MessageResolvedData ResolvedData { get; internal set; } @@ -203,6 +206,9 @@ internal override void Update(ClientState state, Model model) ResolvedData = new MessageResolvedData(users, members, roles, channels); } + + if (model.InteractionMetadata.IsSpecified) + InteractionMetadata = model.InteractionMetadata.Value.ToInteractionMetadata(); } ///