Skip to content

Commit

Permalink
feat: support entry point command for activity enabled applications (#…
Browse files Browse the repository at this point in the history
…594)

* feat: support entry point command for activity enabled applications

* fix: do not yeet launch entry point & apply description

fix: add missing locales
  • Loading branch information
Lulalaby authored Dec 6, 2024
1 parent 5c07b60 commit c469f7a
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 29 deletions.
16 changes: 13 additions & 3 deletions DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ public IServiceProvider Services
/// </summary>
public static bool FinishFired { get; set; } = false;

internal static DiscordApplicationCommand? EntryPointCommand { get; set; } = null;

/// <summary>
/// Runs setup.
/// <note type="caution">DO NOT RUN THIS MANUALLY. DO NOT DO ANYTHING WITH THIS.</note>
Expand Down Expand Up @@ -519,7 +521,7 @@ internal async Task UpdateAsync()
List<ulong> 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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -849,7 +858,7 @@ private async Task RegisterCommands(List<ApplicationCommandsModuleConfiguration>
{
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();
Expand All @@ -861,7 +870,8 @@ private async Task RegisterCommands(List<ApplicationCommandsModuleConfiguration>
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)
{
Expand Down
19 changes: 14 additions & 5 deletions DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ internal class RegistrationWorker
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="commands">The command list.</param>
/// <param name="entryPointCommand">The entry point command.</param>
/// <returns>A list of registered commands.</returns>
internal static async Task<List<DiscordApplicationCommand>?> RegisterGlobalCommandsAsync(DiscordClient client, List<DiscordApplicationCommand> commands)
internal static async Task<List<DiscordApplicationCommand>?> RegisterGlobalCommandsAsync(DiscordClient client, List<DiscordApplicationCommand> 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())
{
Expand Down Expand Up @@ -420,8 +421,9 @@ private static (
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="updateList">The command list.</param>
/// <param name="entryPointCommand">The entry point command.</param>
/// <returns>A list of command ids.</returns>
private static List<ulong>? BuildGlobalDeleteList(DiscordClient client, List<DiscordApplicationCommand>? updateList = null)
private static List<ulong>? BuildGlobalDeleteList(DiscordClient client, List<DiscordApplicationCommand>? updateList = null, DiscordApplicationCommand? entryPointCommand = null)
{
if (ApplicationCommandsExtension.GlobalDiscordCommands.Count is 0)
return null;
Expand All @@ -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;
}

Expand All @@ -446,8 +451,9 @@ private static (
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="updateList">The command list.</param>
/// <param name="entryPointCommand">The entry point command.</param>
/// <returns>A list of commands.</returns>
private static List<DiscordApplicationCommand>? BuildGlobalCreateList(DiscordClient client, List<DiscordApplicationCommand>? updateList = null)
private static List<DiscordApplicationCommand>? BuildGlobalCreateList(DiscordClient client, List<DiscordApplicationCommand>? updateList = null, DiscordApplicationCommand? entryPointCommand = null)
{
if (updateList is null)
return null;
Expand All @@ -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;
}

Expand Down
14 changes: 14 additions & 0 deletions DisCatSharp/DiscordConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
Expand Down Expand Up @@ -498,4 +500,16 @@ internal List<Type> TrackExceptions
/// <para>Defaults to <see langword="false" />.</para>
/// </summary>
public bool ShowReleaseNotesInUpdateCheck { internal get; set; } = false;

/// <summary>
/// Whether this app uses activities.
/// <para>Defaults to <see langword="false" />.</para>
/// </summary>
public bool HasActivitiesEnabled { internal get; set; } = false;

/// <summary>
/// If <see cref="HasActivitiesEnabled" /> is <see langword="true" />, determines which handler type we use..
/// <para>Defaults to <see cref="ApplicationCommandHandlerType.DiscordLaunchActivity" />.</para>
/// </summary>
public ApplicationCommandHandlerType ActivityHandlerType { internal get; set; } = ApplicationCommandHandlerType.DiscordLaunchActivity;
}
72 changes: 53 additions & 19 deletions DisCatSharp/Entities/Application/DiscordApplicationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class DiscordApplicationCommand : SnowflakeObject, IEquatable<DiscordAppl
/// <param name="isNsfw">Whether this command is NSFW.</param>
/// <param name="allowedContexts">Where the command can be used.</param>
/// <param name="integrationTypes">The allowed integration types.</param>
/// <param name="handlerType">The handler type.</param>
public DiscordApplicationCommand(
string name,
string? description,
Expand All @@ -39,47 +40,80 @@ public DiscordApplicationCommand(
bool? dmPermission = null,
bool isNsfw = false,
List<InteractionContextType>? allowedContexts = null,
List<ApplicationCommandIntegrationTypes>? integrationTypes = null
List<ApplicationCommandIntegrationTypes>? 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;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed class DiscordApplicationCommandLocalization
/// <summary>
/// Gets valid [locales](xref:modules_application_commands_translations_reference#valid-locales) for Discord.
/// </summary>
internal readonly List<string> 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<string> 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"];

/// <summary>
/// Initializes a new instance of <see cref="DiscordApplicationCommandLocalization" />.
Expand Down
2 changes: 1 addition & 1 deletion DisCatSharp/Net/Rest/DiscordApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6685,7 +6685,7 @@ internal async Task<DiscordApplicationCommand> 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(),
Expand Down

0 comments on commit c469f7a

Please sign in to comment.