Skip to content

Commit

Permalink
Update ApplicationCommandEqualityChecks.cs
Browse files Browse the repository at this point in the history
  • Loading branch information
Lulalaby committed Sep 21, 2023
1 parent 3520ff3 commit 768272a
Showing 1 changed file with 131 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;

Expand All @@ -11,6 +10,9 @@

namespace DisCatSharp.ApplicationCommands.Checks;

/// <summary>
/// The application command equality checks.
/// </summary>
internal static class ApplicationCommandEqualityChecks
{
/// <summary>
Expand All @@ -20,42 +22,54 @@ internal static class ApplicationCommandEqualityChecks
/// <param name="targetApplicationCommand">Command to check against.</param>
/// <param name="client">The discord client.</param>
/// <param name="isGuild">Whether the equal check is performed for a guild command.</param>
internal static bool IsEqualTo(this DiscordApplicationCommand ac1,
DiscordApplicationCommand targetApplicationCommand, DiscordClient client, bool isGuild)
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, ac1.AllowedContexts, ac1.IntegrationTypes
);
ac1.Name, ac1.Description, ac1.Options,
ac1.Type,
ac1.NameLocalizations, ac1.DescriptionLocalizations,
ac1.DefaultMemberPermissions, ac1.DmPermission ?? true,
ac1.IsNsfw, ac1.AllowedContexts, ac1.IntegrationTypes
);

if (sourceApplicationCommand.DefaultMemberPermissions == Permissions.None &&
targetApplicationCommand.DefaultMemberPermissions == null)
targetApplicationCommand.DefaultMemberPermissions == null)
sourceApplicationCommand.DefaultMemberPermissions = null;

if (isGuild)
{
sourceApplicationCommand.DmPermission = null;
targetApplicationCommand.DmPermission = null;
sourceApplicationCommand.IntegrationTypes = null;
targetApplicationCommand.IntegrationTypes = null;
sourceApplicationCommand.AllowedContexts = null;
targetApplicationCommand.AllowedContexts = null;
}
else
{
sourceApplicationCommand.IntegrationTypes ??= new() { ApplicationCommandIntegrationTypes.InstalledToGuild };
targetApplicationCommand.IntegrationTypes ??= new() { ApplicationCommandIntegrationTypes.InstalledToGuild };
sourceApplicationCommand.IntegrationTypes ??= new()
{
ApplicationCommandIntegrationTypes.InstalledToGuild
};
targetApplicationCommand.IntegrationTypes ??= new()
{
ApplicationCommandIntegrationTypes.InstalledToGuild
};
}

client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel,
"[AC Change Check] Command {name}\n\n[{jsonOne},{jsontwo}]\n\n", ac1.Name,
JsonConvert.SerializeObject(sourceApplicationCommand),
JsonConvert.SerializeObject(targetApplicationCommand));
"[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, isGuild);
ac1.Type, client, ApplicationCommandsExtension.Configuration?.EnableLocalization ?? false, isGuild);
}

/// <summary>
Expand All @@ -67,56 +81,56 @@ internal static bool IsEqualTo(this DiscordApplicationCommand ac1,
/// <param name="type">The application command type.</param>
/// <param name="localizationEnabled">Whether localization is enabled.</param>
/// <param name="guild">Whether the equal check is performed for a guild command.</param>
internal static bool SoftEqual(this DiscordApplicationCommand source, DiscordApplicationCommand target,
ApplicationCommandType type, bool localizationEnabled = false, bool guild = false)
internal static bool SoftEqual(
this DiscordApplicationCommand source, DiscordApplicationCommand target,
ApplicationCommandType type, BaseDiscordClient client, bool localizationEnabled = false, bool guild = false
)
{
bool? sDmPerm = source.DmPermission ?? true;
bool? tDmPerm = target.DmPermission ?? true;
if (!guild)
return localizationEnabled
? type switch
{
ApplicationCommandType.ChatInput => DeepEqual(source, target, true, sDmPerm, tDmPerm),
ApplicationCommandType.ChatInput => DeepEqual(source, target, client, 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
&& source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes)
&& source.Type == target.Type && source.NameLocalizations == target.NameLocalizations
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
&& source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes) &&
source.RawNameLocalizations.AreDictionariesEqual(target.RawNameLocalizations)
}
: type switch
{
ApplicationCommandType.ChatInput => DeepEqual(source, target, false, sDmPerm, tDmPerm),
ApplicationCommandType.ChatInput => DeepEqual(source, target, client, false, sDmPerm, tDmPerm),
_ => source.Name == target.Name
&& source.Type == target.Type
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
&& source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes)
&& source.Type == target.Type
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
&& source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes)
};

