Skip to content

Commit

Permalink
[Feature] Parameter precondition attribute for simplifying performing…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
zobweyt committed May 11, 2024
1 parent 476ec06 commit 1a5cba8
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/guides/int_framework/preconditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ to use.
* @Discord.Interactions.RequireNsfwAttribute
* @Discord.Interactions.RequireRoleAttribute
* @Discord.Interactions.RequireTeamAttribute
* @Discord.Interactions.DoHierarchyCheckAttribute

## Using Preconditions

Expand Down
1 change: 1 addition & 0 deletions docs/guides/text_commands/preconditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ to use.
* @Discord.Commands.RequireBotPermissionAttribute
* @Discord.Commands.RequireUserPermissionAttribute
* @Discord.Commands.RequireNsfwAttribute
* @Discord.Commands.DoHierarchyCheckAttribute

## Using Preconditions

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;

namespace Discord.Commands
{
/// <summary>
/// Ensures that command parameters are passed within a correct hierarchical context.
/// </summary>
/// <remarks>
/// Useful for performing hierarchical operations within a guild, such as managing roles or users.
/// <note type="warning">
/// This supports <see cref="IRole"/>, <see cref="IGuildUser"/>, and <see cref="IUser"/> parameter types.
/// </note>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the parameter type is not supported by this precondition attribute.
/// </exception>
/// <seealso cref="RequireBotPermissionAttribute"/>
/// <seealso cref="RequireUserPermissionAttribute"/>
public class DoHierarchyCheckAttribute : ParameterPreconditionAttribute
{
/// <summary>
/// Gets or sets the error message displayed when the command is used outside of a guild.
/// </summary>
public string NotAGuildErrorMessage { get; set; } = "This command cannot be used outside of a guild.";

/// <summary>
/// Gets the error message to be returned if execution context doesn't pass the precondition check.
/// </summary>
public string ErrorMessage { get; set; } = "You cannot target anyone who is higher or equal in the hierarchy to you or the bot.";

/// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the parameter type is not supported by this precondition attribute.
/// </exception>
public override async Task<PreconditionResult> 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();
}
}
}
31 changes: 31 additions & 0 deletions src/Discord.Net.Core/Utils/PermissionUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;

namespace Discord
{
/// <summary>
/// Provides a series of helper methods for permissions.
/// </summary>
public static class PermissionUtils
{
/// <summary>
/// Determines the hierarchy of a target object based on its type.
/// </summary>
/// <param name="target">
/// The target object: <see cref="IRole"/>, <see cref="IGuildUser"/>, or <see cref="IUser"/>.
/// </param>
/// <returns>
/// An integer representing the hierarchy of the target.
/// </returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the parameter type is not supported by this precondition attribute.
/// </exception>
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.")
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;

namespace Discord.Interactions
{
/// <summary>
/// Ensures that command parameters are passed within a correct hierarchical context.
/// </summary>
/// <remarks>
/// Useful for performing hierarchical operations within a guild, such as managing roles or users.
/// <note type="warning">
/// This supports <see cref="IRole"/>, <see cref="IGuildUser"/>, and <see cref="IUser"/> parameter types.
/// </note>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the parameter type is not supported by this precondition attribute.
/// </exception>
/// <seealso cref="RequireRoleAttribute"/>
/// <seealso cref="RequireBotPermissionAttribute"/>
/// <seealso cref="RequireUserPermissionAttribute"/>
public class DoHierarchyCheckAttribute : ParameterPreconditionAttribute
{
/// <summary>
/// Gets or sets the error message displayed when the command is used outside of a guild.
/// </summary>
public string NotAGuildErrorMessage { get; set; } = "This command cannot be used outside of a guild.";

/// <inheritdoc />
public override string ErrorMessage => "You cannot target anyone who is higher or equal in the hierarchy to you or the bot.";

/// <inheritdoc />
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the parameter type is not supported by this precondition attribute.
/// </exception>
public override async Task<PreconditionResult> 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();
}
}
}

0 comments on commit 1a5cba8

Please sign in to comment.