From c469f7a8bc3d8ff2bfd691e3d68d69fdba1e4ec2 Mon Sep 17 00:00:00 2001 From: Lala Sabathil Date: Fri, 6 Dec 2024 22:45:47 +0100 Subject: [PATCH] feat: support entry point command for activity enabled applications (#594) * feat: support entry point command for activity enabled applications * fix: do not yeet launch entry point & apply description fix: add missing locales --- .../ApplicationCommandsExtension.cs | 16 ++++- .../Workers/RegistrationWorker.cs | 19 +++-- DisCatSharp/DiscordConfiguration.cs | 14 ++++ .../Application/DiscordApplicationCommand.cs | 72 ++++++++++++++----- .../DiscordApplicationCommandLocalization.cs | 2 +- DisCatSharp/Net/Rest/DiscordApiClient.cs | 2 +- 6 files changed, 96 insertions(+), 29 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; } /// diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs index 46172e6f3..b238a21b4 100644 --- a/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs +++ b/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs @@ -12,7 +12,7 @@ public sealed class DiscordApplicationCommandLocalization /// /// Gets valid [locales](xref:modules_application_commands_translations_reference#valid-locales) for Discord. /// - internal readonly List ValidLocales = ["ru", "fi", "hr", "de", "hu", "sv-SE", "cs", "fr", "it", "en-GB", "pt-BR", "ja", "tr", "en-US", "es-ES", "uk", "hi", "th", "el", "no", "ro", "ko", "zh-TW", "vi", "zh-CN", "pl", "bg", "da", "nl", "lt", "id", "es-419"]; + internal readonly List ValidLocales = ["ar", "bg", "cs", "da", "de", "el", "en-GB", "en-US", "es-419", "es-ES", "fi", "fr", "he", "hi", "hr", "hu", "id", "it", "ja", "ko", "lt", "nl", "no", "pl", "pt-BR", "ro", "ru", "sv-SE", "th", "tr", "uk", "vi", "zh-CN", "zh-TW"]; /// /// Initializes a new instance of . diff --git a/DisCatSharp/Net/Rest/DiscordApiClient.cs b/DisCatSharp/Net/Rest/DiscordApiClient.cs index 55514bcd8..eceecefa2 100644 --- a/DisCatSharp/Net/Rest/DiscordApiClient.cs +++ b/DisCatSharp/Net/Rest/DiscordApiClient.cs @@ -6685,7 +6685,7 @@ internal async Task CreateGlobalApplicationCommandAsy { Type = command.Type, Name = command.Name, - Description = command.Type is ApplicationCommandType.ChatInput ? command.Description : null, + Description = command.Type is ApplicationCommandType.ChatInput or ApplicationCommandType.PrimaryEntryPoint ? command.Description : null, Options = command.Options, NameLocalizations = command.NameLocalizations?.GetKeyValuePairs(), DescriptionLocalizations = command.DescriptionLocalizations?.GetKeyValuePairs(),