sDmPerm = null;
tDmPerm = null;
return localizationEnabled
? type switch
{
ApplicationCommandType.ChatInput => DeepEqual(source, target, true, sDmPerm, tDmPerm),
ApplicationCommandType.ChatInput => DeepEqual(source, target, client, 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
&& source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes)
&& source.Type == target.Type && source.NameLocalizations == target.NameLocalizations
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
&& source.RawNameLocalizations.AreDictionariesEqual(target.RawNameLocalizations)
}
: type switch
{
ApplicationCommandType.ChatInput => DeepEqual(source, target, false, sDmPerm, tDmPerm),
ApplicationCommandType.ChatInput => DeepEqual(source, target, client, false, sDmPerm, tDmPerm),
_ => source.Name == target.Name
&& source.Type == target.Type
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
&& source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes)
&& source.Type == target.Type
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
};
}

Expand All @@ -127,13 +141,33 @@ internal static bool SoftEqual(this DiscordApplicationCommand source, DiscordApp
/// <param name="source">The source enumerable.</param>
/// <param name="target">The target enumerable.</param>
/// <returns>Whether both nullable enumerable are equal.</returns>
internal static bool NullableSequenceEqual<T>(this IEnumerable<T>? source, IEnumerable<T>? target)
internal static bool NullableSequenceEqual<T>(this List<T>? source, List<T>? target)
{
if (source is not null && target is not null)
return source.OrderBy(x => x).SequenceEqual(target.OrderBy(x => x));
return source.All(target.Contains) && source.Count == target.Count;

return source is null && target is null;
}

/// <summary>
/// Checks whether two dictionaries are equal.
/// </summary>
/// <param name="sourceDictionary">The source dictionary.</param>
/// <param name="targetDictionary">The target dictionary.</param>
/// <returns>Whether both dictionaries are equal.</returns>
internal static bool AreDictionariesEqual(this Dictionary<string, string> sourceDictionary, Dictionary<string, string> targetDictionary)
{
if (sourceDictionary?.Count != targetDictionary?.Count)
return false;

if (sourceDictionary is null && targetDictionary is null)
return true;

foreach (var kvp in sourceDictionary)
if (!targetDictionary.TryGetValue(kvp.Key, out var value) || value != kvp.Value)
return false;

return (source is null || target is not null) &&
(source is not null || target is null);
return true;
}

/// <summary>
Expand All @@ -145,29 +179,33 @@ internal static bool NullableSequenceEqual<T>(this IEnumerable<T>? source, IEnum
/// <param name="localizationEnabled">Whether localization is enabled.</param>
/// <param name="sDmPerm">The source dm permission.</param>
/// <param name="tDmPerm">The target dm permission.</param>
internal static bool DeepEqual(DiscordApplicationCommand source, DiscordApplicationCommand target,
bool localizationEnabled = false, bool? sDmPerm = null, bool? tDmPerm = null)
internal static bool DeepEqual(
DiscordApplicationCommand source, DiscordApplicationCommand target, BaseDiscordClient client,
bool localizationEnabled = false, bool? sDmPerm = null, bool? tDmPerm = null
)
{
var name = source.Name;
var rootCheck = source.Name == target.Name &&
source.Description == target.Description &&
source.Type == target.Type &&
source.DefaultMemberPermissions == target.DefaultMemberPermissions &&
sDmPerm == tDmPerm &&
source.IsNsfw == target.IsNsfw
&& source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes);
source.Description == target.Description &&
source.Type == target.Type &&
source.DefaultMemberPermissions == target.DefaultMemberPermissions &&
sDmPerm == tDmPerm &&
source.IsNsfw == target.IsNsfw &&
source.AllowedContexts.NullableSequenceEqual(target.AllowedContexts) &&
source.IntegrationTypes.NullableSequenceEqual(target.IntegrationTypes);

if (localizationEnabled)
rootCheck = rootCheck &&
source.NameLocalizations.Localizations.NullableSequenceEqual(target.NameLocalizations.Localizations) &&
source.DescriptionLocalizations.Localizations.NullableSequenceEqual(target.DescriptionLocalizations
.Localizations);
source.RawNameLocalizations.AreDictionariesEqual(target.RawNameLocalizations) &&
source.RawDescriptionLocalizations.AreDictionariesEqual(target.RawDescriptionLocalizations);

// Compare the Options using recursion
var optionsEqual = DeepEqualOptions(source.Options, target.Options, localizationEnabled);
if (optionsEqual.Reason is not null)
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, "Inequality found in options of {name} - {reason}", name, optionsEqual.Reason);
if (!rootCheck)
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, "Inequality found in root of {name}", name);

return rootCheck && optionsEqual;
return rootCheck && optionsEqual.Equal;
}

