diff --git a/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs b/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs
index 6fa659c4b..688c52d88 100644
--- a/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs
@@ -1,41 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Represents a base for all command pre-execution check attributes.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public abstract class CheckBaseAttribute : Attribute
{
///
/// Asynchronously checks whether this command can be executed within given context.
///
/// Context to check execution ability for.
/// Whether this check is being executed from help or not. This can be used to probe whether command can be run without setting off certain fail conditions (such as cooldowns).
/// Whether the command can be executed in given context.
public abstract Task ExecuteCheckAsync(CommandContext ctx, bool help);
+
+ ///
+ /// Asynchronously checks whether this command can be executed within given context.
+ ///
+ /// Context to check execution ability for.
+ /// Whether this check is being executed from help or not. This can be used to probe whether command can be run without setting off certain fail conditions (such as cooldowns).
+ /// Whether the command can be executed in given context.
+ public abstract Task ExecuteCheckAsync(HybridCommandContext ctx, bool help);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs
index 5ffaa467a..686073aa0 100644
--- a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs
@@ -1,332 +1,378 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Concurrent;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class CooldownAttribute : CheckBaseAttribute
{
///
/// Gets the maximum number of uses before this command triggers a cooldown for its bucket.
///
public int MaxUses { get; }
///
/// Gets the time after which the cooldown is reset.
///
public TimeSpan Reset { get; }
///
/// Gets the type of the cooldown bucket. This determines how cooldowns are applied.
///
public CooldownBucketType BucketType { get; }
///
/// Gets the cooldown buckets for this command.
///
private readonly ConcurrentDictionary _buckets;
///
/// Defines a cooldown for this command. This means that users will be able to use the command a specific number of times before they have to wait to use it again.
///
/// Number of times the command can be used before triggering a cooldown.
/// Number of seconds after which the cooldown is reset.
/// Type of cooldown bucket. This allows controlling whether the bucket will be cooled down per user, guild, channel, or globally.
public CooldownAttribute(int maxUses, double resetAfter, CooldownBucketType bucketType)
{
this.MaxUses = maxUses;
this.Reset = TimeSpan.FromSeconds(resetAfter);
this.BucketType = bucketType;
this._buckets = new ConcurrentDictionary();
}
///
/// Gets a cooldown bucket for given command context.
///
/// Command context to get cooldown bucket for.
/// Requested cooldown bucket, or null if one wasn't present.
public CommandCooldownBucket GetBucket(CommandContext ctx)
{
var bid = this.GetBucketId(ctx, out _, out _, out _);
this._buckets.TryGetValue(bid, out var bucket);
return bucket;
}
///
/// Calculates the cooldown remaining for given command context.
///
/// Context for which to calculate the cooldown.
/// Remaining cooldown, or zero if no cooldown is active.
public TimeSpan GetRemainingCooldown(CommandContext ctx)
{
var bucket = this.GetBucket(ctx);
return bucket == null ? TimeSpan.Zero : bucket.RemainingUses > 0 ? TimeSpan.Zero : bucket.ResetsAt - DateTimeOffset.UtcNow;
}
///
/// Calculates bucket ID for given command context.
///
/// Context for which to calculate bucket ID for.
/// ID of the user with which this bucket is associated.
/// ID of the channel with which this bucket is associated.
/// ID of the guild with which this bucket is associated.
/// Calculated bucket ID.
private string GetBucketId(CommandContext ctx, out ulong userId, out ulong channelId, out ulong guildId)
{
userId = 0ul;
if ((this.BucketType & CooldownBucketType.User) != 0)
userId = ctx.User.Id;
channelId = 0ul;
if ((this.BucketType & CooldownBucketType.Channel) != 0)
channelId = ctx.Channel.Id;
if ((this.BucketType & CooldownBucketType.Guild) != 0 && ctx.Guild == null)
channelId = ctx.Channel.Id;
guildId = 0ul;
if (ctx.Guild != null && (this.BucketType & CooldownBucketType.Guild) != 0)
guildId = ctx.Guild.Id;
var bid = CommandCooldownBucket.MakeId(userId, channelId, guildId);
return bid;
}
+ ///
+ /// Calculates bucket ID for given command context.
+ ///
+ /// Context for which to calculate bucket ID for.
+ /// ID of the user with which this bucket is associated.
+ /// ID of the channel with which this bucket is associated.
+ /// ID of the guild with which this bucket is associated.
+ /// Calculated bucket ID.
+ private string GetBucketId(HybridCommandContext ctx, out ulong userId, out ulong channelId, out ulong guildId)
+ {
+ userId = 0ul;
+ if ((this.BucketType & CooldownBucketType.User) != 0)
+ userId = ctx.User.Id;
+
+ channelId = 0ul;
+ if ((this.BucketType & CooldownBucketType.Channel) != 0)
+ channelId = ctx.Channel.Id;
+ if ((this.BucketType & CooldownBucketType.Guild) != 0 && ctx.Guild == null)
+ channelId = ctx.Channel.Id;
+
+ guildId = 0ul;
+ if (ctx.Guild != null && (this.BucketType & CooldownBucketType.Guild) != 0)
+ guildId = ctx.Guild.Id;
+
+ var bid = CommandCooldownBucket.MakeId(userId, channelId, guildId);
+ return bid;
+ }
+
///
/// Executes a check.
///
/// The command context.
/// If true, help - returns true.
public override async Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (help)
return true;
var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld);
if (!this._buckets.TryGetValue(bid, out var bucket))
{
bucket = new CommandCooldownBucket(this.MaxUses, this.Reset, usr, chn, gld);
this._buckets.AddOrUpdate(bid, bucket, (k, v) => bucket);
}
return await bucket.DecrementUseAsync().ConfigureAwait(false);
}
+
+ public override async Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ if (help)
+ return true;
+
+ var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld);
+ if (!this._buckets.TryGetValue(bid, out var bucket))
+ {
+ bucket = new CommandCooldownBucket(this.MaxUses, this.Reset, usr, chn, gld);
+ this._buckets.AddOrUpdate(bid, bucket, (k, v) => bucket);
+ }
+
+ return await bucket.DecrementUseAsync().ConfigureAwait(false);
+ }
+
}
///
/// Defines how are command cooldowns applied.
///
public enum CooldownBucketType : int
{
///
/// Denotes that the command will have its cooldown applied per-user.
///
User = 1,
///
/// Denotes that the command will have its cooldown applied per-channel.
///
Channel = 2,
///
/// Denotes that the command will have its cooldown applied per-guild. In DMs, this applies the cooldown per-channel.
///
Guild = 4,
///
/// Denotes that the command will have its cooldown applied globally.
///
Global = 0
}
///
/// Represents a cooldown bucket for commands.
///
public sealed class CommandCooldownBucket : IEquatable
{
///
/// Gets the ID of the user with whom this cooldown is associated.
///
public ulong UserId { get; }
///
/// Gets the ID of the channel with which this cooldown is associated.
///
public ulong ChannelId { get; }
///
/// Gets the ID of the guild with which this cooldown is associated.
///
public ulong GuildId { get; }
///
/// Gets the ID of the bucket. This is used to distinguish between cooldown buckets.
///
public string BucketId { get; }
///
/// Gets the remaining number of uses before the cooldown is triggered.
///
public int RemainingUses
=> Volatile.Read(ref this._remainingUses);
private int _remainingUses;
///
/// Gets the maximum number of times this command can be used in given timespan.
///
public int MaxUses { get; }
///
/// Gets the date and time at which the cooldown resets.
///
public DateTimeOffset ResetsAt { get; internal set; }
///
/// Gets the time after which this cooldown resets.
///
public TimeSpan Reset { get; internal set; }
///
/// Gets the semaphore used to lock the use value.
///
private readonly SemaphoreSlim _usageSemaphore;
///
/// Creates a new command cooldown bucket.
///
/// Maximum number of uses for this bucket.
/// Time after which this bucket resets.
/// ID of the user with which this cooldown is associated.
/// ID of the channel with which this cooldown is associated.
/// ID of the guild with which this cooldown is associated.
internal CommandCooldownBucket(int maxUses, TimeSpan resetAfter, ulong userId = 0, ulong channelId = 0, ulong guildId = 0)
{
this._remainingUses = maxUses;
this.MaxUses = maxUses;
this.ResetsAt = DateTimeOffset.UtcNow + resetAfter;
this.Reset = resetAfter;
this.UserId = userId;
this.ChannelId = channelId;
this.GuildId = guildId;
this.BucketId = MakeId(userId, channelId, guildId);
this._usageSemaphore = new SemaphoreSlim(1, 1);
}
///
/// Decrements the remaining use counter.
///
/// Whether decrement succeeded or not.
internal async Task DecrementUseAsync()
{
await this._usageSemaphore.WaitAsync().ConfigureAwait(false);
// if we're past reset time...
var now = DateTimeOffset.UtcNow;
if (now >= this.ResetsAt)
{
// ...do the reset and set a new reset time
Interlocked.Exchange(ref this._remainingUses, this.MaxUses);
this.ResetsAt = now + this.Reset;
}
// check if we have any uses left, if we do...
var success = false;
if (this.RemainingUses > 0)
{
// ...decrement, and return success...
Interlocked.Decrement(ref this._remainingUses);
success = true;
}
// ...otherwise just fail
this._usageSemaphore.Release();
return success;
}
///
/// Returns a string representation of this command cooldown bucket.
///
/// String representation of this command cooldown bucket.
public override string ToString() => $"Command bucket {this.BucketId}";
///
/// Checks whether this is equal to another object.
///
/// Object to compare to.
/// Whether the object is equal to this .
public override bool Equals(object obj) => this.Equals(obj as CommandCooldownBucket);
///
/// Checks whether this is equal to another .
///
/// to compare to.
/// Whether the is equal to this .
public bool Equals(CommandCooldownBucket other) => other is not null && (ReferenceEquals(this, other) || (this.UserId == other.UserId && this.ChannelId == other.ChannelId && this.GuildId == other.GuildId));
///
/// Gets the hash code for this .
///
/// The hash code for this .
public override int GetHashCode() => HashCode.Combine(this.UserId, this.ChannelId, this.GuildId);
///
/// Gets whether the two objects are equal.
///
/// First bucket to compare.
/// Second bucket to compare.
/// Whether the two buckets are equal.
public static bool operator ==(CommandCooldownBucket bucket1, CommandCooldownBucket bucket2)
{
var null1 = bucket1 is null;
var null2 = bucket2 is null;
return (null1 && null2) || (null1 == null2 && null1.Equals(null2));
}
///
/// Gets whether the two objects are not equal.
///
/// First bucket to compare.
/// Second bucket to compare.
/// Whether the two buckets are not equal.
public static bool operator !=(CommandCooldownBucket bucket1, CommandCooldownBucket bucket2)
=> !(bucket1 == bucket2);
///
/// Creates a bucket ID from given bucket parameters.
///
/// ID of the user with which this cooldown is associated.
/// ID of the channel with which this cooldown is associated.
/// ID of the guild with which this cooldown is associated.
/// Generated bucket ID.
public static string MakeId(ulong userId = 0, ulong channelId = 0, ulong guildId = 0)
=> $"{userId.ToString(CultureInfo.InvariantCulture)}:{channelId.ToString(CultureInfo.InvariantCulture)}:{guildId.ToString(CultureInfo.InvariantCulture)}";
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs
index a4f50571a..261600c3e 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs
@@ -1,83 +1,99 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to boosters.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireBoostingAttribute : CheckBaseAttribute
{
///
/// Gets the required boost time.
///
public int Since { get; }
///
/// Gets the required guild.
///
public ulong GuildId { get; }
///
/// Initializes a new instance of the class.
///
/// Boosting since days.
public RequireBoostingAttribute(int days = 0)
{
this.GuildId = 0;
this.Since = days;
}
///
/// Initializes a new instance of the class.
///
/// Target guild id.
/// Boosting since days.
public RequireBoostingAttribute(ulong guildId, int days = 0)
{
this.GuildId = guildId;
this.Since = days;
}
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override async Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (this.GuildId != 0)
{
var guild = await ctx.Client.GetGuildAsync(this.GuildId);
var member = await guild.GetMemberAsync(ctx.User.Id);
return member != null && member.PremiumSince.HasValue ? await Task.FromResult(member.PremiumSince.Value.UtcDateTime.Date < DateTime.UtcNow.Date.AddDays(-this.Since)) : await Task.FromResult(false);
}
else
{
return ctx.Member != null && ctx.Member.PremiumSince.HasValue ? await Task.FromResult(ctx.Member.PremiumSince.Value.UtcDateTime.Date < DateTime.UtcNow.Date.AddDays(-this.Since)) : await Task.FromResult(false);
}
}
+
+ public override async Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ if (this.GuildId != 0)
+ {
+ var guild = await ctx.Client.GetGuildAsync(this.GuildId);
+ var member = await guild.GetMemberAsync(ctx.User.Id);
+ return member != null && member.PremiumSince.HasValue ? await Task.FromResult(member.PremiumSince.Value.UtcDateTime.Date < DateTime.UtcNow.Date.AddDays(-this.Since)) : await Task.FromResult(false);
+ }
+ else
+ {
+ return ctx.Member != null && ctx.Member.PremiumSince.HasValue ? await Task.FromResult(ctx.Member.PremiumSince.Value.UtcDateTime.Date < DateTime.UtcNow.Date.AddDays(-this.Since)) : await Task.FromResult(false);
+ }
+ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireBotPermissionsAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireBotPermissionsAttribute.cs
index 17a13c233..af94551d6 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireBotPermissionsAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireBotPermissionsAttribute.cs
@@ -1,83 +1,106 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Enums;
+using DisCatSharp.HybridCommands.Entities;
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is only possible when the bot is granted a specific permission.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireBotPermissionsAttribute : CheckBaseAttribute
{
///
/// Gets the permissions required by this attribute.
///
public Permissions Permissions { get; }
///
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
///
public bool IgnoreDms { get; } = true;
///
/// Defines that usage of this command is only possible when the bot is granted a specific permission.
///
/// Permissions required to execute this command.
/// Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
public RequireBotPermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override async Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null)
return this.IgnoreDms;
var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
if (bot == null)
return false;
if (bot.Id == ctx.Guild.OwnerId)
return true;
var channel = ctx.Channel;
if (ctx.Channel.GuildId == null)
{
channel = await ctx.Client.GetChannelAsync(ctx.Channel.Id, true);
}
var pbot = channel.PermissionsFor(bot);
return (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
}
+
+ public override async Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ if (ctx.Guild == null)
+ return this.IgnoreDms;
+
+ var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
+ if (bot == null)
+ return false;
+
+ if (bot.Id == ctx.Guild.OwnerId)
+ return true;
+
+ var channel = ctx.Channel;
+ if (ctx.Channel.GuildId == null)
+ {
+ channel = await ctx.Client.GetChannelAsync(ctx.Channel.Id, true);
+ }
+ var pbot = channel.PermissionsFor(bot);
+
+ return (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
+ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
index bbeee7cd3..ed21f6450 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
@@ -1,42 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Enums;
+using DisCatSharp.HybridCommands.Entities;
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to discord certified moderators.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireCertifiedModeratorAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.CertifiedModerator)) : Task.FromResult(false);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.CertifiedModerator)) : Task.FromResult(false);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs
index 5a0a0f76f..189a60ac0 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs
@@ -1,47 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that a command is only usable within a community-enabled guild.
///
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireCommunityAttribute : CheckBaseAttribute
{
///
/// Defines that this command is only usable within a community-enabled guild.
///
public RequireCommunityAttribute()
{ }
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.IsCommunity);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.IsCommunity);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs
index 97c00a2de..1efc42699 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs
@@ -1,50 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Entities;
+using DisCatSharp.HybridCommands.Entities;
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that a command is only usable within a direct message channel.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireDirectMessageAttribute : CheckBaseAttribute
{
///
/// Defines that this command is only usable within a direct message channel.
///
public RequireDirectMessageAttribute()
{ }
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(ctx.Channel is DiscordDmChannel);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ => Task.FromResult(ctx.Channel is DiscordDmChannel);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
index a9c31bc99..b6c9f6460 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
@@ -1,41 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to DisCatSharp Developers.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireDisCatSharpDeveloperAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(true);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ => Task.FromResult(true);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs
index da9146b5c..b592015a6 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs
@@ -1,45 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that a command is only usable within a guild.
///
public sealed class RequireGuildAttribute : CheckBaseAttribute
{
///
/// Defines that this command is only usable within a guild.
///
public RequireGuildAttribute()
{ }
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(ctx.Guild != null);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ => Task.FromResult(ctx.Guild != null);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs
index 9c7743596..85444941d 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs
@@ -1,53 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to the guild owner.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireGuildOwnerAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override async Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
var guild = await Task.FromResult(ctx.Guild != null);
if (guild)
{
var owner = await Task.FromResult(ctx.Member == ctx.Guild.Owner);
return owner;
}
else
{
return false;
}
}
+
+ public async override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ var guild = await Task.FromResult(ctx.Guild != null);
+ if (guild)
+ {
+ var owner = await Task.FromResult(ctx.Member == ctx.Guild.Owner);
+
+ return owner;
+ }
+ else
+ {
+ return false;
+ }
+ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs
index 49c265bf0..158474b42 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs
@@ -1,44 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that a command is only usable within a guild which has enabled the member verification gate.
///
public sealed class RequireMemberVerificationGateAttribute : CheckBaseAttribute
{
///
/// Defines that this command is only usable within guild which has enabled the member verification gate.
///
public RequireMemberVerificationGateAttribute()
{ }
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.HasMemberVerificationGate);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.HasMemberVerificationGate);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs
index 2e5bb224e..8200a881f 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs
@@ -1,41 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to NSFW channels.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireNsfwAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(ctx.Channel.Guild == null || ctx.Channel.IsNsfw);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ => Task.FromResult(ctx.Channel.Guild == null || ctx.Channel.IsNsfw);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs
index fd6d6231b..be4408c2f 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs
@@ -1,47 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Linq;
using System.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to the owner of the bot.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireOwnerAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
var app = ctx.Client.CurrentApplication;
var me = ctx.Client.CurrentUser;
return app != null ? Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : Task.FromResult(ctx.User.Id == me.Id);
}
+
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ var app = ctx.Client.CurrentApplication;
+ var me = ctx.Client.CurrentUser;
+
+ return app != null ? Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : Task.FromResult(ctx.User.Id == me.Id);
+ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs
index e2b081423..a3120a4be 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs
@@ -1,68 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Requires ownership of the bot or a whitelisted id to execute this command.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireOwnerOrIdAttribute : CheckBaseAttribute
{
///
/// Allowed user ids
///
public IReadOnlyList UserIds { get; }
///
/// Defines that usage of this command is restricted to the owner or whitelisted ids of the bot.
///
/// List of allowed user ids
public RequireOwnerOrIdAttribute(params ulong[] userIds)
{
this.UserIds = new ReadOnlyCollection(userIds);
}
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override async Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
var app = ctx.Client.CurrentApplication;
var me = ctx.Client.CurrentUser;
var owner = app != null ? await Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : await Task.FromResult(ctx.User.Id == me.Id);
var allowed = this.UserIds.Contains(ctx.User.Id);
return owner || allowed;
}
+
+ public override async Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ var app = ctx.Client.CurrentApplication;
+ var me = ctx.Client.CurrentUser;
+
+ var owner = app != null ? await Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : await Task.FromResult(ctx.User.Id == me.Id);
+
+ var allowed = this.UserIds.Contains(ctx.User.Id);
+
+ return owner || allowed;
+
+ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequirePermissionsAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequirePermissionsAttribute.cs
index e5eac535c..112f297e0 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequirePermissionsAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequirePermissionsAttribute.cs
@@ -1,94 +1,128 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Enums;
+using DisCatSharp.HybridCommands.Entities;
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to members with specified permissions. This check also verifies that the bot has the same permissions.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequirePermissionsAttribute : CheckBaseAttribute
{
///
/// Gets the permissions required by this attribute.
///
public Permissions Permissions { get; }
///
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
///
public bool IgnoreDms { get; } = true;
///
/// Defines that usage of this command is restricted to members with specified permissions. This check also verifies that the bot has the same permissions.
///
/// Permissions required to execute this command.
/// Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
public RequirePermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override async Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null)
return this.IgnoreDms;
var channel = ctx.Channel;
if (ctx.Channel.GuildId == null)
{
channel = await ctx.Client.GetChannelAsync(ctx.Channel.Id, true);
}
var usr = ctx.Member;
if (usr == null)
return false;
var pusr = channel.PermissionsFor(usr);
var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
if (bot == null)
return false;
var pbot = channel.PermissionsFor(bot);
var usrok = ctx.Guild.OwnerId == usr.Id;
var botok = ctx.Guild.OwnerId == bot.Id;
if (!usrok)
usrok = (pusr & Permissions.Administrator) != 0 || (pusr & this.Permissions) == this.Permissions;
if (!botok)
botok = (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
return usrok && botok;
}
+
+ public override async Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ if (ctx.Guild == null)
+ return this.IgnoreDms;
+
+ var channel = ctx.Channel;
+ if (ctx.Channel.GuildId == null)
+ {
+ channel = await ctx.Client.GetChannelAsync(ctx.Channel.Id, true);
+ }
+
+ var usr = ctx.Member;
+ if (usr == null)
+ return false;
+ var pusr = channel.PermissionsFor(usr);
+
+ var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
+ if (bot == null)
+ return false;
+ var pbot = channel.PermissionsFor(bot);
+
+ var usrok = ctx.Guild.OwnerId == usr.Id;
+ var botok = ctx.Guild.OwnerId == bot.Id;
+
+ if (!usrok)
+ usrok = (pusr & Permissions.Administrator) != 0 || (pusr & this.Permissions) == this.Permissions;
+
+ if (!botok)
+ botok = (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
+
+ return usrok && botok;
+ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs
index 30cb8dc33..6ed3a2a3a 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs
@@ -1,65 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Linq;
using System.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is only allowed with specific prefixes.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)]
public sealed class RequirePrefixesAttribute : CheckBaseAttribute
{
///
/// Gets the array of prefixes with which execution of this command is allowed.
///
public string[] Prefixes { get; }
///
/// Gets or sets default help behaviour for this check. When this is enabled, invoking help without matching prefix will show the commands.
/// Defaults to false.
///
public bool ShowInHelp { get; set; } = false;
///
/// Defines that usage of this command is only allowed with specific prefixes.
///
/// Prefixes with which the execution of this command is allowed.
public RequirePrefixesAttribute(params string[] prefixes)
{
if (prefixes?.Any() != true)
throw new ArgumentNullException(nameof(prefixes), "The allowed prefix collection cannot be null or empty.");
this.Prefixes = prefixes;
}
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult((help && this.ShowInHelp) || this.Prefixes.Contains(ctx.Prefix, ctx.CommandsNext.GetStringComparer()));
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ => Task.FromResult((help && this.ShowInHelp) || this.Prefixes.Contains(ctx.Prefix, ctx.Client.GetCommandsNext().GetStringComparer()));
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs
index 15eb210a7..5f079f4fc 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs
@@ -1,40 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that a command is only usable when sent in reply. Command will appear in help regardless of this attribute.
///
public sealed class RequireReferencedMessageAttribute : CheckBaseAttribute
{
///
/// Defines that a command is only usable when sent in reply. Command will appear in help regardless of this attribute.
///
public RequireReferencedMessageAttribute()
{ }
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(help || ctx.Message.ReferencedMessage != null);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ => Task.FromResult(help || ctx.Message.ReferencedMessage != null);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs
index 704ac3655..dfcc870bd 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs
@@ -1,107 +1,128 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to members with specified role. Note that it's much preferred to restrict access using .
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireRolesAttribute : CheckBaseAttribute
{
///
/// Gets the name of the role required to execute this command.
///
public IReadOnlyList RoleNames { get; }
///
/// Gets the role checking mode. Refer to for more information.
///
public RoleCheckMode CheckMode { get; }
///
/// Defines that usage of this command is restricted to members with specified role. Note that it's much preferred to restrict access using .
///
/// Role checking mode.
/// Names of the role to be verified by this check.
public RequireRolesAttribute(RoleCheckMode checkMode, params string[] roleNames)
{
this.CheckMode = checkMode;
this.RoleNames = new ReadOnlyCollection(roleNames);
}
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null || ctx.Member == null)
return Task.FromResult(false);
var rns = ctx.Member.Roles.Select(xr => xr.Name);
var rnc = rns.Count();
var ins = rns.Intersect(this.RoleNames, ctx.CommandsNext.GetStringComparer());
var inc = ins.Count();
return this.CheckMode switch
{
RoleCheckMode.All => Task.FromResult(this.RoleNames.Count == inc),
RoleCheckMode.SpecifiedOnly => Task.FromResult(rnc == inc),
RoleCheckMode.None => Task.FromResult(inc == 0),
_ => Task.FromResult(inc > 0),
};
}
+
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ if (ctx.Guild == null || ctx.Member == null)
+ return Task.FromResult(false);
+
+ var rns = ctx.Member.Roles.Select(xr => xr.Name);
+ var rnc = rns.Count();
+ var ins = rns.Intersect(this.RoleNames, ctx.Client.GetCommandsNext().GetStringComparer());
+ var inc = ins.Count();
+
+ return this.CheckMode switch
+ {
+ RoleCheckMode.All => Task.FromResult(this.RoleNames.Count == inc),
+ RoleCheckMode.SpecifiedOnly => Task.FromResult(rnc == inc),
+ RoleCheckMode.None => Task.FromResult(inc == 0),
+ _ => Task.FromResult(inc > 0),
+ };
+ }
}
///
/// Specifies how does check for roles.
///
public enum RoleCheckMode
{
///
/// Member is required to have any of the specified roles.
///
Any,
///
/// Member is required to have all of the specified roles.
///
All,
///
/// Member is required to have exactly the same roles as specified; no extra roles may be present.
///
SpecifiedOnly,
///
/// Member is required to have none of the specified roles.
///
None
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
index 9f1994d4f..fdd555ed0 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
@@ -1,42 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Enums;
+using DisCatSharp.HybridCommands.Entities;
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to discord employees.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireStaffAttribute : CheckBaseAttribute
{
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.Staff)) : Task.FromResult(false);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.Staff)) : Task.FromResult(false);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs
index 442540ce0..e8ca68a56 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs
@@ -1,82 +1,103 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Enums;
+using DisCatSharp.HybridCommands.Entities;
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that usage of this command is restricted to members with specified permissions.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireUserPermissionsAttribute : CheckBaseAttribute
{
///
/// Gets the permissions required by this attribute.
///
public Permissions Permissions { get; }
///
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
///
public bool IgnoreDms { get; } = true;
///
/// Defines that usage of this command is restricted to members with specified permissions.
///
/// Permissions required to execute this command.
/// Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
public RequireUserPermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
///
/// Executes the a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null)
return Task.FromResult(this.IgnoreDms);
var usr = ctx.Member;
if (usr == null)
return Task.FromResult(false);
if (usr.Id == ctx.Guild.OwnerId)
return Task.FromResult(true);
var pusr = ctx.Channel.PermissionsFor(usr);
if ((pusr & Permissions.Administrator) != 0)
return Task.FromResult(true);
return (pusr & this.Permissions) == this.Permissions ? Task.FromResult(true) : Task.FromResult(false);
}
+
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help)
+ {
+ if (ctx.Guild == null)
+ return Task.FromResult(this.IgnoreDms);
+
+ var usr = ctx.Member;
+ if (usr == null)
+ return Task.FromResult(false);
+
+ if (usr.Id == ctx.Guild.OwnerId)
+ return Task.FromResult(true);
+
+ var pusr = ctx.Channel.PermissionsFor(usr);
+
+ if ((pusr & Permissions.Administrator) != 0)
+ return Task.FromResult(true);
+
+ return (pusr & this.Permissions) == this.Permissions ? Task.FromResult(true) : Task.FromResult(false);
+ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs
index ac3ceebfd..90ea4634e 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs
@@ -1,46 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext.Attributes;
///
/// Defines that a command is only usable within a guild which has enabled the welcome screen.
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireWelcomeScreenAttribute : CheckBaseAttribute
{
///
/// Defines that this command is only usable within a guild which has enabled the welcome screen.
///
public RequireWelcomeScreenAttribute()
{ }
///
/// Executes a check.
///
/// The command context.
/// If true, help - returns true.
public override Task ExecuteCheckAsync(CommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.HasWelcomeScreen);
+ public override Task ExecuteCheckAsync(HybridCommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.HasWelcomeScreen);
}
diff --git a/DisCatSharp.CommandsNext/BaseCommandModule.cs b/DisCatSharp.CommandsNext/BaseCommandModule.cs
index 6465ce13b..f56475f95 100644
--- a/DisCatSharp.CommandsNext/BaseCommandModule.cs
+++ b/DisCatSharp.CommandsNext/BaseCommandModule.cs
@@ -1,47 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Threading.Tasks;
+using DisCatSharp.HybridCommands.Entities;
+
namespace DisCatSharp.CommandsNext;
///
/// Represents a base class for all command modules.
///
public abstract class BaseCommandModule
{
///
/// Called before a command in the implementing module is executed.
///
/// Context in which the method is being executed.
///
public virtual Task BeforeExecutionAsync(CommandContext ctx)
=> Task.Delay(0);
///
/// Called after a command in the implementing module is successfully executed.
///
/// Context in which the method is being executed.
///
public virtual Task AfterExecutionAsync(CommandContext ctx)
=> Task.Delay(0);
+
+ ///
+ /// Called before a command in the implementing module is executed.
+ ///
+ /// Context in which the method is being executed.
+ ///
+ public virtual Task BeforeExecutionAsync(HybridCommandContext ctx)
+ => Task.Delay(0);
+
+ ///
+ /// Called after a command in the implementing module is successfully executed.
+ ///
+ /// Context in which the method is being executed.
+ ///
+ public virtual Task AfterExecutionAsync(HybridCommandContext ctx)
+ => Task.Delay(0);
}
diff --git a/DisCatSharp.CommandsNext/CommandsNextExtension.cs b/DisCatSharp.CommandsNext/CommandsNextExtension.cs
index 9a03591a6..246a82e79 100644
--- a/DisCatSharp.CommandsNext/CommandsNextExtension.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextExtension.cs
@@ -1,1086 +1,1213 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2023 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.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using DisCatSharp.CommandsNext.Attributes;
using DisCatSharp.CommandsNext.Builders;
using DisCatSharp.CommandsNext.Converters;
using DisCatSharp.CommandsNext.Entities;
using DisCatSharp.CommandsNext.Exceptions;
using DisCatSharp.Common.Utilities;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
+using DisCatSharp.HybridCommands.Entities;
+using DisCatSharp.HybridCommands.Enums;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.CommandsNext;
///
/// This is the class which handles command registration, management, and execution.
///
public class CommandsNextExtension : BaseExtension
{
///
/// Gets the config.
///
- private readonly CommandsNextConfiguration _config;
+ internal readonly CommandsNextConfiguration _config;
///
/// Gets the help formatter.
///
private readonly HelpFormatterFactory _helpFormatter;
///
/// Gets the convert generic.
///
private readonly MethodInfo _convertGeneric;
///
/// Gets the user friendly type names.
///
private readonly Dictionary _userFriendlyTypeNames;
///
/// Gets the argument converters.
///
internal Dictionary ArgumentConverters { get; }
///
/// Gets the service provider this CommandsNext module was configured with.
///
public IServiceProvider Services
=> this._config.ServiceProvider;
///
/// Initializes a new instance of the class.
///
/// The cfg.
internal CommandsNextExtension(CommandsNextConfiguration cfg)
{
this._config = new CommandsNextConfiguration(cfg);
this._topLevelCommands = new Dictionary();
this._registeredCommandsLazy = new Lazy>(() => new ReadOnlyDictionary(this._topLevelCommands));
this._helpFormatter = new HelpFormatterFactory();
this._helpFormatter.SetFormatterType();
this.ArgumentConverters = new Dictionary
{
[typeof(string)] = new StringConverter(),
[typeof(bool)] = new BoolConverter(),
[typeof(sbyte)] = new Int8Converter(),
[typeof(byte)] = new Uint8Converter(),
[typeof(short)] = new Int16Converter(),
[typeof(ushort)] = new Uint16Converter(),
[typeof(int)] = new Int32Converter(),
[typeof(uint)] = new Uint32Converter(),
[typeof(long)] = new Int64Converter(),
[typeof(ulong)] = new Uint64Converter(),
[typeof(float)] = new Float32Converter(),
[typeof(double)] = new Float64Converter(),
[typeof(decimal)] = new Float128Converter(),
[typeof(DateTime)] = new DateTimeConverter(),
[typeof(DateTimeOffset)] = new DateTimeOffsetConverter(),
[typeof(TimeSpan)] = new TimeSpanConverter(),
[typeof(Uri)] = new UriConverter(),
[typeof(DiscordUser)] = new DiscordUserConverter(),
[typeof(DiscordMember)] = new DiscordMemberConverter(),
[typeof(DiscordRole)] = new DiscordRoleConverter(),
[typeof(DiscordChannel)] = new DiscordChannelConverter(),
[typeof(DiscordGuild)] = new DiscordGuildConverter(),
[typeof(DiscordMessage)] = new DiscordMessageConverter(),
[typeof(DiscordEmoji)] = new DiscordEmojiConverter(),
[typeof(DiscordThreadChannel)] = new DiscordThreadChannelConverter(),
[typeof(DiscordInvite)] = new DiscordInviteConverter(),
[typeof(DiscordColor)] = new DiscordColorConverter(),
[typeof(DiscordScheduledEvent)] = new DiscordScheduledEventConverter(),
};
this._userFriendlyTypeNames = new Dictionary()
{
[typeof(string)] = "string",
[typeof(bool)] = "boolean",
[typeof(sbyte)] = "signed byte",
[typeof(byte)] = "byte",
[typeof(short)] = "short",
[typeof(ushort)] = "unsigned short",
[typeof(int)] = "int",
[typeof(uint)] = "unsigned int",
[typeof(long)] = "long",
[typeof(ulong)] = "unsigned long",
[typeof(float)] = "float",
[typeof(double)] = "double",
[typeof(decimal)] = "decimal",
[typeof(DateTime)] = "date and time",
[typeof(DateTimeOffset)] = "date and time",
[typeof(TimeSpan)] = "time span",
[typeof(Uri)] = "URL",
[typeof(DiscordUser)] = "user",
[typeof(DiscordMember)] = "member",
[typeof(DiscordRole)] = "role",
[typeof(DiscordChannel)] = "channel",
[typeof(DiscordGuild)] = "guild",
[typeof(DiscordMessage)] = "message",
[typeof(DiscordEmoji)] = "emoji",
[typeof(DiscordThreadChannel)] = "thread",
[typeof(DiscordInvite)] = "invite",
[typeof(DiscordColor)] = "color",
[typeof(DiscordScheduledEvent)] = "event"
};
foreach (var xt in this.ArgumentConverters.Keys.ToArray())
{
var xti = xt.GetTypeInfo();
if (!xti.IsValueType)
continue;
var xcvt = typeof(NullableConverter<>).MakeGenericType(xt);
var xnt = typeof(Nullable<>).MakeGenericType(xt);
if (this.ArgumentConverters.ContainsKey(xcvt))
continue;
var xcv = Activator.CreateInstance(xcvt) as IArgumentConverter;
this.ArgumentConverters[xnt] = xcv;
this._userFriendlyTypeNames[xnt] = this._userFriendlyTypeNames[xt];
}
var t = this.GetType();
var ms = t.GetTypeInfo().DeclaredMethods;
var m = ms.FirstOrDefault(xm => xm.Name == "ConvertArgumentToObj" && xm.ContainsGenericParameters && !xm.IsStatic && xm.IsPrivate);
this._convertGeneric = m;
}
///
/// Sets the help formatter to use with the default help command.
///
/// Type of the formatter to use.
public void SetHelpFormatter() where T : BaseHelpFormatter => this._helpFormatter.SetFormatterType();
#region DiscordClient Registration
///
/// DO NOT USE THIS MANUALLY.
///
/// DO NOT USE THIS MANUALLY.
///
protected internal override void Setup(DiscordClient client)
{
if (this.Client != null)
throw new InvalidOperationException("What did I tell you?");
this.Client = client;
this._executed = new AsyncEvent("COMMAND_EXECUTED", TimeSpan.Zero, this.Client.EventErrorHandler);
this._error = new AsyncEvent("COMMAND_ERRORED", TimeSpan.Zero, this.Client.EventErrorHandler);
if (this._config.UseDefaultCommandHandler)
this.Client.MessageCreated += this.HandleCommandsAsync;
else
this.Client.Logger.LogWarning(CommandsNextEvents.Misc, "Not attaching default command handler - if this is intentional, you can ignore this message");
if (this._config.EnableDefaultHelp)
{
this.RegisterCommands(typeof(DefaultHelpModule), null, null, out var tcmds);
if (this._config.DefaultHelpChecks != null)
{
var checks = this._config.DefaultHelpChecks.ToArray();
foreach (var cb in tcmds)
cb.WithExecutionChecks(checks);
}
if (tcmds != null)
foreach (var xc in tcmds)
this.AddToCommandDictionary(xc.Build(null));
}
}
#endregion
#region Command Handling
///
/// Handles the commands async.
///
/// The sender.
/// The e.
/// A Task.
private async Task HandleCommandsAsync(DiscordClient sender, MessageCreateEventArgs e)
{
if (e.Author.IsBot) // bad bot
return;
if (!this._config.EnableDms && e.Channel.IsPrivate)
return;
var mpos = -1;
if (this._config.EnableMentionPrefix)
mpos = e.Message.GetMentionPrefixLength(this.Client.CurrentUser);
if (this._config.StringPrefixes?.Any() == true)
foreach (var pfix in this._config.StringPrefixes)
if (mpos == -1 && !string.IsNullOrWhiteSpace(pfix))
mpos = e.Message.GetStringPrefixLength(pfix, this._config.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
if (mpos == -1 && this._config.PrefixResolver != null)
mpos = await this._config.PrefixResolver(e.Message).ConfigureAwait(false);
if (mpos == -1)
return;
var pfx = e.Message.Content[..mpos];
var cnt = e.Message.Content[mpos..];
var __ = 0;
var fname = cnt.ExtractNextArgument(ref __);
var cmd = this.FindCommand(cnt, out var args);
- var ctx = this.CreateContext(e.Message, pfx, cmd, args);
- if (cmd == null)
+ if (cmd?.IsHybrid ?? false)
{
- await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = ctx, Exception = new CommandNotFoundException(fname) }).ConfigureAwait(false);
- return;
+ var ctx = this.CreateHybridContext(e.Message, pfx, cmd);
+ if (cmd == null)
+ {
+ return;
+ }
+
+ _ = Task.Run(async () => await this.ExecuteCommandAsync(ctx, cmd, args).ConfigureAwait(false));
}
+ else
+ {
+ var ctx = this.CreateCnextContext(e.Message, pfx, cmd, args);
+ if (cmd == null)
+ {
+ await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = ctx, Exception = new CommandNotFoundException(fname) }).ConfigureAwait(false);
+ return;
+ }
- _ = Task.Run(async () => await this.ExecuteCommandAsync(ctx).ConfigureAwait(false));
+ _ = Task.Run(async () => await this.ExecuteCommandAsync(ctx).ConfigureAwait(false));
+ }
}
///
/// Finds a specified command by its qualified name, then separates arguments.
///
/// Qualified name of the command, optionally with arguments.
/// Separated arguments.
/// Found command or null if none was found.
public Command FindCommand(string commandString, out string rawArguments)
{
rawArguments = null;
var ignoreCase = !this._config.CaseSensitive;
var pos = 0;
var next = commandString.ExtractNextArgument(ref pos);
if (next == null)
return null;
if (!this.RegisteredCommands.TryGetValue(next, out var cmd))
{
if (!ignoreCase)
return null;
next = next.ToLowerInvariant();
var cmdKvp = this.RegisteredCommands.FirstOrDefault(x => x.Key.ToLowerInvariant() == next);
if (cmdKvp.Value == null)
return null;
cmd = cmdKvp.Value;
}
if (cmd is not CommandGroup)
{
rawArguments = commandString[pos..].Trim();
return cmd;
}
while (cmd is CommandGroup)
{
var cm2 = cmd as CommandGroup;
var oldPos = pos;
next = commandString.ExtractNextArgument(ref pos);
if (next == null)
break;
if (ignoreCase)
{
next = next.ToLowerInvariant();
cmd = cm2.Children.FirstOrDefault(x => x.Name.ToLowerInvariant() == next || x.Aliases?.Any(xx => xx.ToLowerInvariant() == next) == true);
}
else
{
cmd = cm2.Children.FirstOrDefault(x => x.Name == next || x.Aliases?.Contains(next) == true);
}
if (cmd == null)
{
cmd = cm2;
pos = oldPos;
break;
}
}
rawArguments = commandString[pos..].Trim();
return cmd;
}
///
/// Creates a command execution context from specified arguments.
///
/// Message to use for context.
/// Command prefix, used to execute commands.
/// Command to execute.
/// Raw arguments to pass to command.
/// Created command execution context.
- public CommandContext CreateContext(DiscordMessage msg, string prefix, Command cmd, string rawArguments = null)
+ public CommandContext CreateCnextContext(DiscordMessage msg, string prefix, Command cmd, string rawArguments = null)
{
var ctx = new CommandContext
{
Client = this.Client,
Command = cmd,
Message = msg,
Config = this._config,
RawArgumentString = rawArguments ?? "",
Prefix = prefix,
CommandsNext = this,
Services = this.Services
};
if (cmd != null && (cmd.Module is TransientCommandModule || cmd.Module == null))
{
var scope = ctx.Services.CreateScope();
ctx.ServiceScopeContext = new CommandContext.ServiceContext(ctx.Services, scope);
ctx.Services = scope.ServiceProvider;
}
return ctx;
}
+ ///
+ /// Creates a command execution context from specified arguments.
+ ///
+ /// Message to use for context.
+ /// Command prefix, used to execute commands.
+ /// Command to execute.
+ /// Raw arguments to pass to command.
+ /// Created command execution context.
+ public HybridCommandContext CreateHybridContext(DiscordMessage msg, string prefix, Command cmd)
+ {
+ var ctx = new HybridCommandContext
+ {
+ CommandType = HybridCommandType.Prefix,
+ Client = this.Client,
+ CommandName = cmd.Name,
+ Message = msg,
+ Prefix = prefix,
+ Services = this.Services
+ };
+
+ if (cmd != null && (cmd.Module is TransientCommandModule || cmd.Module == null))
+ {
+ var scope = ctx.Services.CreateScope();
+ ctx.Services = scope.ServiceProvider;
+ }
+
+ return ctx;
+ }
+
///
/// Executes specified command from given context.
///
/// Context to execute command from.
///
public async Task ExecuteCommandAsync(CommandContext ctx)
{
try
{
var cmd = ctx.Command;
await this.RunAllChecksAsync(cmd, ctx).ConfigureAwait(false);
var res = await cmd.ExecuteAsync(ctx).ConfigureAwait(false);
if (res.IsSuccessful)
- await this._executed.InvokeAsync(this, new CommandExecutionEventArgs(this.Client.ServiceProvider) { Context = res.Context }).ConfigureAwait(false);
+ await this._executed.InvokeAsync(this, new CommandExecutionEventArgs(this.Client.ServiceProvider) { Context = res.CommandContext }).ConfigureAwait(false);
else
- await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = res.Context, Exception = res.Exception }).ConfigureAwait(false);
+ await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = res.CommandContext, Exception = res.Exception }).ConfigureAwait(false);
}
catch (Exception ex)
{
await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = ctx, Exception = ex }).ConfigureAwait(false);
}
finally
{
if (ctx.ServiceScopeContext.IsInitialized)
ctx.ServiceScopeContext.Dispose();
}
}
+ ///
+ /// Executes specified command from given context.
+ ///
+ /// Context to execute command from.
+ ///
+ public async Task ExecuteCommandAsync(HybridCommandContext ctx, Command cmd, string rawArgs)
+ {
+ try
+ {
+ await this.RunAllChecksAsync(cmd, ctx).ConfigureAwait(false);
+
+ var res = await cmd.ExecuteAsync(ctx, cmd, rawArgs).ConfigureAwait(false);
+
+ // todo: handle commands stuff
+
+ //if (res.IsSuccessful)
+ // await this._executed.InvokeAsync(this, new CommandExecutionEventArgs(this.Client.ServiceProvider) { Context = res.HybridContext }).ConfigureAwait(false);
+ //else
+ // await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = res.HybridContext, Exception = res.Exception }).ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ // todo: handle exceptions
+ }
+ }
+
///
/// Runs the all checks async.
///
/// The cmd.
/// The ctx.
/// A Task.
private async Task RunAllChecksAsync(Command cmd, CommandContext ctx)
{
if (cmd.Parent != null)
await this.RunAllChecksAsync(cmd.Parent, ctx).ConfigureAwait(false);
var fchecks = await cmd.RunChecksAsync(ctx, false).ConfigureAwait(false);
if (fchecks.Any())
throw new ChecksFailedException(cmd, ctx, fchecks);
}
+
+ ///
+ /// Runs the all checks async.
+ ///
+ /// The cmd.
+ /// The ctx.
+ /// A Task.
+ private async Task RunAllChecksAsync(Command cmd, HybridCommandContext ctx)
+ {
+ if (cmd.Parent != null)
+ await this.RunAllChecksAsync(cmd.Parent, ctx).ConfigureAwait(false);
+
+ var fchecks = await cmd.RunChecksAsync(ctx, false).ConfigureAwait(false);
+ if (fchecks.Any())
+ throw new ChecksFailedException(cmd, ctx, fchecks);
+ }
#endregion
#region Command Registration
///
/// Gets a dictionary of registered top-level commands.
///
public IReadOnlyDictionary RegisteredCommands
=> this._registeredCommandsLazy.Value;
///
/// Gets or sets the top level commands.
///
private readonly Dictionary _topLevelCommands;
private readonly Lazy> _registeredCommandsLazy;
///
/// Registers all commands from a given assembly. The command classes need to be public to be considered for registration.
///
/// Assembly to register commands from.
public void RegisterCommands(Assembly assembly)
{
var types = assembly.ExportedTypes.Where(xt =>
{
var xti = xt.GetTypeInfo();
return xti.IsModuleCandidateType() && !xti.IsNested;
});
foreach (var xt in types)
this.RegisterCommands(xt);
}
///
/// Registers all commands from a given command class.
///
/// Class which holds commands to register.
public void RegisterCommands() where T : BaseCommandModule
{
var t = typeof(T);
this.RegisterCommands(t);
}
///
/// Registers all commands from a given command class.
///
/// Type of the class which holds commands to register.
public void RegisterCommands(Type t)
{
if (t == null)
throw new ArgumentNullException(nameof(t), "Type cannot be null.");
if (!t.IsModuleCandidateType())
throw new ArgumentNullException(nameof(t), "Type must be a class, which cannot be abstract or static.");
this.RegisterCommands(t, null, null, out var tempCommands);
if (tempCommands != null)
foreach (var command in tempCommands)
this.AddToCommandDictionary(command.Build(null));
}
///
/// Registers the commands.
///
/// The type.
/// The current parent.
/// The inherited checks.
/// The found commands.
private void RegisterCommands(Type t, CommandGroupBuilder currentParent, IEnumerable inheritedChecks, out List foundCommands)
{
var ti = t.GetTypeInfo();
var lifespan = ti.GetCustomAttribute();
var moduleLifespan = lifespan != null ? lifespan.Lifespan : ModuleLifespan.Singleton;
var module = new CommandModuleBuilder()
.WithType(t)
.WithLifespan(moduleLifespan)
.Build(this.Services);
// restrict parent lifespan to more or equally restrictive
if (currentParent?.Module is TransientCommandModule && moduleLifespan != ModuleLifespan.Transient)
throw new InvalidOperationException("In a transient module, child modules can only be transient.");
// check if we are anything
var groupBuilder = new CommandGroupBuilder(module);
var isModule = false;
var moduleAttributes = ti.GetCustomAttributes();
var moduleHidden = false;
var moduleChecks = new List();
foreach (var xa in moduleAttributes)
{
switch (xa)
{
case GroupAttribute g:
isModule = true;
var moduleName = g.Name;
if (moduleName == null)
{
moduleName = ti.Name;
if (moduleName.EndsWith("Group") && moduleName != "Group")
moduleName = moduleName[0..^5];
else if (moduleName.EndsWith("Module") && moduleName != "Module")
moduleName = moduleName[0..^6];
else if (moduleName.EndsWith("Commands") && moduleName != "Commands")
moduleName = moduleName[0..^8];
}
if (!this._config.CaseSensitive)
moduleName = moduleName.ToLowerInvariant();
groupBuilder.WithName(moduleName);
if (inheritedChecks != null)
foreach (var chk in inheritedChecks)
groupBuilder.WithExecutionCheck(chk);
- foreach (var mi in ti.DeclaredMethods.Where(x => x.IsCommandCandidate(out _) && x.GetCustomAttribute() != null))
+ foreach (var mi in ti.DeclaredMethods.Where(x => x.IsCommandCandidate(out _, out _) && x.GetCustomAttribute() != null))
groupBuilder.WithOverload(new CommandOverloadBuilder(mi));
break;
case AliasesAttribute a:
foreach (var xalias in a.Aliases)
groupBuilder.WithAlias(this._config.CaseSensitive ? xalias : xalias.ToLowerInvariant());
break;
case HiddenAttribute h:
groupBuilder.WithHiddenStatus(true);
moduleHidden = true;
break;
case DescriptionAttribute d:
groupBuilder.WithDescription(d.Description);
break;
case CheckBaseAttribute c:
moduleChecks.Add(c);
groupBuilder.WithExecutionCheck(c);
break;
default:
groupBuilder.WithCustomAttribute(xa);
break;
}
}
if (!isModule)
{
groupBuilder = null;
if (inheritedChecks != null)
moduleChecks.AddRange(inheritedChecks);
}
// candidate methods
var methods = ti.DeclaredMethods;
var commands = new List();
var commandBuilders = new Dictionary();
foreach (var m in methods)
{
- if (!m.IsCommandCandidate(out _))
+ if (!m.IsCommandCandidate(out _, out var isHybrid))
continue;
var attrs = m.GetCustomAttributes();
if (attrs.FirstOrDefault(xa => xa is CommandAttribute) is not CommandAttribute cattr)
continue;
var commandName = cattr.Name;
if (commandName == null)
{
commandName = m.Name;
if (commandName.EndsWith("Async") && commandName != "Async")
commandName = commandName[0..^5];
}
if (!this._config.CaseSensitive)
commandName = commandName.ToLowerInvariant();
if (!commandBuilders.TryGetValue(commandName, out var commandBuilder))
{
- commandBuilders.Add(commandName, commandBuilder = new CommandBuilder(module).WithName(commandName));
+ commandBuilders.Add(commandName, commandBuilder = new CommandBuilder(module).WithName(commandName).AsHybrid(isHybrid));
if (!isModule)
if (currentParent != null)
currentParent.WithChild(commandBuilder);
else
commands.Add(commandBuilder);
else
groupBuilder.WithChild(commandBuilder);
}
+ commandBuilder.IsHybrid = isHybrid;
commandBuilder.WithOverload(new CommandOverloadBuilder(m));
if (!isModule && moduleChecks.Any())
foreach (var chk in moduleChecks)
commandBuilder.WithExecutionCheck(chk);
foreach (var xa in attrs)
{
switch (xa)
{
case AliasesAttribute a:
foreach (var xalias in a.Aliases)
commandBuilder.WithAlias(this._config.CaseSensitive ? xalias : xalias.ToLowerInvariant());
break;
case CheckBaseAttribute p:
commandBuilder.WithExecutionCheck(p);
break;
case DescriptionAttribute d:
commandBuilder.WithDescription(d.Description);
break;
case HiddenAttribute h:
commandBuilder.WithHiddenStatus(true);
break;
default:
commandBuilder.WithCustomAttribute(xa);
break;
}
}
if (!isModule && moduleHidden)
commandBuilder.WithHiddenStatus(true);
}
// candidate types
var types = ti.DeclaredNestedTypes
.Where(xt => xt.IsModuleCandidateType() && xt.DeclaredConstructors.Any(xc => xc.IsPublic));
foreach (var type in types)
{
this.RegisterCommands(type.AsType(),
groupBuilder,
!isModule ? moduleChecks : null,
out var tempCommands);
if (isModule)
foreach (var chk in moduleChecks)
groupBuilder.WithExecutionCheck(chk);
if (isModule && tempCommands != null)
foreach (var xtcmd in tempCommands)
groupBuilder.WithChild(xtcmd);
else if (tempCommands != null)
commands.AddRange(tempCommands);
}
if (isModule && currentParent == null)
commands.Add(groupBuilder);
else if (isModule)
currentParent.WithChild(groupBuilder);
foundCommands = commands;
}
///
/// Builds and registers all supplied commands.
///
/// Commands to build and register.
public void RegisterCommands(params CommandBuilder[] cmds)
{
foreach (var cmd in cmds)
this.AddToCommandDictionary(cmd.Build(null));
}
///
/// Unregister specified commands from CommandsNext.
///
/// Commands to unregister.
public void UnregisterCommands(params Command[] cmds)
{
if (cmds.Any(x => x.Parent != null))
throw new InvalidOperationException("Cannot unregister nested commands.");
var keys = this.RegisteredCommands.Where(x => cmds.Contains(x.Value)).Select(x => x.Key).ToList();
foreach (var key in keys)
this._topLevelCommands.Remove(key);
}
///
/// Adds the to command dictionary.
///
/// The cmd.
private void AddToCommandDictionary(Command cmd)
{
if (cmd.Parent != null)
return;
if (this._topLevelCommands.ContainsKey(cmd.Name) || (cmd.Aliases != null && cmd.Aliases.Any(xs => this._topLevelCommands.ContainsKey(xs))))
throw new DuplicateCommandException(cmd.QualifiedName);
this._topLevelCommands[cmd.Name] = cmd;
if (cmd.Aliases != null)
foreach (var xs in cmd.Aliases)
this._topLevelCommands[xs] = cmd;
}
#endregion
#region Default Help
///
/// Represents the default help module.
///
[ModuleLifespan(ModuleLifespan.Transient)]
public class DefaultHelpModule : BaseCommandModule
{
///
/// Defaults the help async.
///
/// The ctx.
/// The command.
/// A Task.
[Command("help"), Description("Displays command help.")]
public async Task DefaultHelpAsync(CommandContext ctx, [Description("Command to provide help for.")] params string[] command)
{
var topLevel = ctx.CommandsNext._topLevelCommands.Values.Distinct();
var helpBuilder = ctx.CommandsNext._helpFormatter.Create(ctx);
if (command != null && command.Any())
{
Command cmd = null;
var searchIn = topLevel;
foreach (var c in command)
{
if (searchIn == null)
{
cmd = null;
break;
}
cmd = ctx.Config.CaseSensitive
? searchIn.FirstOrDefault(xc => xc.Name == c || (xc.Aliases != null && xc.Aliases.Contains(c)))
: searchIn.FirstOrDefault(xc => xc.Name.ToLowerInvariant() == c.ToLowerInvariant() || (xc.Aliases != null && xc.Aliases.Select(xs => xs.ToLowerInvariant()).Contains(c.ToLowerInvariant())));
if (cmd == null)
break;
var failedChecks = await cmd.RunChecksAsync(ctx, true).ConfigureAwait(false);
if (failedChecks.Any())
throw new ChecksFailedException(cmd, ctx, failedChecks);
searchIn = cmd is CommandGroup ? (cmd as CommandGroup).Children : null;
}
if (cmd == null)
throw new CommandNotFoundException(string.Join(" ", command));
helpBuilder.WithCommand(cmd);
if (cmd is CommandGroup group)
{
var commandsToSearch = group.Children.Where(xc => !xc.IsHidden);
var eligibleCommands = new List();
foreach (var candidateCommand in commandsToSearch)
{
if (candidateCommand.ExecutionChecks == null || !candidateCommand.ExecutionChecks.Any())
{
eligibleCommands.Add(candidateCommand);
continue;
}
var candidateFailedChecks = await candidateCommand.RunChecksAsync(ctx, true).ConfigureAwait(false);
if (!candidateFailedChecks.Any())
eligibleCommands.Add(candidateCommand);
}
if (eligibleCommands.Any())
helpBuilder.WithSubcommands(eligibleCommands.OrderBy(xc => xc.Name));
}
}
else
{
var commandsToSearch = topLevel.Where(xc => !xc.IsHidden);
var eligibleCommands = new List();
foreach (var sc in commandsToSearch)
{
if (sc.ExecutionChecks == null || !sc.ExecutionChecks.Any())
{
eligibleCommands.Add(sc);
continue;
}
var candidateFailedChecks = await sc.RunChecksAsync(ctx, true).ConfigureAwait(false);
if (!candidateFailedChecks.Any())
eligibleCommands.Add(sc);
}
if (eligibleCommands.Any())
helpBuilder.WithSubcommands(eligibleCommands.OrderBy(xc => xc.Name));
}
var helpMessage = helpBuilder.Build();
var builder = new DiscordMessageBuilder().WithContent(helpMessage.Content).WithEmbed(helpMessage.Embed);
if (!ctx.Config.DmHelp || ctx.Channel is DiscordDmChannel || ctx.Guild == null)
await ctx.RespondAsync(builder).ConfigureAwait(false);
else
await ctx.Member.SendMessageAsync(builder).ConfigureAwait(false);
}
}
#endregion
#region Sudo
///
/// Creates a fake command context to execute commands with.
///
/// The user or member to use as message author.
/// The channel the message is supposed to appear from.
/// Contents of the message.
/// Command prefix, used to execute commands.
/// Command to execute.
/// Raw arguments to pass to command.
/// Created fake context.
public CommandContext CreateFakeContext(DiscordUser actor, DiscordChannel channel, string messageContents, string prefix, Command cmd, string rawArguments = null)
{
var epoch = new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero);
var now = DateTimeOffset.UtcNow;
var timeSpan = (ulong)(now - epoch).TotalMilliseconds;
// create fake message
var msg = new DiscordMessage
{
Discord = this.Client,
Author = actor,
ChannelId = channel.Id,
Content = messageContents,
Id = timeSpan << 22,
Pinned = false,
MentionEveryone = messageContents.Contains("@everyone"),
IsTts = false,
AttachmentsInternal = new List(),
EmbedsInternal = new List(),
TimestampRaw = now.ToString("yyyy-MM-ddTHH:mm:sszzz"),
ReactionsInternal = new List()
};
var mentionedUsers = new List();
var mentionedRoles = msg.Channel.Guild != null ? new List() : null;
var mentionedChannels = msg.Channel.Guild != null ? new List() : null;
if (!string.IsNullOrWhiteSpace(msg.Content))
{
if (msg.Channel.Guild != null)
{
mentionedUsers = Utilities.GetUserMentions(msg).Select(xid => msg.Channel.Guild.MembersInternal.TryGetValue(xid, out var member) ? member : null).Cast().ToList();
mentionedRoles = Utilities.GetRoleMentions(msg).Select(xid => msg.Channel.Guild.GetRole(xid)).ToList();
mentionedChannels = Utilities.GetChannelMentions(msg).Select(xid => msg.Channel.Guild.GetChannel(xid)).ToList();
}
else
{
mentionedUsers = Utilities.GetUserMentions(msg).Select(this.Client.GetCachedOrEmptyUserInternal).ToList();
}
}
msg.MentionedUsersInternal = mentionedUsers;
msg.MentionedRolesInternal = mentionedRoles;
msg.MentionedChannelsInternal = mentionedChannels;
var ctx = new CommandContext
{
Client = this.Client,
Command = cmd,
Message = msg,
Config = this._config,
RawArgumentString = rawArguments ?? "",
Prefix = prefix,
CommandsNext = this,
Services = this.Services
};
if (cmd != null && (cmd.Module is TransientCommandModule || cmd.Module == null))
{
var scope = ctx.Services.CreateScope();
ctx.ServiceScopeContext = new CommandContext.ServiceContext(ctx.Services, scope);
ctx.Services = scope.ServiceProvider;
}
return ctx;
}
#endregion
#region Type Conversion
///
/// Converts a string to specified type.
///
/// Type to convert to.
/// Value to convert.
/// Context in which to convert to.
/// Converted object.
public async Task ConvertArgument(string value, CommandContext ctx)
{
var t = typeof(T);
if (!this.ArgumentConverters.ContainsKey(t))
throw new ArgumentException("There is no converter specified for given type.", nameof(T));
if (this.ArgumentConverters[t] is not IArgumentConverter cv)
throw new ArgumentException("Invalid converter registered for this type.", nameof(T));
var cvr = await cv.ConvertAsync(value, ctx).ConfigureAwait(false);
return !cvr.HasValue ? throw new ArgumentException("Could not convert specified value to given type.", nameof(value)) : cvr.Value;
}
+ ///
+ /// Converts a string to specified type.
+ ///
+ /// Type to convert to.
+ /// Value to convert.
+ /// Context in which to convert to.
+ /// Converted object.
+ public async Task ConvertArgument(string value, HybridCommandContext ctx)
+ {
+ var t = typeof(T);
+ if (!this.ArgumentConverters.ContainsKey(t))
+ throw new ArgumentException("There is no converter specified for given type.", nameof(T));
+
+ if (this.ArgumentConverters[t] is not IArgumentConverter cv)
+ throw new ArgumentException("Invalid converter registered for this type.", nameof(T));
+
+ var cvr = await cv.ConvertAsync(value, ctx).ConfigureAwait(false);
+ return !cvr.HasValue ? throw new ArgumentException("Could not convert specified value to given type.", nameof(value)) : cvr.Value;
+ }
+
///
/// Converts a string to specified type.
///
/// Value to convert.
/// Context in which to convert to.
/// Type to convert to.
/// Converted object.
public async Task