diff --git a/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs b/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs index 61b0b12dc..d7e3dff46 100644 --- a/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs +++ b/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs @@ -1,312 +1,319 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2022 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using DisCatSharp.Entities; using DisCatSharp.Enums; using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace DisCatSharp.ApplicationCommands.Checks; internal static class ApplicationCommandEqualityChecks { /// /// Whether two application commands are equal. /// /// Source command. /// Command to check against. /// The discord client. - internal static bool IsEqualTo(this DiscordApplicationCommand ac1, DiscordApplicationCommand targetApplicationCommand, DiscordClient client) + /// Whether the equal check is performed for a guild command. + internal static bool IsEqualTo(this DiscordApplicationCommand ac1, DiscordApplicationCommand targetApplicationCommand, DiscordClient client, bool IsGuild) { if (targetApplicationCommand is null || ac1 is null) return false; DiscordApplicationCommand sourceApplicationCommand = new( ac1.Name, ac1.Description, ac1.Options, ac1.Type, ac1.NameLocalizations, ac1.DescriptionLocalizations, ac1.DefaultMemberPermissions, ac1.DmPermission ?? true//, ac1.IsNsfw ); + if (IsGuild) + { + sourceApplicationCommand.DmPermission = true; + targetApplicationCommand.DmPermission = true; + } + client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, "[AC Change Check] Command {name}\n\n[{jsonOne},{jsontwo}]\n\n", ac1.Name, JsonConvert.SerializeObject(sourceApplicationCommand), JsonConvert.SerializeObject(targetApplicationCommand)); return ac1.Type == targetApplicationCommand.Type && sourceApplicationCommand.SoftEqual(targetApplicationCommand, ac1.Type, ApplicationCommandsExtension.Configuration?.EnableLocalization ?? false); } /// /// Checks softly whether two s are the same. /// Excluding id, application id and version here. /// /// Source application command. /// Application command to check against. /// The application command type. /// Whether localization is enabled. internal static bool SoftEqual(this DiscordApplicationCommand source, DiscordApplicationCommand target, ApplicationCommandType type, bool localizationEnabled = false) { var sDmPerm = source.DmPermission ?? true; var tDmPerm = target.DmPermission ?? true; return localizationEnabled ? type switch { ApplicationCommandType.ChatInput => DeepEqual(source, target, true, sDmPerm, tDmPerm), _ => source.Name == target.Name && source.Type == target.Type && source.NameLocalizations == target.NameLocalizations && source.DefaultMemberPermissions == target.DefaultMemberPermissions && sDmPerm == tDmPerm //&& (source.IsNsfw == target.IsNsfw) } : type switch { ApplicationCommandType.ChatInput => DeepEqual(source, target, false, sDmPerm, tDmPerm), _ => source.Name == target.Name && source.Type == target.Type && source.DefaultMemberPermissions == target.DefaultMemberPermissions && sDmPerm == tDmPerm //&& (source.IsNsfw == target.IsNsfw) }; } /// /// Checks deeply whether two s are the same. /// Excluding id, application id and version here. /// /// Source application command. /// Application command to check against. /// Whether localization is enabled. /// The source dm permission. /// The target dm permission. internal static bool DeepEqual(DiscordApplicationCommand source, DiscordApplicationCommand target, bool localizationEnabled = false, bool sDmPerm = true, bool tDmPerm = true) { var rootCheck = true; /*//Console.WriteLine($"{source.Name == target.Name}"); //Console.WriteLine($"{source.Description == target.Description}"); //Console.WriteLine($"{source.Type == target.Type}"); //Console.WriteLine($"{source.DefaultMemberPermissions == target.DefaultMemberPermissions}"); //Console.WriteLine($"{sDmPerm == tDmPerm}");*/ rootCheck = source.Name == target.Name && source.Description == target.Description && source.Type == target.Type && source.DefaultMemberPermissions == target.DefaultMemberPermissions && sDmPerm == tDmPerm; if (localizationEnabled) rootCheck = rootCheck && source.NameLocalizations == target.NameLocalizations && source.DescriptionLocalizations == target.DescriptionLocalizations; ////Console.WriteLine($"{rootCheck}"); if (source.Options == null && target.Options == null) return rootCheck; else if ((source.Options != null && target.Options == null) || (source.Options == null && target.Options != null)) return false; else if (source.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommandGroup) && target.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommandGroup)) { List minimalSourceOptions = new(); List minimalTargetOptions = new(); foreach (var option in source.Options) { List minimalSubSourceOptions = new(); foreach (var subOption in option.Options) { List minimalSubSubSourceOptions = null; if (subOption.Options != null) { minimalSubSubSourceOptions = new(); foreach (var subSubOption in subOption.Options) minimalSubSubSourceOptions.Add(new DiscordApplicationCommandOption( subSubOption.Name, subSubOption.Description, subSubOption.Type, subSubOption.Required, subSubOption.Choices, null, subSubOption.ChannelTypes, subSubOption.AutoComplete, subSubOption.MinimumValue, subSubOption.MaximumValue, localizationEnabled ? subSubOption.NameLocalizations : null, localizationEnabled ? subSubOption.DescriptionLocalizations : null, subSubOption.MinimumLength, subSubOption.MaximumLength )); minimalSubSourceOptions.Add(new DiscordApplicationCommandOption( subOption.Name, subOption.Description, subOption.Type, options: minimalSubSubSourceOptions, nameLocalizations: localizationEnabled ? subOption.NameLocalizations : null, descriptionLocalizations: localizationEnabled ? subOption.DescriptionLocalizations : null )); } } minimalSourceOptions.Add(new DiscordApplicationCommandOption( option.Name, option.Description, option.Type, options: minimalSubSourceOptions, nameLocalizations: localizationEnabled ? option.NameLocalizations : null, descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null )); } foreach (var option in target.Options) { List minimalSubTargetOptions = new(); foreach (var subOption in option.Options) { List minimalSubSubTargetOptions = null; if (subOption.Options != null && subOption.Options.Any()) { minimalSubSubTargetOptions = new(); foreach (var subSubOption in subOption.Options) minimalSubSubTargetOptions.Add(new DiscordApplicationCommandOption( subSubOption.Name, subSubOption.Description, subSubOption.Type, subSubOption.Required, subSubOption.Choices, null, subSubOption.ChannelTypes, subSubOption.AutoComplete, subSubOption.MinimumValue, subSubOption.MaximumValue, localizationEnabled ? subSubOption.NameLocalizations : null, localizationEnabled ? subSubOption.DescriptionLocalizations : null, subSubOption.MinimumLength, subSubOption.MaximumLength )); minimalSubTargetOptions.Add(new DiscordApplicationCommandOption( subOption.Name, subOption.Description, subOption.Type, options: minimalSubSubTargetOptions, nameLocalizations: localizationEnabled ? subOption.NameLocalizations : null, descriptionLocalizations: localizationEnabled ? subOption.DescriptionLocalizations : null )); } } minimalTargetOptions.Add(new DiscordApplicationCommandOption( option.Name, option.Description, option.Type, options: minimalSubTargetOptions, nameLocalizations: localizationEnabled ? option.NameLocalizations : null, descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null )); } var sOpt = JsonConvert.SerializeObject(minimalSourceOptions, Formatting.None); var tOpt = JsonConvert.SerializeObject(minimalTargetOptions, Formatting.None); //Console.WriteLine("Checking equality subcommandgroup"); //Console.WriteLine($"{rootCheck}"); //Console.WriteLine($"{sOpt}"); //Console.WriteLine($"{tOpt}"); return rootCheck && sOpt == tOpt; } else if (source.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommand) && target.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommand)) { List minimalSourceOptions = new(); List minimalTargetOptions = new(); foreach (var option in source.Options) { List minimalSubSourceOptions =null; if (option.Options != null) { minimalSubSourceOptions = new(); foreach (var subOption in option.Options) minimalSubSourceOptions.Add(new DiscordApplicationCommandOption( subOption.Name, subOption.Description, subOption.Type, subOption.Required, subOption.Choices, null, subOption.ChannelTypes, subOption.AutoComplete, subOption.MinimumValue, subOption.MaximumValue, localizationEnabled ? subOption.NameLocalizations : null, localizationEnabled ? subOption.DescriptionLocalizations : null, subOption.MinimumLength, subOption.MaximumLength )); } minimalSourceOptions.Add(new DiscordApplicationCommandOption( option.Name, option.Description, option.Type, options: minimalSubSourceOptions, nameLocalizations: localizationEnabled ? option.NameLocalizations : null, descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null )); } foreach (var option in target.Options) { List minimalSubTargetOptions = null; if (option.Options != null && option.Options.Any()) { minimalSubTargetOptions = new(); foreach (var subOption in option.Options) minimalSubTargetOptions.Add(new DiscordApplicationCommandOption( subOption.Name, subOption.Description, subOption.Type, subOption.Required, subOption.Choices, null, subOption.ChannelTypes, subOption.AutoComplete, subOption.MinimumValue, subOption.MaximumValue, localizationEnabled ? subOption.NameLocalizations : null, localizationEnabled ? subOption.DescriptionLocalizations : null, subOption.MinimumLength, subOption.MaximumLength )); } minimalTargetOptions.Add(new DiscordApplicationCommandOption( option.Name, option.Description, option.Type, options: minimalSubTargetOptions, nameLocalizations: localizationEnabled ? option.NameLocalizations : null, descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null )); } var sOpt = JsonConvert.SerializeObject(minimalSourceOptions, Formatting.None); var tOpt = JsonConvert.SerializeObject(minimalTargetOptions, Formatting.None); //Console.WriteLine("Checking equality subcommand"); //Console.WriteLine($"{rootCheck}"); //Console.WriteLine($"{sOpt}"); //Console.WriteLine($"{tOpt}"); return rootCheck && sOpt == tOpt; } else { List minimalSourceOptions = new(); List minimalTargetOptions = new(); foreach (var option in source.Options) minimalSourceOptions.Add(new DiscordApplicationCommandOption( option.Name, option.Description, option.Type, option.Required, option.Choices, null, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue, localizationEnabled ? option.NameLocalizations : null, localizationEnabled ? option.DescriptionLocalizations : null, option.MinimumLength, option.MaximumLength )); foreach (var option in target.Options) minimalTargetOptions.Add(new DiscordApplicationCommandOption( option.Name, option.Description, option.Type, option.Required, option.Choices, null, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue, localizationEnabled ? option.NameLocalizations : null, localizationEnabled ? option.DescriptionLocalizations : null, option.MinimumLength, option.MaximumLength )); var sOpt = JsonConvert.SerializeObject(minimalSourceOptions, Formatting.None); var tOpt = JsonConvert.SerializeObject(minimalTargetOptions, Formatting.None); //Console.WriteLine("Checking equality other"); //Console.WriteLine($"{rootCheck}"); //Console.WriteLine($"{sOpt}"); //Console.WriteLine($"{tOpt}"); return rootCheck && sOpt == tOpt; } } } diff --git a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs index 642dba1d3..6ff129c1a 100644 --- a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs +++ b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs @@ -1,541 +1,541 @@ // This file is part of the DisCatSharp project, based off DSharpPlus. // // Copyright (c) 2021-2022 AITSYS // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DisCatSharp.ApplicationCommands.Checks; using DisCatSharp.Common; using DisCatSharp.Entities; using DisCatSharp.Exceptions; using Microsoft.Extensions.Logging; namespace DisCatSharp.ApplicationCommands.Workers; /// /// Represents a . /// internal class RegistrationWorker { /// /// Registers the global commands. /// /// The discord client. /// The command list. /// A list of registered commands. internal static async Task> RegisterGlobalCommandsAsync(DiscordClient client, List commands) { var (changedCommands, unchangedCommands) = BuildGlobalOverwriteList(client, commands); var globalCommandsCreateList = BuildGlobalCreateList(client, commands); var globalCommandsDeleteList = BuildGlobalDeleteList(client, commands); if (globalCommandsCreateList.NotEmptyAndNotNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Creating, re-using and overwriting application commands."); foreach (var cmd in globalCommandsCreateList) { var discordBackendCommand = await client.CreateGlobalApplicationCommandAsync(cmd); commands.Add(discordBackendCommand); } foreach (var cmd in changedCommands) { var command = cmd.Value; var discordBackendCommand = await client.EditGlobalApplicationCommandAsync(cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Optional.Some(command.Options); action.DefaultMemberPermissions = command.DefaultMemberPermissions; action.DmPermission = command.DmPermission ?? true; //action.IsNsfw = command.IsNsfw; }); commands.Add(discordBackendCommand); } commands.AddRange(unchangedCommands); } else if (globalCommandsCreateList.NotEmptyAndNotNull() && (unchangedCommands.NotEmptyAndNotNull() || changedCommands.NotEmptyAndNotNull())) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Creating, re-using and overwriting application commands."); foreach (var cmd in globalCommandsCreateList) { var discordBackendCommand = await client.CreateGlobalApplicationCommandAsync(cmd); commands.Add(discordBackendCommand); } if (changedCommands.NotEmptyAndNotNull()) foreach (var cmd in changedCommands) { var command = cmd.Value; var discordBackendCommand = await client.EditGlobalApplicationCommandAsync(cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Optional.Some(command.Options); action.DefaultMemberPermissions = command.DefaultMemberPermissions; action.DmPermission = command.DmPermission ?? true; //action.IsNsfw = command.IsNsfw; }); commands.Add(discordBackendCommand); } if (unchangedCommands.NotEmptyAndNotNull()) commands.AddRange(unchangedCommands); } else if (globalCommandsCreateList.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Editing & re-using application commands."); foreach (var cmd in changedCommands) { var command = cmd.Value; var discordBackendCommand = await client.EditGlobalApplicationCommandAsync(cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Optional.Some(command.Options); action.DefaultMemberPermissions = command.DefaultMemberPermissions; action.DmPermission = command.DmPermission ?? true; //action.IsNsfw = command.IsNsfw; }); commands.Add(discordBackendCommand); } commands.AddRange(unchangedCommands); } else if (globalCommandsCreateList.EmptyOrNull() && changedCommands.NotEmptyAndNotNull() && unchangedCommands.EmptyOrNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Overwriting all application commands."); List overwriteList = new(); foreach (var overwrite in changedCommands) { var cmd = overwrite.Value; cmd.Id = overwrite.Key; overwriteList.Add(cmd); } var discordBackendCommands = await client.BulkOverwriteGlobalApplicationCommandsAsync(overwriteList); commands.AddRange(discordBackendCommands); } else if (globalCommandsCreateList.NotEmptyAndNotNull() && changedCommands.EmptyOrNull() && unchangedCommands.EmptyOrNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Creating all application commands."); var cmds = await client.BulkOverwriteGlobalApplicationCommandsAsync(globalCommandsCreateList); commands.AddRange(cmds); } else if (globalCommandsCreateList.EmptyOrNull() && changedCommands.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Re-using all application commands."); commands.AddRange(unchangedCommands); } if (globalCommandsDeleteList.NotEmptyAndNotNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Deleting missing application commands."); foreach (var cmdId in globalCommandsDeleteList) try { await client.DeleteGlobalApplicationCommandAsync(cmdId); } catch (NotFoundException) { client.Logger.LogError(@"Could not delete global command {cmd}. Please clean up manually", cmdId); } } return commands.NotEmptyAndNotNull() ? commands : null; } /// /// Registers the guild commands. /// /// The discord client. /// The target guild id. /// The command list. /// A list of registered commands. internal static async Task> RegisterGuildCommandsAsync(DiscordClient client, ulong guildId, List commands) { var (changedCommands, unchangedCommands) = BuildGuildOverwriteList(client, guildId, commands); var guildCommandsCreateList = BuildGuildCreateList(client, guildId, commands); var guildCommandsDeleteList = BuildGuildDeleteList(client, guildId, commands); if (guildCommandsCreateList.NotEmptyAndNotNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Creating, re-using and overwriting application commands. Guild ID: {guild}", guildId); foreach (var cmd in guildCommandsCreateList) { var discordBackendCommand = await client.CreateGuildApplicationCommandAsync(guildId, cmd); commands.Add(discordBackendCommand); } foreach (var cmd in changedCommands) { var command = cmd.Value; var discordBackendCommand = await client.EditGuildApplicationCommandAsync(guildId, cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Optional.Some(command.Options); action.DefaultMemberPermissions = command.DefaultMemberPermissions; action.DmPermission = command.DmPermission ?? true; //action.IsNsfw = command.IsNsfw; }); commands.Add(discordBackendCommand); } commands.AddRange(unchangedCommands); } else if (guildCommandsCreateList.NotEmptyAndNotNull() && (unchangedCommands.NotEmptyAndNotNull() || changedCommands.NotEmptyAndNotNull())) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Creating, re-using and overwriting application commands. Guild ID: {guild}", guildId); foreach (var cmd in guildCommandsCreateList) { var discordBackendCommand = await client.CreateGuildApplicationCommandAsync(guildId, cmd); commands.Add(discordBackendCommand); } if (changedCommands.NotEmptyAndNotNull()) foreach (var cmd in changedCommands) { var command = cmd.Value; var discordBackendCommand = await client.EditGuildApplicationCommandAsync(guildId, cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Optional.Some(command.Options); action.DefaultMemberPermissions = command.DefaultMemberPermissions; action.DmPermission = command.DmPermission ?? true; //action.IsNsfw = command.IsNsfw; }); commands.Add(discordBackendCommand); } if (unchangedCommands.NotEmptyAndNotNull()) commands.AddRange(unchangedCommands); } else if (guildCommandsCreateList.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Editing & re-using application commands. Guild ID: {guild}", guildId); foreach (var cmd in changedCommands) { var command = cmd.Value; var discordBackendCommand = await client.EditGuildApplicationCommandAsync(guildId, cmd.Key, action => { action.Name = command.Name; action.NameLocalizations = command.NameLocalizations; action.Description = command.Description; action.DescriptionLocalizations = command.DescriptionLocalizations; if(command.Options != null && command.Options.Any()) action.Options = Optional.Some(command.Options); if (command.DefaultMemberPermissions.HasValue) action.DefaultMemberPermissions = command.DefaultMemberPermissions.Value; if (command.DmPermission.HasValue) action.DmPermission = command.DmPermission.Value; //action.IsNsfw = command.IsNsfw; }); commands.Add(discordBackendCommand); } commands.AddRange(unchangedCommands); } else if (guildCommandsCreateList.EmptyOrNull() && changedCommands.NotEmptyAndNotNull() && unchangedCommands.EmptyOrNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Overwriting all application commands. Guild ID: {guild}", guildId); List overwriteList = new(); foreach (var overwrite in changedCommands) { var cmd = overwrite.Value; cmd.Id = overwrite.Key; overwriteList.Add(cmd); } var discordBackendCommands = await client.BulkOverwriteGuildApplicationCommandsAsync(guildId, overwriteList); commands.AddRange(discordBackendCommands); } else if (guildCommandsCreateList.NotEmptyAndNotNull() && changedCommands.EmptyOrNull() && unchangedCommands.EmptyOrNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Creating all application commands. Guild ID: {guild}", guildId); var cmds = await client.BulkOverwriteGuildApplicationCommandsAsync(guildId, guildCommandsCreateList); commands.AddRange(cmds); } else if (guildCommandsCreateList.EmptyOrNull() && changedCommands.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull()) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Re-using all application commands Guild ID: {guild}.", guildId); commands.AddRange(unchangedCommands); } if (guildCommandsDeleteList.NotEmptyAndNotNull()) foreach (var cmdId in guildCommandsDeleteList) { client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Deleting missing application commands. Guild ID: {guild}", guildId); try { await client.DeleteGuildApplicationCommandAsync(guildId, cmdId); } catch (NotFoundException) { client.Logger.LogError(@"Could not delete guild command {cmd} in guild {guild}. Please clean up manually", cmdId, guildId); } } return commands.NotEmptyAndNotNull() ? commands : null; } /// /// Builds a list of guild command ids to be deleted on discords backend. /// /// The discord client. /// The guild id these commands belong to. /// The command list. /// A list of command ids. private static List BuildGuildDeleteList(DiscordClient client, ulong guildId, List updateList) { if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any() || !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out var discord) ) return null; List invalidCommandIds = new(); if (discord == null) return null; if (updateList == null) foreach (var cmd in discord) invalidCommandIds.Add(cmd.Id); else foreach (var cmd in discord) if (!updateList.Any(ul => ul.Name == cmd.Name)) invalidCommandIds.Add(cmd.Id); return invalidCommandIds; } /// /// Builds a list of guild commands to be created on discords backend. /// /// The discord client. /// The guild id these commands belong to. /// The command list. /// private static List BuildGuildCreateList(DiscordClient client, ulong guildId, List updateList) { if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any() || updateList == null || !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out var discord) ) return updateList; List newCommands = new(); if (discord == null) return updateList; foreach (var cmd in updateList) if (discord.All(d => d.Name != cmd.Name)) newCommands.Add(cmd); return newCommands; } /// /// Builds a list of guild commands to be overwritten on discords backend. /// /// The discord client. /// The guild id these commands belong to. /// The command list. /// A dictionary of command id and command. private static ( Dictionary changedCommands, List unchangedCommands ) BuildGuildOverwriteList(DiscordClient client, ulong guildId, List updateList) { if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any() || ApplicationCommandsExtension.GuildDiscordCommands.All(l => l.Key != guildId) || updateList == null || !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out var discord) ) return (null, null); Dictionary updateCommands = new(); List unchangedCommands = new(); if (discord == null) return (null, null); foreach (var cmd in updateList) if (discord.GetFirstValueWhere(d => d.Name == cmd.Name, out var command)) - if (command.IsEqualTo(cmd, client)) + if (command.IsEqualTo(cmd, client, true)) { if (ApplicationCommandsExtension.DebugEnabled) client.Logger.LogDebug(@"[AC] Command {cmdName} unchanged", cmd.Name); cmd.Id = command.Id; cmd.ApplicationId = command.ApplicationId; cmd.Version = command.Version; unchangedCommands.Add(cmd); } else { if (ApplicationCommandsExtension.DebugEnabled) client.Logger.LogDebug(@"[AC] Command {cmdName} changed", cmd.Name); updateCommands.Add(command.Id, cmd); } return (updateCommands, unchangedCommands); } /// /// Builds a list of global command ids to be deleted on discords backend. /// /// The discord client. /// The command list. /// A list of command ids. private static List BuildGlobalDeleteList(DiscordClient client, List updateList = null) { if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any() || ApplicationCommandsExtension.GlobalDiscordCommands == null ) return null; var discord = ApplicationCommandsExtension.GlobalDiscordCommands; List invalidCommandIds = new(); if (discord == null) return null; if (updateList == null) foreach (var cmd in discord) invalidCommandIds.Add(cmd.Id); else foreach (var cmd in discord) if (updateList.All(ul => ul.Name != cmd.Name)) invalidCommandIds.Add(cmd.Id); return invalidCommandIds; } /// /// Builds a list of global commands to be created on discords backend. /// /// The discord client. /// The command list. /// A list of commands. private static List BuildGlobalCreateList(DiscordClient client, List updateList) { if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any() || updateList == null) return updateList; var discord = ApplicationCommandsExtension.GlobalDiscordCommands; List newCommands = new(); if (discord == null) return updateList; foreach (var cmd in updateList) if (discord.All(d => d.Name != cmd.Name)) newCommands.Add(cmd); return newCommands; } /// /// Builds a list of global commands to be overwritten on discords backend. /// /// The discord client. /// The command list. /// A dictionary of command ids and commands. private static ( Dictionary changedCommands, List unchangedCommands ) BuildGlobalOverwriteList(DiscordClient client, List updateList) { if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any() || updateList == null || ApplicationCommandsExtension.GlobalDiscordCommands == null ) return (null, null); var discord = ApplicationCommandsExtension.GlobalDiscordCommands; if (discord == null) return (null, null); Dictionary updateCommands = new(); List unchangedCommands = new(); foreach (var cmd in updateList) if (discord.GetFirstValueWhere(d => d.Name == cmd.Name, out var command)) - if (command.IsEqualTo(cmd, client)) + if (command.IsEqualTo(cmd, client, false)) { if (ApplicationCommandsExtension.DebugEnabled) client.Logger.LogDebug(@"[AC] Command {cmdName} unchanged", cmd.Name); cmd.Id = command.Id; cmd.ApplicationId = command.ApplicationId; cmd.Version = command.Version; unchangedCommands.Add(cmd); } else { if (ApplicationCommandsExtension.DebugEnabled) client.Logger.LogDebug(@"[AC] Command {cmdName} changed", cmd.Name); updateCommands.Add(command.Id, cmd); } return (updateCommands, unchangedCommands); } }