/// <summary>
Expand All @@ -176,65 +214,67 @@ internal static bool DeepEqual(DiscordApplicationCommand source, DiscordApplicat
/// <param name="sourceOptions">Source options.</param>
/// <param name="targetOptions">Options to check against.</param>
/// <param name="localizationEnabled">Whether localization is enabled.</param>
private static bool DeepEqualOptions(IReadOnlyList<DiscordApplicationCommandOption>? sourceOptions,
IReadOnlyList<DiscordApplicationCommandOption>? targetOptions, bool localizationEnabled)
private static (bool Equal, string? Reason) DeepEqualOptions(
IReadOnlyList<DiscordApplicationCommandOption>? sourceOptions,
IReadOnlyList<DiscordApplicationCommandOption>? targetOptions, bool localizationEnabled
)
{
if (sourceOptions == null && targetOptions == null)
return true;
return (true, null);

if ((sourceOptions != null && targetOptions == null) || (sourceOptions == null && targetOptions != null))
return false;
if (sourceOptions != null && targetOptions == null || sourceOptions == null && targetOptions != null)
return (false, "source or target option null, but not both");

if (sourceOptions!.Count != targetOptions!.Count)
return false;
return (false, "source option count mismatches target option count");

for (var i = 0; i < sourceOptions.Count; i++)
{
var sourceOption = sourceOptions[i];
var targetOption = targetOptions[i];

var optionCheck = sourceOption.Name == targetOption.Name &&
sourceOption.Description == targetOption.Description &&
sourceOption.Type == targetOption.Type &&
sourceOption.Required == targetOption.Required &&
sourceOption.AutoComplete == targetOption.AutoComplete &&
sourceOption.MinimumValue == targetOption.MinimumValue &&
sourceOption.MaximumValue == targetOption.MaximumValue &&
sourceOption.MinimumLength == targetOption.MinimumLength &&
sourceOption.MaximumLength == targetOption.MaximumLength;
sourceOption.Description == targetOption.Description &&
sourceOption.Type == targetOption.Type &&
sourceOption.Required == targetOption.Required &&
sourceOption.AutoComplete == targetOption.AutoComplete &&
sourceOption.MinimumValue?.ToString() == targetOption.MinimumValue?.ToString() &&
sourceOption.MaximumValue?.ToString() == targetOption.MaximumValue?.ToString() &&
sourceOption.MinimumLength == targetOption.MinimumLength &&
sourceOption.MaximumLength == targetOption.MaximumLength;

if (localizationEnabled)
optionCheck = optionCheck &&
sourceOption.NameLocalizations.Localizations.NullableSequenceEqual(targetOption.NameLocalizations
.Localizations) &&
sourceOption.DescriptionLocalizations.Localizations.NullableSequenceEqual(targetOption
.DescriptionLocalizations.Localizations);
sourceOption.RawNameLocalizations.AreDictionariesEqual(targetOption.RawNameLocalizations) &&
sourceOption.RawDescriptionLocalizations.AreDictionariesEqual(targetOption
.RawDescriptionLocalizations);

if ((sourceOption.Choices is null && targetOption.Choices is not null) ||
(sourceOption.Choices is not null && targetOption.Choices is null))
return false;
if (sourceOption.Choices is null && targetOption.Choices is not null ||
sourceOption.Choices is not null && targetOption.Choices is null)
return (false, "source or target choices null, but not both - " + sourceOption.Name);

if (sourceOption.Choices is not null && targetOption.Choices is not null)
{
var j1 = JsonConvert.SerializeObject(sourceOption.Choices.OrderBy(x => x.Name), Formatting.None);
var j2 = JsonConvert.SerializeObject(targetOption.Choices.OrderBy(x => x.Name), Formatting.None);
if (j1 != j2)
return false;
return (false, "choice mismatch - " + sourceOption.Name);
}

if ((sourceOption.ChannelTypes is null && targetOption.ChannelTypes is not null) ||
(sourceOption.ChannelTypes is not null && targetOption.ChannelTypes is null) ||
(sourceOption.ChannelTypes is not null && targetOption.ChannelTypes is not null &&
!sourceOption.ChannelTypes.OrderBy(x => x).All(targetOption.ChannelTypes.OrderBy(x => x).Contains)))
return false;
if (sourceOption.ChannelTypes is null && targetOption.ChannelTypes is not null ||
sourceOption.ChannelTypes is not null && targetOption.ChannelTypes is null ||
sourceOption.ChannelTypes is not null && targetOption.ChannelTypes is not null &&
!sourceOption.ChannelTypes.OrderBy(x => x).All(targetOption.ChannelTypes.OrderBy(x => x).Contains))
return (false, "channel type mismatch - " + sourceOption.Name);

if (!DeepEqualOptions(sourceOption.Options, targetOption.Options, localizationEnabled))
return false;
var rec = DeepEqualOptions(sourceOption.Options, targetOption.Options, localizationEnabled);
if (!rec.Equal)
return (false, "Options Recursive - " + sourceOption.Name + " -- " + rec.Reason);

if (!optionCheck)
return false;
return (false, "OptionCheck - " + sourceOption.Name);
}

return true;
return (true, null);
}
}

0 comments on commit 768272a

Please sign in to comment.