From 1a5cba875d349a983d53a0881558d7798cd23de3 Mon Sep 17 00:00:00 2001
From: zobweyt <98274273+zobweyt@users.noreply.github.com>
Date: Sun, 12 May 2024 01:27:15 +0300
Subject: [PATCH] [Feature] Parameter precondition attribute for simplifying
performing hierarchical operations within a guild (#2906)
* Support interaction framework and update bundled preconditions docs
* Support text commands and update bundled preconditions docs
* Fix example
* Move hierarchy util to `PermissionUtils`
* Refactoring
---
docs/guides/int_framework/preconditions.md | 1 +
docs/guides/text_commands/preconditions.md | 1 +
.../DoHierarchyCheckAttribute.cs | 49 +++++++++++++++++++
src/Discord.Net.Core/Utils/PermissionUtils.cs | 31 ++++++++++++
.../DoHierarchyCheckAttribute.cs | 48 ++++++++++++++++++
5 files changed, 130 insertions(+)
create mode 100644 src/Discord.Net.Commands/Attributes/Preconditions/DoHierarchyCheckAttribute.cs
create mode 100644 src/Discord.Net.Core/Utils/PermissionUtils.cs
create mode 100644 src/Discord.Net.Interactions/Attributes/Preconditions/DoHierarchyCheckAttribute.cs
diff --git a/docs/guides/int_framework/preconditions.md b/docs/guides/int_framework/preconditions.md
index bfa7168e..9573ff61 100644
--- a/docs/guides/int_framework/preconditions.md
+++ b/docs/guides/int_framework/preconditions.md
@@ -30,6 +30,7 @@ to use.
* @Discord.Interactions.RequireNsfwAttribute
* @Discord.Interactions.RequireRoleAttribute
* @Discord.Interactions.RequireTeamAttribute
+* @Discord.Interactions.DoHierarchyCheckAttribute
## Using Preconditions
diff --git a/docs/guides/text_commands/preconditions.md b/docs/guides/text_commands/preconditions.md
index 4be7ca2b..86ddccbb 100644
--- a/docs/guides/text_commands/preconditions.md
+++ b/docs/guides/text_commands/preconditions.md
@@ -29,6 +29,7 @@ to use.
* @Discord.Commands.RequireBotPermissionAttribute
* @Discord.Commands.RequireUserPermissionAttribute
* @Discord.Commands.RequireNsfwAttribute
+* @Discord.Commands.DoHierarchyCheckAttribute
## Using Preconditions
diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/DoHierarchyCheckAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/DoHierarchyCheckAttribute.cs
new file mode 100644
index 00000000..615bfea6
--- /dev/null
+++ b/src/Discord.Net.Commands/Attributes/Preconditions/DoHierarchyCheckAttribute.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord.Commands
+{
+ ///
+ /// Ensures that command parameters are passed within a correct hierarchical context.
+ ///
+ ///
+ /// Useful for performing hierarchical operations within a guild, such as managing roles or users.
+ ///
+ /// This supports , , and parameter types.
+ ///
+ ///
+ ///
+ /// Thrown when the parameter type is not supported by this precondition attribute.
+ ///
+ ///
+ ///
+ public class DoHierarchyCheckAttribute : ParameterPreconditionAttribute
+ {
+ ///
+ /// Gets or sets the error message displayed when the command is used outside of a guild.
+ ///
+ public string NotAGuildErrorMessage { get; set; } = "This command cannot be used outside of a guild.";
+
+ ///
+ /// Gets the error message to be returned if execution context doesn't pass the precondition check.
+ ///
+ public string ErrorMessage { get; set; } = "You cannot target anyone who is higher or equal in the hierarchy to you or the bot.";
+
+ ///
+ ///
+ /// Thrown when the parameter type is not supported by this precondition attribute.
+ ///
+ public override async Task CheckPermissionsAsync(ICommandContext context, ParameterInfo parameterInfo, object value, IServiceProvider services)
+ {
+ if (context.User is not IGuildUser guildUser)
+ return PreconditionResult.FromError(NotAGuildErrorMessage);
+
+ var hieararchy = PermissionUtils.GetHieararchy(value);
+ if (hieararchy >= guildUser.Hierarchy ||
+ hieararchy >= (await context.Guild.GetCurrentUserAsync().ConfigureAwait(false)).Hierarchy)
+ return PreconditionResult.FromError(ErrorMessage);
+
+ return PreconditionResult.FromSuccess();
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/Utils/PermissionUtils.cs b/src/Discord.Net.Core/Utils/PermissionUtils.cs
new file mode 100644
index 00000000..edd159d0
--- /dev/null
+++ b/src/Discord.Net.Core/Utils/PermissionUtils.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Discord
+{
+ ///
+ /// Provides a series of helper methods for permissions.
+ ///
+ public static class PermissionUtils
+ {
+ ///
+ /// Determines the hierarchy of a target object based on its type.
+ ///
+ ///
+ /// The target object: , , or .
+ ///
+ ///
+ /// An integer representing the hierarchy of the target.
+ ///
+ ///
+ /// Thrown when the parameter type is not supported by this precondition attribute.
+ ///
+ public static int GetHieararchy(object target) => target switch
+ {
+ // The order of cases here is important to determine the correct hierarchy value.
+ IRole role => role.Position,
+ IGuildUser guildUser => guildUser.Hierarchy,
+ IUser => int.MinValue,
+ _ => throw new ArgumentOutOfRangeException(nameof(target), "Cannot determine hierarchy for the provided target.")
+ };
+ }
+}
diff --git a/src/Discord.Net.Interactions/Attributes/Preconditions/DoHierarchyCheckAttribute.cs b/src/Discord.Net.Interactions/Attributes/Preconditions/DoHierarchyCheckAttribute.cs
new file mode 100644
index 00000000..180bc393
--- /dev/null
+++ b/src/Discord.Net.Interactions/Attributes/Preconditions/DoHierarchyCheckAttribute.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord.Interactions
+{
+ ///
+ /// Ensures that command parameters are passed within a correct hierarchical context.
+ ///
+ ///
+ /// Useful for performing hierarchical operations within a guild, such as managing roles or users.
+ ///
+ /// This supports , , and parameter types.
+ ///
+ ///
+ ///
+ /// Thrown when the parameter type is not supported by this precondition attribute.
+ ///
+ ///
+ ///
+ ///
+ public class DoHierarchyCheckAttribute : ParameterPreconditionAttribute
+ {
+ ///
+ /// Gets or sets the error message displayed when the command is used outside of a guild.
+ ///
+ public string NotAGuildErrorMessage { get; set; } = "This command cannot be used outside of a guild.";
+
+ ///
+ public override string ErrorMessage => "You cannot target anyone who is higher or equal in the hierarchy to you or the bot.";
+
+ ///
+ ///
+ /// Thrown when the parameter type is not supported by this precondition attribute.
+ ///
+ public override async Task CheckRequirementsAsync(IInteractionContext context, IParameterInfo parameterInfo, object value, IServiceProvider services)
+ {
+ if (context.User is not IGuildUser guildUser)
+ return PreconditionResult.FromError(NotAGuildErrorMessage);
+
+ var hieararchy = PermissionUtils.GetHieararchy(value);
+ if (hieararchy >= guildUser.Hierarchy ||
+ hieararchy >= (await context.Guild.GetCurrentUserAsync().ConfigureAwait(false)).Hierarchy)
+ return PreconditionResult.FromError(ErrorMessage);
+
+ return PreconditionResult.FromSuccess();
+ }
+ }
+}