From 9ca68c84bd078143499cb441b5154d6d2bd8c429 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Wed, 4 Dec 2024 19:43:56 +0100 Subject: [PATCH] feat: support entry point command for activity enabled applications --- .../ApplicationCommandsExtension.cs | 16 ++++- .../Workers/RegistrationWorker.cs | 19 +++-- DisCatSharp/DiscordConfiguration.cs | 14 ++++ .../Application/DiscordApplicationCommand.cs | 72 ++++++++++++++----- 4 files changed, 94 insertions(+), 27 deletions(-) diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs index f54c208d3..1eab885ce 100644 --- a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs +++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs @@ -270,6 +270,8 @@ public IServiceProvider Services /// public static bool FinishFired { get; set; } = false; + internal static DiscordApplicationCommand? EntryPointCommand { get; set; } = null; + /// /// Runs setup. /// DO NOT RUN THIS MANUALLY. DO NOT DO ANYTHING WITH THIS. @@ -519,7 +521,7 @@ internal async Task UpdateAsync() List failedGuilds = []; var globalCommands = IsCalledByUnitTest ? null : (await this.Client.GetGlobalApplicationCommandsAsync(Configuration?.EnableLocalization ?? false).ConfigureAwait(false))?.ToList() ?? null; - var guilds = CheckAllGuilds ? this.Client.ReadyGuildIds : this._updateList.Where(x => x.Key is not null)?.Select(x => x.Key.Value).Distinct().ToList(); + var guilds = CheckAllGuilds ? this.Client.ReadyGuildIds : this._updateList.Where(x => x.Key is not null)?.Select(x => x.Key!.Value).Distinct().ToList(); var wrongShards = guilds is not null && this.Client.ReadyGuildIds.Count is not 0 ? guilds.Where(x => !this.Client.ReadyGuildIds.Contains(x)).ToList() : []; if (wrongShards.Count is not 0) { @@ -585,7 +587,14 @@ internal async Task UpdateAsync() } if (globalCommands is not null && globalCommands.Count is not 0) + { GlobalDiscordCommands.AddRange(globalCommands); + if (this.Client.Configuration.HasActivitiesEnabled) + { + var entryPointCommand = globalCommands.First(command => command.Name == "launch"); + EntryPointCommand = entryPointCommand; + } + } foreach (var key in commandsPending) { @@ -849,7 +858,7 @@ private async Task RegisterCommands(List { if (updateList.Count is not 0) { - var regCommands = await RegistrationWorker.RegisterGlobalCommandsAsync(this.Client, updateList).ConfigureAwait(false); + var regCommands = await RegistrationWorker.RegisterGlobalCommandsAsync(this.Client, updateList, EntryPointCommand).ConfigureAwait(false); if (regCommands is not null) { var actualCommands = regCommands.Distinct().ToList(); @@ -861,7 +870,8 @@ private async Task RegisterCommands(List foreach (var cmd in GlobalDiscordCommands) try { - await this.Client.DeleteGlobalApplicationCommandAsync(cmd.Id).ConfigureAwait(false); + if (EntryPointCommand is null || cmd.Name is not "launch") + await this.Client.DeleteGlobalApplicationCommandAsync(cmd.Id).ConfigureAwait(false); } catch (NotFoundException) { diff --git a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs index be0ea845a..3d151a572 100644 --- a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs +++ b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs @@ -21,12 +21,13 @@ internal class RegistrationWorker /// /// The discord client. /// The command list. + /// The entry point command. /// A list of registered commands. - internal static async Task?> RegisterGlobalCommandsAsync(DiscordClient client, List commands) + internal static async Task?> RegisterGlobalCommandsAsync(DiscordClient client, List commands, DiscordApplicationCommand? entryPointCommand = null) { var (changedCommands, unchangedCommands) = BuildGlobalOverwriteList(client, commands); - var globalCommandsCreateList = BuildGlobalCreateList(client, commands); - var globalCommandsDeleteList = BuildGlobalDeleteList(client, commands); + var globalCommandsCreateList = BuildGlobalCreateList(client, commands, entryPointCommand); + var globalCommandsDeleteList = BuildGlobalDeleteList(client, commands, entryPointCommand); if (globalCommandsCreateList!.NotEmptyAndNotNull() && unchangedCommands!.NotEmptyAndNotNull() && changedCommands!.NotEmptyAndNotNull()) { @@ -420,8 +421,9 @@ private static ( /// /// The discord client. /// The command list. + /// The entry point command. /// A list of command ids. - private static List? BuildGlobalDeleteList(DiscordClient client, List? updateList = null) + private static List? BuildGlobalDeleteList(DiscordClient client, List? updateList = null, DiscordApplicationCommand? entryPointCommand = null) { if (ApplicationCommandsExtension.GlobalDiscordCommands.Count is 0) return null; @@ -438,6 +440,9 @@ private static ( else invalidCommandIds.AddRange(from cmd in discord where updateList.All(ul => ul.Name != cmd.Name) select cmd.Id); + if (entryPointCommand is not null && invalidCommandIds.Contains(entryPointCommand.Id)) + invalidCommandIds.Remove(entryPointCommand.Id); + return invalidCommandIds; } @@ -446,8 +451,9 @@ private static ( /// /// The discord client. /// The command list. + /// The entry point command. /// A list of commands. - private static List? BuildGlobalCreateList(DiscordClient client, List? updateList = null) + private static List? BuildGlobalCreateList(DiscordClient client, List? updateList = null, DiscordApplicationCommand? entryPointCommand = null) { if (updateList is null) return null; @@ -461,6 +467,9 @@ private static ( newCommands.AddRange(updateList.Where(cmd => discord.All(d => d.Name != cmd.Name))); + if (entryPointCommand is not null && newCommands.All(command => command.Name != "launch")) + newCommands.Add(entryPointCommand); + return newCommands; } diff --git a/DisCatSharp/DiscordConfiguration.cs b/DisCatSharp/DiscordConfiguration.cs index 734b30ded..cf3a1ef5d 100644 --- a/DisCatSharp/DiscordConfiguration.cs +++ b/DisCatSharp/DiscordConfiguration.cs @@ -110,6 +110,8 @@ public DiscordConfiguration(DiscordConfiguration other) this.UpdateCheckGitHubToken = other.UpdateCheckGitHubToken; this.ShowReleaseNotesInUpdateCheck = other.ShowReleaseNotesInUpdateCheck; this.AutoFetchApplicationEmojis = other.AutoFetchApplicationEmojis; + this.HasActivitiesEnabled = other.HasActivitiesEnabled; + this.ActivityHandlerType = other.ActivityHandlerType; } /// @@ -498,4 +500,16 @@ internal List TrackExceptions /// Defaults to . /// public bool ShowReleaseNotesInUpdateCheck { internal get; set; } = false; + + /// + /// Whether this app uses activities. + /// Defaults to . + /// + public bool HasActivitiesEnabled { internal get; set; } = false; + + /// + /// If is , determines which handler type we use.. + /// Defaults to . + /// + public ApplicationCommandHandlerType ActivityHandlerType { internal get; set; } = ApplicationCommandHandlerType.DiscordLaunchActivity; } diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs index 2c74c9874..4aeeabf29 100644 --- a/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs +++ b/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs @@ -28,6 +28,7 @@ public class DiscordApplicationCommand : SnowflakeObject, IEquatableWhether this command is NSFW. /// Where the command can be used. /// The allowed integration types. + /// The handler type. public DiscordApplicationCommand( string name, string? description, @@ -39,47 +40,80 @@ public DiscordApplicationCommand( bool? dmPermission = null, bool isNsfw = false, List? allowedContexts = null, - List? integrationTypes = null + List? integrationTypes = null, + ApplicationCommandHandlerType? handlerType = null ) : base(["guild_id", "name_localizations", "description_localizations"]) { if (type is ApplicationCommandType.ChatInput) { if (!Utilities.IsValidSlashCommandName(name)) - throw new ArgumentException("Invalid slash command name specified. It must be below 32 characters and not contain any whitespace.", nameof(name)); + throw new ArgumentException($"Invalid slash command name specified. It must be below 32 characters and not contain any whitespace. Error for command {name}.", nameof(name)); if (name.Any(char.IsUpper)) - throw new ArgumentException("Slash command name cannot have any upper case characters.", nameof(name)); + throw new ArgumentException($"Slash command name cannot have any upper case characters. Error for command {name}.", nameof(name)); if (description?.Length > 100) - throw new ArgumentException("Slash command description cannot exceed 100 characters.", nameof(description)); + throw new ArgumentException($"Slash command description cannot exceed 100 characters. Error for command {name}.", nameof(description)); if (string.IsNullOrWhiteSpace(description)) - throw new ArgumentException("Slash commands need a description.", nameof(description)); + throw new ArgumentException($"Slash commands need a description. Error for command {name}.", nameof(description)); this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs(); this.RawDescriptionLocalizations = descriptionLocalizations?.GetKeyValuePairs(); + this.Type = type; + this.Name = name; + this.Description = description; + this.Options = options != null && options.Any() ? options.ToList() : null; + this.DefaultMemberPermissions = defaultMemberPermissions; + this.DmPermission = dmPermission; + this.IsNsfw = isNsfw; + this.AllowedContexts = allowedContexts; + this.IntegrationTypes = integrationTypes; + } + else if (type is ApplicationCommandType.PrimaryEntryPoint) + { + if (!Utilities.IsValidSlashCommandName(name)) + throw new ArgumentException($"Invalid slash command name specified. It must be below 32 characters and not contain any whitespace. Error for command {name}.", nameof(name)); + if (name.Any(char.IsUpper)) + throw new ArgumentException($"Slash command name cannot have any upper case characters. Error for command {name}.", nameof(name)); + if (description?.Length > 100) + throw new ArgumentException($"Slash command description cannot exceed 100 characters. Error for command {name}.", nameof(description)); + if (string.IsNullOrWhiteSpace(description)) + throw new ArgumentException($"Slash commands need a description. Error for command {name}.", nameof(description)); + if (options?.Any() ?? false) + throw new ArgumentException($"Primary entrypoints do not support options. Error for command {name}."); + + this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs(); + this.RawDescriptionLocalizations = descriptionLocalizations?.GetKeyValuePairs(); + this.Type = type; + this.Name = name; + this.Description = description; + this.Options = null; + this.DefaultMemberPermissions = defaultMemberPermissions; + this.DmPermission = dmPermission; + this.IsNsfw = isNsfw; + this.AllowedContexts = allowedContexts; + this.IntegrationTypes = integrationTypes; + this.HandlerType = handlerType; } else { if (!string.IsNullOrWhiteSpace(description)) - throw new ArgumentException("Context menus do not support descriptions."); + throw new ArgumentException($"Context menus do not support descriptions. Error for command {name}."); if (options?.Any() ?? false) - throw new ArgumentException("Context menus do not support options."); + throw new ArgumentException($"Context menus do not support options. Error for command {name}."); description = string.Empty; this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs(); + this.Type = type; + this.Name = name; + this.Description = description; + this.Options = null; + this.DefaultMemberPermissions = defaultMemberPermissions; + this.DmPermission = dmPermission; + this.IsNsfw = isNsfw; + this.AllowedContexts = allowedContexts; + this.IntegrationTypes = integrationTypes; } - - var optionsList = options != null && options.Any() ? options.ToList() : null; - - this.Type = type; - this.Name = name; - this.Description = description; - this.Options = optionsList; - this.DefaultMemberPermissions = defaultMemberPermissions; - this.DmPermission = dmPermission; - this.IsNsfw = isNsfw; - this.AllowedContexts = allowedContexts; - this.IntegrationTypes = integrationTypes; } ///