Skip to content

Commit

Permalink
[Feature] Initial user apps support (#2883)
Browse files Browse the repository at this point in the history
* omg it kinda works somehow

* more things added

* a bit of xmldocs

* added interaction framework support

* working? IF

* more builder stuff

* space

* rename attribute to prevent conflict with `ContextType` enum

* context type

* moar features

* remove integration types

* trigger workflow

* modelzzzz

* `InteractionContextType`

* allow setting custom status with `SetGameAsync`

* bugzzz

* app permissions

* message interaction context

* hm

* push for cd

* structs lets goooo

* whoops forgot to change types

* whoops x2

* tweak some things

* xmldocs + missing prop + fix enabled in dm

* moar validations

* deprecate a bunch of stuffz

* disable moar obsolete warnings

* add IF sample

* Apply suggestions from code review

Co-authored-by: Quin Lynch <[email protected]>

* Update src/Discord.Net.Rest/Entities/RestApplication.cs

Co-authored-by: Quin Lynch <[email protected]>

---------

Co-authored-by: Quin Lynch <[email protected]>
  • Loading branch information
Misha-133 and quinchs authored Mar 18, 2024
1 parent bfc8dc2 commit 24a6978
Show file tree
Hide file tree
Showing 63 changed files with 1,234 additions and 277 deletions.
10 changes: 10 additions & 0 deletions docs/guides/int_framework/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions docs/guides/int_framework/samples/intro/userapps.cs
Original file line number Diff line number Diff line change
@@ -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<SocketInteractionContext>
{
[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}");
}
6 changes: 3 additions & 3 deletions docs/toc.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Discord;

/// <summary>
/// Defines where an application can be installed.
/// </summary>
public enum ApplicationIntegrationType
{
/// <summary>
/// The application can be installed to a guild.
/// </summary>
GuildInstall = 0,

/// <summary>
/// The application can be installed to a user.
/// </summary>
UserInstall = 1,
}
5 changes: 5 additions & 0 deletions src/Discord.Net.Core/Entities/Applications/IApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,5 +154,10 @@ public interface IApplication : ISnowflakeEntity
/// Gets the application's verification state.
/// </summary>
ApplicationVerificationState VerificationState { get; }

/// <summary>
/// Gets application install params configured for integration install types.
/// </summary>
IReadOnlyDictionary<ApplicationIntegrationType, ApplicationInstallParams> IntegrationTypesConfig { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;

namespace Discord;

/// <summary>
Expand Down Expand Up @@ -54,4 +56,9 @@ public class ModifyApplicationProperties
/// </remarks>
public Optional<ApplicationFlags> Flags { get; set; }

/// <summary>
/// Gets or sets application install params configured for integration install types.
/// </summary>
public Optional<Dictionary<ApplicationIntegrationType, ApplicationInstallParams>> IntegrationTypesConfig { get; set; }

}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ public IReadOnlyDictionary<string, string> DescriptionLocalizations
/// </summary>
public Optional<GuildPermission> DefaultMemberPermissions { get; set; }

/// <summary>
/// Gets or sets the install method for this command.
/// </summary>
public Optional<HashSet<ApplicationIntegrationType>> IntegrationTypes { get; set; }

/// <summary>
/// Gets or sets context types this command can be executed in.
/// </summary>
public Optional<HashSet<InteractionContextType>> ContextTypes { get; set; }

internal ApplicationCommandProperties() { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public interface IApplicationCommand : ISnowflakeEntity, IDeletable
/// <remarks>
/// Only for globally-scoped commands.
/// </remarks>
[Obsolete("This property will be deprecated soon. Use ContextTypes instead.")]
bool IsEnabledInDm { get; }

/// <summary>
Expand Down Expand Up @@ -83,6 +84,16 @@ public interface IApplicationCommand : ISnowflakeEntity, IDeletable
/// </remarks>
string DescriptionLocalized { get; }

/// <summary>
/// Gets context types the command can be used in; <see langword="null" /> if not specified.
/// </summary>
IReadOnlyCollection<InteractionContextType> ContextTypes { get; }

/// <summary>
/// Gets the install method for the command; <see langword="null" /> if not specified.
/// </summary>
IReadOnlyCollection<ApplicationIntegrationType> IntegrationTypes { get; }

/// <summary>
/// Modifies the current application command.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Discord;

/// <summary>
/// Represents a context in Discord where an interaction can be used.
/// </summary>
public enum InteractionContextType
{
/// <summary>
/// The command can be used in guilds.
/// </summary>
Guild = 0,

/// <summary>
/// The command can be used in DM channel with the bot.
/// </summary>
BotDm = 1,

/// <summary>
/// The command can be used in private channels.
/// </summary>
PrivateChannel = 2
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public string Name
/// <summary>
/// Gets or sets whether or not this command can be used in DMs.
/// </summary>
[Obsolete("This property will be deprecated soon. Configure with ContextTypes instead.")]
public bool IsDMEnabled { get; set; } = true;

/// <summary>
Expand All @@ -56,6 +57,16 @@ public string Name
/// </summary>
public GuildPermission? DefaultMemberPermissions { get; set; }

/// <summary>
/// Gets the install method for this command; <see langword="null" /> if not specified.
/// </summary>
public HashSet<ApplicationIntegrationType> IntegrationTypes { get; set; } = null;

/// <summary>
/// Gets the context types this command can be executed in; <see langword="null" /> if not specified.
/// </summary>
public HashSet<InteractionContextType> ContextTypes { get; set; } = null;

private string _name;
private Dictionary<string, string> _nameLocalizations;

Expand All @@ -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<GuildPermission>.Unspecified,
NameLocalizations = NameLocalizations,
IsNsfw = IsNsfw,
IntegrationTypes = IntegrationTypes,
ContextTypes = ContextTypes
};

return props;
Expand Down Expand Up @@ -133,6 +148,7 @@ public MessageCommandBuilder WithNameLocalizations(IDictionary<string, string> n
/// </summary>
/// <param name="permission"><see langword="true"/> if the command is available in dms, otherwise <see langword="false"/>.</param>
/// <returns>The current builder.</returns>
[Obsolete("This method will be deprecated soon. Configure with WithContextTypes instead.")]
public MessageCommandBuilder WithDMPermission(bool permission)
{
IsDMEnabled = permission;
Expand Down Expand Up @@ -187,5 +203,31 @@ public MessageCommandBuilder WithDefaultMemberPermissions(GuildPermission? permi
DefaultMemberPermissions = permissions;
return this;
}

/// <summary>
/// Sets the install method for this command.
/// </summary>
/// <param name="integrationTypes">Install types for this command.</param>
/// <returns>The builder instance.</returns>
public MessageCommandBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes)
{
IntegrationTypes = integrationTypes is not null
? new HashSet<ApplicationIntegrationType>(integrationTypes)
: null;
return this;
}

/// <summary>
/// Sets context types this command can be executed in.
/// </summary>
/// <param name="contextTypes">Context types the command can be executed in.</param>
/// <returns>The builder instance.</returns>
public MessageCommandBuilder WithContextTypes(params InteractionContextType[] contextTypes)
{
ContextTypes = contextTypes is not null
? new HashSet<InteractionContextType>(contextTypes)
: null;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public string Name
/// <summary>
/// Gets or sets whether or not this command can be used in DMs.
/// </summary>
[Obsolete("This property will be deprecated soon. Configure with ContextTypes instead.")]
public bool IsDMEnabled { get; set; } = true;

/// <summary>
Expand All @@ -56,6 +57,16 @@ public string Name
/// </summary>
public GuildPermission? DefaultMemberPermissions { get; set; }

/// <summary>
/// Gets the installation method for this command. <see langword="null"/> if not set.
/// </summary>
public HashSet<ApplicationIntegrationType> IntegrationTypes { get; set; }

/// <summary>
/// Gets the context types this command can be executed in. <see langword="null"/> if not set.
/// </summary>
public HashSet<InteractionContextType> ContextTypes { get; set; }

private string _name;
private Dictionary<string, string> _nameLocalizations;

Expand All @@ -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<GuildPermission>.Unspecified,
NameLocalizations = NameLocalizations,
IsNsfw = IsNsfw,
ContextTypes = ContextTypes,
IntegrationTypes = IntegrationTypes
};

return props;
Expand Down Expand Up @@ -131,6 +146,7 @@ public UserCommandBuilder WithNameLocalizations(IDictionary<string, string> name
/// </summary>
/// <param name="permission"><see langword="true"/> if the command is available in dms, otherwise <see langword="false"/>.</param>
/// <returns>The current builder.</returns>
[Obsolete("This method will be deprecated soon. Configure with WithContextTypes instead.")]
public UserCommandBuilder WithDMPermission(bool permission)
{
IsDMEnabled = permission;
Expand Down Expand Up @@ -185,5 +201,31 @@ public UserCommandBuilder WithDefaultMemberPermissions(GuildPermission? permissi
DefaultMemberPermissions = permissions;
return this;
}

/// <summary>
/// Sets the installation method for this command.
/// </summary>
/// <param name="integrationTypes">Installation types for this command.</param>
/// <returns>The builder instance.</returns>
public UserCommandBuilder WithIntegrationTypes(params ApplicationIntegrationType[] integrationTypes)
{
IntegrationTypes = integrationTypes is not null
? new HashSet<ApplicationIntegrationType>(integrationTypes)
: null;
return this;
}

/// <summary>
/// Sets context types this command can be executed in.
/// </summary>
/// <param name="contextTypes">Context types the command can be executed in.</param>
/// <returns>The builder instance.</returns>
public UserCommandBuilder WithContextTypes(params InteractionContextType[] contextTypes)
{
ContextTypes = contextTypes is not null
? new HashSet<InteractionContextType>(contextTypes)
: null;
return this;
}
}
}
15 changes: 15 additions & 0 deletions src/Discord.Net.Core/Entities/Interactions/IDiscordInteraction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ public interface IDiscordInteraction : ISnowflakeEntity
/// </summary>
IReadOnlyCollection<IEntitlement> Entitlements { get; }

/// <summary>
/// Gets which integrations authorized the interaction.
/// </summary>
IReadOnlyDictionary<ApplicationIntegrationType, ulong> IntegrationOwners { get; }

/// <summary>
/// Gets the context this interaction was created in. <see langword="null"/> if context type is unknown.
/// </summary>
InteractionContextType? ContextType { get; }

/// <summary>
/// Gets the permissions the app or bot has within the channel the interaction was sent from.
/// </summary>
GuildPermissions Permissions { get; }

/// <summary>
/// Responds to an Interaction with type <see cref="InteractionResponseType.ChannelMessageWithSource"/>.
/// </summary>
Expand Down
Loading

0 comments on commit 24a6978

Please sign in to comment.