diff --git a/.idea/.idea.Remora.Discord/.idea/.gitignore b/.idea/.idea.Remora.Discord/.idea/.gitignore index bc9371e1b5..343d51bf13 100644 --- a/.idea/.idea.Remora.Discord/.idea/.gitignore +++ b/.idea/.idea.Remora.Discord/.idea/.gitignore @@ -11,3 +11,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/ApplicationIntegrationType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/ApplicationIntegrationType.cs new file mode 100644 index 0000000000..c7115b5cfb --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/ApplicationIntegrationType.cs @@ -0,0 +1,42 @@ +// +// ApplicationIntegrationType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents valid locations for users to install application integrations. +/// +[PublicAPI] +public enum ApplicationIntegrationType +{ + /// + /// Specifies that the integration can be installed on a guild. + /// + GuildInstallable = 0, + + /// + /// Specifies that the integration can be installed on a user. + /// + UserInstallable = 1, +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs index a6a26b7ef9..7e02553e0f 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplication.cs @@ -165,6 +165,11 @@ public interface IApplication : IPartialApplication /// new Optional CustomInstallUrl { get; } + /// + /// Gets the application's integration type configurations. + /// + new IReadOnlyDictionary IntegrationTypesConfig { get; } + /// Optional IPartialApplication.ID => this.ID; @@ -242,4 +247,7 @@ public interface IApplication : IPartialApplication /// Optional IPartialApplication.CustomInstallUrl => this.CustomInstallUrl; + + /// + Optional> IPartialApplication.IntegrationTypesConfig => new(this.IntegrationTypesConfig); } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationIntegrationTypeConfig.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationIntegrationTypeConfig.cs new file mode 100644 index 0000000000..66cca05a81 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationIntegrationTypeConfig.cs @@ -0,0 +1,37 @@ +// +// IApplicationIntegrationTypeConfig.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// The integration type configuration for an application. +/// +[PublicAPI] +public interface IApplicationIntegrationTypeConfig +{ + /// + /// Gets the OAuth2 install parameters for the integration type. + /// + public IApplicationOAuth2InstallParams OAuth2InstallParams { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationOAuth2InstallParams.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationOAuth2InstallParams.cs new file mode 100644 index 0000000000..2419832d46 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IApplicationOAuth2InstallParams.cs @@ -0,0 +1,46 @@ +// +// IApplicationOAuth2InstallParams.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents the OAuth2 install parameters for an application. +/// +[PublicAPI] +public interface IApplicationOAuth2InstallParams +{ + /// + /// Gets the permissions required for the application. + /// + /// + /// Only applicable if includes `bot`. + /// + IDiscordPermissionSet Permissions { get; } + + /// + /// Gets the list of OAuth2 scopes required for the application. + /// + IReadOnlyList Scopes { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs index f76a9d52f9..57dfb37e98 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Applications/IPartialApplication.cs @@ -110,4 +110,7 @@ public interface IPartialApplication /// Optional CustomInstallUrl { get; } + + /// + Optional> IntegrationTypesConfig { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs index 61fbeb55d0..f4f8607d5b 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IApplicationCommand.cs @@ -114,4 +114,14 @@ public interface IApplicationCommand /// Gets a value indicating whether this command is age-restricted. /// Optional IsNsfw { get; } + + /// + /// Gets a value indicating the contexts in which this command can be installed. + /// + Optional> IntegrationTypes { get; } + + /// + /// Gets a value indicating the contexts in which this command can be invoked. + /// + Optional> Contexts { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs index 8fe59b2d65..940b0012f4 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/ApplicationCommands/IBulkApplicationCommandData.cs @@ -61,4 +61,10 @@ public interface IBulkApplicationCommandData /// Optional IsNsfw { get; } + + /// + Optional> IntegrationTypes { get; } + + /// + Optional> Contexts { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IApplicationCommandInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IApplicationCommandInteractionMetadata.cs new file mode 100644 index 0000000000..2b7349b175 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IApplicationCommandInteractionMetadata.cs @@ -0,0 +1,37 @@ +// +// IApplicationCommandInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents metadata related to application commands. +/// +[PublicAPI] +public interface IApplicationCommandInteractionMetadata : IMessageInteractionMetadata +{ + /// + /// Gets the name of the command. + /// + string Name { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs index ef3ce30c89..52a8c6ea2f 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IInteraction.cs @@ -103,7 +103,7 @@ public interface IInteraction /// /// Gets the computed permissions for the application in the context of the interaction's execution. /// - Optional AppPermissions { get; } + IDiscordPermissionSet AppPermissions { get; } /// /// Gets the locale of the invoking user. @@ -122,4 +122,23 @@ public interface IInteraction /// Gets, for monetized apps, any entitlements for the invoking user. /// IReadOnlyList Entitlements { get; } + + /// + /// Gets the context of the interaction. + /// + Optional Context { get; } + + /// + /// Gets the integrations that authorized the interaction. + /// + /// + /// This is a mapping of the integration type to the ID of its resource. + /// + /// The dictionary contains the following, given the circumstances:
+ /// - If the integration is installed to a user, a key of and the value is the user ID.
+ /// - If the integration is installed to a guild, a key of and the value is the guild ID. + /// If the interaction is sent outside the context of a guild, the value is always zero.
+ ///
+ ///
+ Optional> AuthorizingIntegrationOwners { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageComponentInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageComponentInteractionMetadata.cs new file mode 100644 index 0000000000..36725b1112 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageComponentInteractionMetadata.cs @@ -0,0 +1,38 @@ +// +// IMessageComponentInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents metadata related to a message component interaction. +/// +[PublicAPI] +public interface IMessageComponentInteractionMetadata : IMessageInteractionMetadata +{ + /// + /// Gets the ID of the message that was interacted with. + /// + Snowflake InteractedMessageID { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageInteractionMetadata.cs new file mode 100644 index 0000000000..8e2decdbc1 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IMessageInteractionMetadata.cs @@ -0,0 +1,68 @@ +// +// IMessageInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents the metadata of an application command interaction. +/// +[PublicAPI] +public interface IMessageInteractionMetadata +{ + /// + /// Gets the ID of the interaction. + /// + Snowflake ID { get; } + + /// + /// Gets the ID of the user who triggered the interaction. + /// + Snowflake UserID { get; } + + /// + /// Gets the type of the interaction. + /// + InteractionType Type { get; } + + /// + /// Gets the ID of the original response message. Only applicable to followup messages. + /// + Optional OriginalResponseMessageID { get; } + + /// + /// Gets the integrations that authorized the interaction. + /// + /// + /// This is a mapping of the integration type to the ID of its resource. + /// + /// The dictionary contains the following, given the circumstances:
+ /// - If the integration is installed to a user, a key of and the value is the user ID.
+ /// - If the integration is installed to a guild, a key of and the value is the guild ID. + /// If the interaction is sent outside the context of a guild, the value is always zero.
+ ///
+ ///
+ IReadOnlyDictionary AuthorizingIntegrationOwners { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IModalSubmitInteractionMetadata.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IModalSubmitInteractionMetadata.cs new file mode 100644 index 0000000000..2c2dfd30e1 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/IModalSubmitInteractionMetadata.cs @@ -0,0 +1,38 @@ +// +// IModalSubmitInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using OneOf; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Represents metadata related to a modal submit interaction. +/// +[PublicAPI] +public interface IModalSubmitInteractionMetadata : IMessageInteractionMetadata +{ + /// + /// Gets the metadata for the interaction that triggered the modal. + /// + OneOf TriggeringInteractionMetadata { get; } +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionContextType.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionContextType.cs new file mode 100644 index 0000000000..f292c3b6d8 --- /dev/null +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Interactions/InteractionContextType.cs @@ -0,0 +1,47 @@ +// +// InteractionContextType.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; + +namespace Remora.Discord.API.Abstractions.Objects; + +/// +/// Enumerates various interaction context types. +/// +[PublicAPI] +public enum InteractionContextType +{ + /// + /// The interaction was executed in the context of a guild. + /// + Guild = 1, + + /// + /// The interaction was executed in the context of a direct message to the bot associated with the application. + /// + BotDM = 2, + + /// + /// The interaction was executed in the context of a direct message or group direct message. + /// + PrivateChannel = 3, +} diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs index 4944db72a3..6e8a968238 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IMessage.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Rest.Core; namespace Remora.Discord.API.Abstractions.Objects; @@ -193,6 +194,11 @@ public interface IMessage : IPartialMessage /// new Optional Resolved { get; } + /// + /// Gets the metadata of the interaction, if any. + /// + new Optional> InteractionMetadata { get; } + /// Optional IPartialMessage.ID => this.ID; @@ -282,4 +288,7 @@ public interface IMessage : IPartialMessage /// Optional IPartialMessage.Resolved => this.Resolved; + + /// + Optional> IPartialMessage.InteractionMetadata => this.InteractionMetadata; } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs index 06feb3faf5..e8cd370eb8 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Objects/Messages/IPartialMessage.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Rest.Core; namespace Remora.Discord.API.Abstractions.Objects; @@ -122,4 +123,7 @@ public interface IPartialMessage /// Optional Resolved { get; } + + /// + Optional> InteractionMetadata { get; } } diff --git a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs index a3b097bd2b..cb703bd09a 100644 --- a/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs +++ b/Backend/Remora.Discord.API.Abstractions/API/Rest/IDiscordRestApplicationAPI.cs @@ -72,6 +72,8 @@ Task>> GetGlobalApplicationCommandsAsy /// The permissions required to execute the command. /// Whether this command is executable in DMs. /// Whether the command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. Task> CreateGlobalApplicationCommandAsync @@ -86,6 +88,8 @@ Task> CreateGlobalApplicationCommandAsync Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -130,6 +134,8 @@ Task> GetGlobalApplicationCommandAsync /// The permissions required to execute the command. /// Whether this command is executable in DMs. /// Whether this command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. Task> EditGlobalApplicationCommandAsync @@ -144,6 +150,8 @@ Task> EditGlobalApplicationCommandAsync Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -197,6 +205,8 @@ Task>> GetGuildApplicationCommandsAsyn /// The localized descriptions of the command. /// The permissions required to execute the command. /// Whether the command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. Task> CreateGuildApplicationCommandAsync @@ -211,6 +221,8 @@ Task> CreateGuildApplicationCommandAsync Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -259,6 +271,8 @@ Task> GetGuildApplicationCommandAsync /// The localized descriptions of the command. /// The permissions required to execute the command. /// Whether this command is age-restricted. + /// The installation contexts the command can be installed to. + /// The contexts in which the command is allowed to be run in. /// The cancellation token for this operation. /// A creation result which may or may not have succeeded. /// @@ -276,6 +290,8 @@ Task> EditGuildApplicationCommandAsync Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ); @@ -394,6 +410,7 @@ Task>> UpdateApplicatio /// The new cover image. /// The new interactions endpoint URL. /// The new tags. + /// The new integration types. /// The cancellation token for this operation. /// The updated application. Task> EditCurrentApplicationAsync @@ -407,6 +424,7 @@ Task> EditCurrentApplicationAsync Optional coverImage = default, Optional interactionsEndpointUrl = default, Optional> tags = default, + Optional> integrationTypes = default, CancellationToken ct = default ); } diff --git a/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj b/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj index 6d51932ace..7ca89deb06 100644 --- a/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj +++ b/Backend/Remora.Discord.API.Abstractions/Remora.Discord.API.Abstractions.csproj @@ -117,6 +117,15 @@ IDiscordRestGuildAPI.cs + + IMessageInteractionMetadata.cs + + + IMessageInteractionMetadata.cs + + + IMessageInteractionMetadata.cs + diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs index 5f86584432..b7a411f20f 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Interactions/InteractionCreate.cs @@ -47,8 +47,10 @@ public record InteractionCreate string Token, int Version, Optional Message, - Optional AppPermissions, + IDiscordPermissionSet AppPermissions, Optional Locale, Optional GuildLocale, - IReadOnlyList Entitlements + IReadOnlyList Entitlements, + Optional Context, + Optional> AuthorizingIntegrationOwners ) : IInteractionCreate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs index 098c2d7464..4ff3e7c950 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageCreate.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -64,5 +65,6 @@ public record MessageCreate Optional> Components = default, Optional> StickerItems = default, Optional Position = default, - Optional Resolved = default + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IMessageCreate; diff --git a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs index 4969f0d7f1..d3d5591ccf 100644 --- a/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs +++ b/Backend/Remora.Discord.API/API/Gateway/Events/Messages/MessageUpdate.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -64,5 +65,6 @@ public record MessageUpdate Optional> Components = default, Optional> StickerItems = default, Optional Position = default, - Optional Resolved = default + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IMessageUpdate; diff --git a/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs b/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs index cb27e7571e..11cf2e040c 100644 --- a/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs +++ b/Backend/Remora.Discord.API/API/Objects/Applications/Application.cs @@ -47,6 +47,7 @@ public record Application Optional Owner, string VerifyKey, ITeam? Team, + IReadOnlyDictionary IntegrationTypesConfig, Optional GuildID = default, Optional Guild = default, Optional PrimarySKUID = default, diff --git a/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs b/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs index 4a1550ad49..58726b36e3 100644 --- a/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs +++ b/Backend/Remora.Discord.API/API/Objects/Applications/PartialApplication.cs @@ -59,5 +59,6 @@ public record PartialApplication Optional RoleConnectionsVerificationUrl = default, Optional> Tags = default, Optional InstallParams = default, - Optional CustomInstallUrl = default + Optional CustomInstallUrl = default, + Optional> IntegrationTypesConfig = default ) : IPartialApplication; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommandInteractionMetadata.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommandInteractionMetadata.cs new file mode 100644 index 0000000000..83714785d2 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommandInteractionMetadata.cs @@ -0,0 +1,40 @@ +// +// ApplicationCommandInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ApplicationCommandInteractionMetadata +( + Snowflake ID, + Snowflake UserID, + InteractionType Type, + Optional OriginalResponseMessageID, + IReadOnlyDictionary AuthorizingIntegrationOwners, + string Name +) : IApplicationCommandInteractionMetadata; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs index fd7a81b558..d909a12895 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/ApplicationCommand.cs @@ -47,5 +47,7 @@ public record ApplicationCommand Optional DescriptionLocalized = default, IDiscordPermissionSet? DefaultMemberPermissions = default, Optional DMPermission = default, - Optional IsNsfw = default + Optional IsNsfw = default, + Optional> IntegrationTypes = default, + Optional> Contexts = default ) : IApplicationCommand; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs index 9c19c5c6f0..97df83b703 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationCommands/BulkApplicationCommandData.cs @@ -40,5 +40,7 @@ public record BulkApplicationCommandData Optional?> DescriptionLocalizations = default, IDiscordPermissionSet? DefaultMemberPermissions = default, Optional DMPermission = default, - Optional IsNsfw = default + Optional IsNsfw = default, + Optional> IntegrationTypes = default, + Optional> Contexts = default ) : IBulkApplicationCommandData; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationIntegrationTypeConfig.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationIntegrationTypeConfig.cs new file mode 100644 index 0000000000..4be5c8f345 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationIntegrationTypeConfig.cs @@ -0,0 +1,30 @@ +// +// ApplicationIntegrationTypeConfig.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ApplicationIntegrationTypeConfig(IApplicationOAuth2InstallParams OAuth2InstallParams) : IApplicationIntegrationTypeConfig; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationOAuth2InstallParams.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationOAuth2InstallParams.cs new file mode 100644 index 0000000000..ef86f77243 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ApplicationOAuth2InstallParams.cs @@ -0,0 +1,31 @@ +// +// ApplicationOAuth2InstallParams.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ApplicationOAuth2InstallParams(IDiscordPermissionSet Permissions, IReadOnlyList Scopes) : IApplicationOAuth2InstallParams; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs index 6aabaa483b..1e65ef2a4d 100644 --- a/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/Interaction.cs @@ -46,8 +46,10 @@ public record Interaction string Token, int Version, Optional Message, - Optional AppPermissions, + IDiscordPermissionSet AppPermissions, Optional Locale, Optional GuildLocale, - IReadOnlyList Entitlements + IReadOnlyList Entitlements, + Optional Context, + Optional> AuthorizingIntegrationOwners ) : IInteraction; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/MessageComponentInteractionMetadata.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/MessageComponentInteractionMetadata.cs new file mode 100644 index 0000000000..701a08d5e8 --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/MessageComponentInteractionMetadata.cs @@ -0,0 +1,40 @@ +// +// MessageComponentInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record MessageComponentInteractionMetadata +( + Snowflake ID, + Snowflake UserID, + InteractionType Type, + Optional OriginalResponseMessageID, + IReadOnlyDictionary AuthorizingIntegrationOwners, + Snowflake InteractedMessageID +) : IMessageComponentInteractionMetadata; diff --git a/Backend/Remora.Discord.API/API/Objects/Interactions/ModalSubmitInteractionMetadata.cs b/Backend/Remora.Discord.API/API/Objects/Interactions/ModalSubmitInteractionMetadata.cs new file mode 100644 index 0000000000..0ddf24ab7f --- /dev/null +++ b/Backend/Remora.Discord.API/API/Objects/Interactions/ModalSubmitInteractionMetadata.cs @@ -0,0 +1,41 @@ +// +// ModalSubmitInteractionMetadata.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Collections.Generic; +using JetBrains.Annotations; +using OneOf; +using Remora.Discord.API.Abstractions.Objects; +using Remora.Rest.Core; + +namespace Remora.Discord.API.Objects; + +/// +[PublicAPI] +public record ModalSubmitInteractionMetadata +( + Snowflake ID, + Snowflake UserID, + InteractionType Type, + Optional OriginalResponseMessageID, + IReadOnlyDictionary AuthorizingIntegrationOwners, + OneOf TriggeringInteractionMetadata +) : IModalSubmitInteractionMetadata; diff --git a/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs b/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs index 725eadcfc9..480d9ef1da 100644 --- a/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs +++ b/Backend/Remora.Discord.API/API/Objects/Messages/Message.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -63,5 +64,6 @@ public record Message Optional> Components = default, Optional> StickerItems = default, Optional Position = default, - Optional Resolved = default + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IMessage; diff --git a/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs b/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs index e6b6ea7e97..d1bd60826b 100644 --- a/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs +++ b/Backend/Remora.Discord.API/API/Objects/Messages/PartialMessage.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using OneOf; using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; @@ -63,5 +64,6 @@ public record PartialMessage Optional> Components = default, Optional> StickerItems = default, Optional Position = default, - Optional Resolved = default + Optional Resolved = default, + Optional> InteractionMetadata = default ) : IPartialMessage; diff --git a/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs b/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs index 61b65e5267..5decb3ea76 100644 --- a/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs +++ b/Backend/Remora.Discord.API/Extensions/ServiceCollectionExtensions.cs @@ -1052,7 +1052,7 @@ private static JsonSerializerOptions AddInteractionObjectConverters(this JsonSer options.AddDataObjectConverter(); options.AddDataObjectConverter() - .WithPropertyName(d => d.IsNsfw, "nsfw"); + .WithPropertyName(d => d.IsNsfw, "nsfw"); options.AddDataObjectConverter() .WithPropertyName(o => o.IsDefault, "default") @@ -1061,8 +1061,9 @@ private static JsonSerializerOptions AddInteractionObjectConverters(this JsonSer options.AddDataObjectConverter(); options.AddDataObjectConverter(); + options.AddDataObjectConverter() - .WithPropertyName(d => d.IsNsfw, "nsfw"); + .WithPropertyName(d => d.IsNsfw, "nsfw"); options.AddDataObjectConverter < @@ -1207,6 +1208,12 @@ private static JsonSerializerOptions AddOAuth2ObjectConverters(this JsonSerializ options.AddDataObjectConverter(); + options.AddDataObjectConverter() + .WithPropertyName(a => a.OAuth2InstallParams, "oauth2_install_params"); + + options.AddDataObjectConverter(); + + options.Converters.Insert(0, new StringEnumConverter(options.PropertyNamingPolicy, true)); return options; } diff --git a/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs b/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs index 783423d158..dc97d60fc6 100644 --- a/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs +++ b/Backend/Remora.Discord.Rest/API/Applications/DiscordRestApplicationAPI.cs @@ -103,6 +103,8 @@ public virtual async Task> CreateGlobalApplicationCo Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -151,6 +153,8 @@ public virtual async Task> CreateGlobalApplicationCo json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("dm_permission", dmPermission, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -230,6 +234,8 @@ public virtual async Task> EditGlobalApplicationComm Optional defaultMemberPermissions = default, Optional dmPermission = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -267,6 +273,8 @@ public virtual async Task> EditGlobalApplicationComm json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("dm_permission", dmPermission, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -379,6 +387,8 @@ public virtual async Task> CreateGuildApplicationCom Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -426,6 +436,8 @@ public virtual async Task> CreateGuildApplicationCom json.Write("description_localizations", descriptionLocalizations, this.JsonOptions); json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -463,6 +475,8 @@ public virtual async Task> EditGuildApplicationComma Optional?> descriptionLocalizations = default, Optional defaultMemberPermissions = default, Optional isNsfw = default, + Optional> allowedIntegrationTypes = default, + Optional> allowedContextTypes = default, CancellationToken ct = default ) { @@ -498,6 +512,8 @@ public virtual async Task> EditGuildApplicationComma json.Write("description_localizations", descriptionLocalizations, this.JsonOptions); json.Write("default_member_permissions", defaultMemberPermissions, this.JsonOptions); json.Write("nsfw", isNsfw, this.JsonOptions); + json.Write("contexts", allowedContextTypes, this.JsonOptions); + json.Write("integration_types", allowedIntegrationTypes, this.JsonOptions); } ) .WithRateLimitContext(this.RateLimitCache), @@ -667,6 +683,7 @@ public async Task> EditCurrentApplicationAsync Optional coverImage = default, Optional interactionsEndpointUrl = default, Optional> tags = default, + Optional> integrationTypes = default, CancellationToken ct = default ) { @@ -709,6 +726,7 @@ public async Task> EditCurrentApplicationAsync json.Write("cover_image", base64EncodedCover, this.JsonOptions); json.Write("interactions_endpoint_url", interactionsEndpointUrl, this.JsonOptions); json.Write("tags", tags, this.JsonOptions); + json.Write("integration_types", integrationTypes, this.JsonOptions); } ); diff --git a/Directory.Packages.props b/Directory.Packages.props index 3bce533a5d..f54917a7c6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,24 +13,24 @@ - + - + - + - + diff --git a/Remora.Discord.Commands/Attributes/AllowedContextsAttribute.cs b/Remora.Discord.Commands/Attributes/AllowedContextsAttribute.cs new file mode 100644 index 0000000000..fa85204db0 --- /dev/null +++ b/Remora.Discord.Commands/Attributes/AllowedContextsAttribute.cs @@ -0,0 +1,43 @@ +// +// AllowedContextsAttribute.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.Commands.Attributes; + +/// +/// Defines the contexts in which a command can be invoked. +/// +/// The contexts the command can be invoked. +[PublicAPI] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class AllowedContextsAttribute(params InteractionContextType[] contexts) : Attribute +{ + /// + /// Gets a value specifying the allowed contexts. + /// + public IReadOnlyList Contexts { get; } = contexts.ToArray(); +} diff --git a/Remora.Discord.Commands/Attributes/DiscordInstallContextAttribute.cs b/Remora.Discord.Commands/Attributes/DiscordInstallContextAttribute.cs new file mode 100644 index 0000000000..8b6aca80d7 --- /dev/null +++ b/Remora.Discord.Commands/Attributes/DiscordInstallContextAttribute.cs @@ -0,0 +1,42 @@ +// +// DiscordInstallContextAttribute.cs +// +// Author: +// Jarl Gullberg +// +// Copyright (c) Jarl Gullberg +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Remora.Discord.API.Abstractions.Objects; + +namespace Remora.Discord.Commands.Attributes; + +/// +/// Specifies that the command is valid to install in the given contexts. +/// +/// The contexts that the command can be installed into. +[PublicAPI] +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class DiscordInstallContextAttribute(params ApplicationIntegrationType[] installTypes) : Attribute +{ + /// + /// Gets a value specifying the contexts that the command can be installed into. + /// + public IReadOnlyList InstallTypes { get; } = installTypes; +} diff --git a/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs b/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs index 313a176224..f3eee1077e 100644 --- a/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs +++ b/Remora.Discord.Commands/Extensions/CommandTreeExtensions.cs @@ -199,7 +199,7 @@ public static IReadOnlyList CreateApplicationComman } // Translate from options to bulk data - var (commandType, directMessagePermission, defaultMemberPermissions, isNsfw) = GetNodeMetadata(node); + var (commandType, directMessagePermission, defaultMemberPermissions, isNsfw, allowedInstalls, allowedContexts) = GetNodeMetadata(node); var localizedNames = localizationProvider.GetStrings(option.Name); var localizedDescriptions = localizationProvider.GetStrings(option.Description); @@ -217,7 +217,9 @@ public static IReadOnlyList CreateApplicationComman localizedDescriptions.Count > 0 ? new(localizedDescriptions) : default, defaultMemberPermissions, directMessagePermission, - isNsfw + isNsfw, + allowedInstalls, + allowedContexts ) ); } @@ -241,6 +243,8 @@ private static TopLevelMetadata GetNodeMetadata(IChildNode node) Optional directMessagePermission = default; IDiscordPermissionSet? defaultMemberPermissions = default; Optional isNsfw = default; + Optional> allowedIntegrationTypes = default; + Optional> allowedContextTypes = default; switch (node) { @@ -321,6 +325,58 @@ private static TopLevelMetadata GetNodeMetadata(IChildNode node) isNsfw = nsfwAttribute.IsNsfw; } + var contextsAttributes = groupNode.GroupTypes.Select + ( + t => t.GetCustomAttribute() + ); + + var contexts = contextsAttributes + .Where(attribute => attribute is not null) + .ToArray(); + + if (contexts.Length > 1) + { + throw new InvalidNodeException + ( + $"In a set of groups with the same name, only one may be marked with a context attribute, but " + + $"{contexts.Length} were found.", + node + ); + } + + var context = contexts.SingleOrDefault(); + + if (context is not null) + { + allowedContextTypes = context.Contexts.AsOptional(); + } + + var installAttributes = groupNode.GroupTypes.Select + ( + t => t.GetCustomAttribute() + ); + + var installs = installAttributes + .Where(attribute => attribute is not null) + .ToArray(); + + if (installs.Length > 1) + { + throw new InvalidNodeException + ( + $"In a set of groups with the same name, only one may be marked with an install attribute, " + + $"but {installs.Length} were found.", + node + ); + } + + var install = installs.SingleOrDefault(); + + if (install is not null) + { + allowedIntegrationTypes = install.InstallTypes.AsOptional(); + } + break; } case CommandNode commandNode: @@ -358,11 +414,29 @@ private static TopLevelMetadata GetNodeMetadata(IChildNode node) isNsfw = nsfwAttribute.IsNsfw; } + var contextsAttribute = + commandNode.GroupType.GetCustomAttribute() ?? + commandNode.CommandMethod.GetCustomAttribute(); + + if (contextsAttribute is not null) + { + allowedContextTypes = contextsAttribute.Contexts.AsOptional(); + } + + var integrationAttribute = + commandNode.GroupType.GetCustomAttribute() ?? + commandNode.CommandMethod.GetCustomAttribute(); + + if (integrationAttribute is not null) + { + allowedIntegrationTypes = integrationAttribute.InstallTypes.AsOptional(); + } + break; } } - return new(commandType, directMessagePermission, defaultMemberPermissions, isNsfw); + return new(commandType, directMessagePermission, defaultMemberPermissions, isNsfw, allowedIntegrationTypes, allowedContextTypes); } private static IApplicationCommandOption? TranslateCommandNode @@ -1043,11 +1117,15 @@ Optional MaxLength /// The DM permission requested for the node. /// The default member permission requested for the node. /// The age restriction requested for the node. + /// The integration types allowed for the node. + /// The context types allowed for the node. private sealed record TopLevelMetadata ( Optional CommandType, Optional DirectMessagePermission, IDiscordPermissionSet? DefaultMemberPermission, - Optional IsNsfw + Optional IsNsfw, + Optional> AllowedIntegrationTypes, + Optional> AllowedContextTypes ); } diff --git a/Remora.Discord.sln.DotSettings b/Remora.Discord.sln.DotSettings index 41e5dd2154..c1b6756ce6 100644 --- a/Remora.Discord.sln.DotSettings +++ b/Remora.Discord.sln.DotSettings @@ -143,10 +143,19 @@ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> True True True True + True True True True diff --git a/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs b/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs index 7e4cc05eee..90eca8f418 100644 --- a/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs +++ b/Tests/Remora.Discord.Rest.Tests/API/Applications/DiscordRestApplicationAPITests.cs @@ -2495,6 +2495,12 @@ public async Task PerformsRequestCorrectly() var interactionsEndpointUrl = new Uri("https://example.org/interact"); var tags = new[] { "ooga", "booga" }; + var integrationTypes = new[] + { + ApplicationIntegrationType.UserInstallable, + ApplicationIntegrationType.GuildInstallable + }; + var api = CreateAPI ( b => b @@ -2534,6 +2540,16 @@ public async Task PerformsRequestCorrectly() .WithElement(1, e => e.Is("booga")) ) ) + .WithProperty + ( + "integration_types", + p => p.IsArray + ( + a => a.WithCount(2) + .WithElement(0, e => e.Is("1")) + .WithElement(1, e => e.Is("0")) + ) + ) ) ) .Respond("application/json", SampleRepository.Samples[typeof(IApplication)]) @@ -2549,7 +2565,8 @@ public async Task PerformsRequestCorrectly() icon, cover, interactionsEndpointUrl, - tags + tags, + integrationTypes ); ResultAssert.Successful(result); diff --git a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json index 3887a7864c..a58a9b9a4c 100644 --- a/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Gateway/Events/INTERACTION_CREATE/INTERACTION_CREATE.optionals.json @@ -8,6 +8,7 @@ "type": 2, "token": "none", "version": 1, + "app_permissions": "180224", "entitlements": [ { "id": "999999999999999999", diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json index 7e27fe746a..6eeaf68a58 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.json @@ -56,5 +56,9 @@ ], "permissions": "0" }, - "custom_install_url": "https://www.example.org" + "custom_install_url": "https://www.example.org", + "integration_types_config": { + "0": { "oauth2_install_params": { "scopes": [ "bot", "application.commands" ], "permissions": "0"} }, + "1": { "oauth2_install_params": { "scopes": [ "application.commands" ], "permissions": "0" } } + } } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json index 1a9fa6485b..344248eed6 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.nulls.json @@ -30,5 +30,9 @@ "permissions": "0" }, "custom_install_url": "https://www.example.org", - "role_connections_verification_url": "https://www.example.org" + "role_connections_verification_url": "https://www.example.org", + "integration_types_config": { + "0": { "oauth2_install_params": { "scopes": [ "bot", "application.commands" ], "permissions": "0"} }, + "1": { "oauth2_install_params": { "scopes": [ "application.commands" ], "permissions": "0" } } + } } \ No newline at end of file diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json index 44497c1265..926d3d2922 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/APPLICATION/APPLICATION.optionals.json @@ -27,5 +27,9 @@ ], "name": "none", "owner_user_id": "999999999999999999" + }, + "integration_types_config": { + "0": { "oauth2_install_params": { "scopes": [ "bot", "application.commands" ], "permissions": "0" } }, + "1": { "oauth2_install_params": { "scopes": [ "application.commands" ], "permissions": "0" } } } } diff --git a/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json b/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json index 3d7449104e..d5c9ba6b03 100644 --- a/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json +++ b/Tests/Remora.Discord.Tests/Samples/Objects/INTERACTION/INTERACTION.optionals.json @@ -4,6 +4,7 @@ "type": 2, "token": "none", "version": 1, + "app_permissions": "180224", "entitlements": [ { "id": "999999999999999999",