diff --git a/DisCatSharp.CommandsNext/CommandsNextExtension.cs b/DisCatSharp.CommandsNext/CommandsNextExtension.cs
index e9447807a..717168ad7 100644
--- a/DisCatSharp.CommandsNext/CommandsNextExtension.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextExtension.cs
@@ -1,1086 +1,1086 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.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 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;
///
/// 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)
{
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));
}
///
/// 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)
{
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;
}
///
/// 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);
else
await this._error.InvokeAsync(this, new CommandErrorEventArgs(this.Client.ServiceProvider) { Context = res.Context, 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();
}
}
///
/// 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);
}
#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))
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 _))
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));
if (!isModule)
if (currentParent != null)
currentParent.WithChild(commandBuilder);
else
commands.Add(commandBuilder);
else
groupBuilder.WithChild(commandBuilder);
}
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.
///
/// Value to convert.
/// Context in which to convert to.
/// Type to convert to.
/// Converted object.
public async Task ConvertArgument(string value, CommandContext ctx, Type type)
{
var m = this._convertGeneric.MakeGenericMethod(type);
try
{
return await (m.Invoke(this, new object[] { value, ctx }) as Task).ConfigureAwait(false);
}
catch (TargetInvocationException ex)
{
throw ex.InnerException;
}
}
///
/// Registers an argument converter for specified type.
///
/// Type for which to register the converter.
/// Converter to register.
public void RegisterConverter(IArgumentConverter converter)
{
if (converter == null)
throw new ArgumentNullException(nameof(converter), "Converter cannot be null.");
var t = typeof(T);
var ti = t.GetTypeInfo();
this.ArgumentConverters[t] = converter;
if (!ti.IsValueType)
return;
var nullableConverterType = typeof(NullableConverter<>).MakeGenericType(t);
var nullableType = typeof(Nullable<>).MakeGenericType(t);
if (this.ArgumentConverters.ContainsKey(nullableType))
return;
var nullableConverter = Activator.CreateInstance(nullableConverterType) as IArgumentConverter;
this.ArgumentConverters[nullableType] = nullableConverter;
}
///
/// Unregister an argument converter for specified type.
///
/// Type for which to unregister the converter.
public void UnregisterConverter()
{
var t = typeof(T);
var ti = t.GetTypeInfo();
if (this.ArgumentConverters.ContainsKey(t))
this.ArgumentConverters.Remove(t);
if (this._userFriendlyTypeNames.ContainsKey(t))
this._userFriendlyTypeNames.Remove(t);
if (!ti.IsValueType)
return;
var nullableType = typeof(Nullable<>).MakeGenericType(t);
if (!this.ArgumentConverters.ContainsKey(nullableType))
return;
this.ArgumentConverters.Remove(nullableType);
this._userFriendlyTypeNames.Remove(nullableType);
}
///
/// Registers a user-friendly type name.
///
/// Type to register the name for.
/// Name to register.
public void RegisterUserFriendlyTypeName(string value)
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(nameof(value), "Name cannot be null or empty.");
var t = typeof(T);
var ti = t.GetTypeInfo();
if (!this.ArgumentConverters.ContainsKey(t))
throw new InvalidOperationException("Cannot register a friendly name for a type which has no associated converter.");
this._userFriendlyTypeNames[t] = value;
if (!ti.IsValueType)
return;
var nullableType = typeof(Nullable<>).MakeGenericType(t);
this._userFriendlyTypeNames[nullableType] = value;
}
///
/// Converts a type into user-friendly type name.
///
/// Type to convert.
/// User-friendly type name.
public string GetUserFriendlyTypeName(Type t)
{
if (this._userFriendlyTypeNames.ContainsKey(t))
return this._userFriendlyTypeNames[t];
var ti = t.GetTypeInfo();
if (ti.IsGenericTypeDefinition && t.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var tn = ti.GenericTypeArguments[0];
return this._userFriendlyTypeNames.ContainsKey(tn) ? this._userFriendlyTypeNames[tn] : tn.Name;
}
return t.Name;
}
#endregion
#region Helpers
///
/// Allows easier interoperability with reflection by turning the returned by
/// into a task containing , using the provided generic type information.
///
private async Task ConvertArgumentToObj(string value, CommandContext ctx)
=> await this.ConvertArgument(value, ctx).ConfigureAwait(false);
///
/// Gets the configuration-specific string comparer. This returns or ,
/// depending on whether is set to or .
///
/// A string comparer.
internal IEqualityComparer GetStringComparer()
=> this._config.CaseSensitive
? StringComparer.Ordinal
: StringComparer.OrdinalIgnoreCase;
#endregion
#region Events
///
/// Triggered whenever a command executes successfully.
///
public event AsyncEventHandler CommandExecuted
{
add => this._executed.Register(value);
remove => this._executed.Unregister(value);
}
private AsyncEvent _executed;
///
/// Triggered whenever a command throws an exception during execution.
///
public event AsyncEventHandler CommandErrored
{
add => this._error.Register(value);
remove => this._error.Unregister(value);
}
private AsyncEvent _error;
///
/// Fires when a command gets executed.
///
/// The command execution event arguments.
private Task OnCommandExecuted(CommandExecutionEventArgs e)
=> this._executed.InvokeAsync(this, e);
///
/// Fires when a command fails.
///
/// The command error event arguments.
private Task OnCommandErrored(CommandErrorEventArgs e)
=> this._error.InvokeAsync(this, e);
#endregion
}
diff --git a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
index c2c2232d6..adb5c3447 100644
--- a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
+++ b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
@@ -1,132 +1,132 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
// ReSharper disable InconsistentNaming
namespace DisCatSharp.Common.Serialization;
///
-/// Defines the format for string-serialized and objects.
+/// Defines the format for string-serialized and objects.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class DateTimeFormatAttribute : SerializationAttribute
{
///
/// Gets the ISO 8601 format string of "yyyy-MM-ddTHH:mm:ss.fffzzz".
///
public const string FORMAT_ISO_8601 = "yyyy-MM-ddTHH:mm:ss.fffzzz";
///
/// Gets the RFC 1123 format string of "R".
///
public const string FORMAT_RFC_1123 = "R";
///
/// Gets the general long format.
///
public const string FORMAT_LONG = "G";
///
/// Gets the general short format.
///
public const string FORMAT_SHORT = "g";
///
/// Gets the custom datetime format string to use.
///
public string Format { get; }
///
/// Gets the predefined datetime format kind.
///
public DateTimeFormatKind Kind { get; }
///
/// Specifies a predefined format to use.
///
/// Predefined format kind to use.
public DateTimeFormatAttribute(DateTimeFormatKind kind)
{
if (kind < 0 || kind > DateTimeFormatKind.InvariantLocaleShort)
throw new ArgumentOutOfRangeException(nameof(kind), "Specified format kind is not legal or supported.");
this.Kind = kind;
this.Format = null;
}
///
/// Specifies a custom format to use.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings for more details.
///
/// Custom format string to use.
public DateTimeFormatAttribute(string format)
{
if (string.IsNullOrWhiteSpace(format))
throw new ArgumentNullException(nameof(format), "Specified format cannot be null or empty.");
this.Kind = DateTimeFormatKind.Custom;
this.Format = format;
}
}
///
-/// Defines which built-in format to use for for and serialization.
+/// Defines which built-in format to use for for and serialization.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings and https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings for more details.
///
public enum DateTimeFormatKind : int
{
///
/// Specifies ISO 8601 format, which is equivalent to .NET format string of "yyyy-MM-ddTHH:mm:ss.fffzzz".
///
ISO8601 = 0,
///
/// Specifies RFC 1123 format, which is equivalent to .NET format string of "R".
///
RFC1123 = 1,
///
/// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
///
CurrentLocaleLong = 2,
///
/// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
///
CurrentLocaleShort = 3,
///
/// Specifies a format defined by , with a format string of "G".
///
InvariantLocaleLong = 4,
///
/// Specifies a format defined by , with a format string of "g".
///
InvariantLocaleShort = 5,
///
/// Specifies a custom format. This value is not usable directly.
///
Custom = int.MaxValue
}
diff --git a/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs b/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
index af1e257ed..b5b123fba 100644
--- a/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
+++ b/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
namespace DisCatSharp.Common.Serialization;
///
-/// Specifies that this will be serialized as a number of whole seconds.
+/// Specifies that this will be serialized as a number of whole seconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class TimeSpanSecondsAttribute : SerializationAttribute
{ }
///
-/// Specifies that this will be serialized as a number of whole milliseconds.
+/// Specifies that this will be serialized as a number of whole milliseconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class TimeSpanMillisecondsAttribute : SerializationAttribute
{ }
diff --git a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
index af570ce41..850614912 100644
--- a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
+++ b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
@@ -1,132 +1,132 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
// ReSharper disable InconsistentNaming
namespace DisCatSharp.Common.Serialization;
///
-/// Defines the format for string-serialized objects.
+/// Defines the format for string-serialized objects.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class TimeSpanFormatAttribute : SerializationAttribute
{
///
/// Gets the ISO 8601 format string of @"ddThh\:mm\:ss\.fff".
///
public const string FORMAT_ISO_8601 = @"ddThh\:mm\:ss\.fff";
///
/// Gets the constant format.
///
public const string FORMAT_CONSTANT = "c";
///
/// Gets the general long format.
///
public const string FORMAT_LONG = "G";
///
/// Gets the general short format.
///
public const string FORMAT_SHORT = "g";
///
/// Gets the custom datetime format string to use.
///
public string Format { get; }
///
/// Gets the predefined datetime format kind.
///
public TimeSpanFormatKind Kind { get; }
///
/// Specifies a predefined format to use.
///
/// Predefined format kind to use.
public TimeSpanFormatAttribute(TimeSpanFormatKind kind)
{
if (kind < 0 || kind > TimeSpanFormatKind.InvariantLocaleShort)
throw new ArgumentOutOfRangeException(nameof(kind), "Specified format kind is not legal or supported.");
this.Kind = kind;
this.Format = null;
}
///
/// Specifies a custom format to use.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings for more details.
///
/// Custom format string to use.
public TimeSpanFormatAttribute(string format)
{
if (string.IsNullOrWhiteSpace(format))
throw new ArgumentNullException(nameof(format), "Specified format cannot be null or empty.");
this.Kind = TimeSpanFormatKind.Custom;
this.Format = format;
}
}
///
-/// Defines which built-in format to use for serialization.
+/// Defines which built-in format to use for serialization.
/// See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings and https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings for more details.
///
public enum TimeSpanFormatKind : int
{
///
/// Specifies ISO 8601-like time format, which is equivalent to .NET format string of @"ddThh\:mm\:ss\.fff".
///
ISO8601 = 0,
///
/// Specifies a format defined by , with a format string of "c".
///
InvariantConstant = 1,
///
/// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
///
CurrentLocaleLong = 2,
///
/// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
///
CurrentLocaleShort = 3,
///
/// Specifies a format defined by , with a format string of "G". This format is not recommended for portability reasons.
///
InvariantLocaleLong = 4,
///
/// Specifies a format defined by , with a format string of "g". This format is not recommended for portability reasons.
///
InvariantLocaleShort = 5,
///
/// Specifies a custom format. This value is not usable directly.
///
Custom = int.MaxValue
}
diff --git a/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs b/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
index cf3b9aed8..32144740a 100644
--- a/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
+++ b/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
namespace DisCatSharp.Common.Serialization;
///
-/// Specifies that this or will be serialized as Unix timestamp seconds.
+/// Specifies that this or will be serialized as Unix timestamp seconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class UnixSecondsAttribute : SerializationAttribute
{ }
///
-/// Specifies that this or will be serialized as Unix timestamp milliseconds.
+/// Specifies that this or will be serialized as Unix timestamp milliseconds.
/// This value will always be serialized as a number.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class UnixMillisecondsAttribute : SerializationAttribute
{ }
diff --git a/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs b/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
index 3ace497bc..75e2141ea 100644
--- a/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
+++ b/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
@@ -1,813 +1,813 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace DisCatSharp.Common;
///
-/// Represents collection of string keys and values, allowing the use of for dictionary operations.
+/// Represents collection of string keys and values, allowing the use of for dictionary operations.
///
/// Type of items in this dictionary.
public sealed class CharSpanLookupDictionary :
IDictionary,
IReadOnlyDictionary,
IDictionary
{
///
/// Gets the collection of all keys present in this dictionary.
///
public IEnumerable Keys => this.GetKeysInternal();
///
/// Gets the keys.
///
ICollection IDictionary.Keys => this.GetKeysInternal();
///
/// Gets the keys.
///
ICollection IDictionary.Keys => this.GetKeysInternal();
///
/// Gets the collection of all values present in this dictionary.
///
public IEnumerable Values => this.GetValuesInternal();
///
/// Gets the values.
///
ICollection IDictionary.Values => this.GetValuesInternal();
///
/// Gets the values.
///
ICollection IDictionary.Values => this.GetValuesInternal();
///
/// Gets the total number of items in this dictionary.
///
public int Count { get; private set; }
///
/// Gets whether this dictionary is read-only.
///
public bool IsReadOnly => false;
///
/// Gets whether this dictionary has a fixed size.
///
public bool IsFixedSize => false;
///
/// Gets whether this dictionary is considered thread-safe.
///
public bool IsSynchronized => false;
///
/// Gets the object which allows synchronizing access to this dictionary.
///
public object SyncRoot { get; } = new();
///
/// Gets or sets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[string key]
{
get
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (!this.TryRetrieveInternal(key.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
return value;
}
set
{
if (key == null)
throw new ArgumentNullException(nameof(key));
this.TryInsertInternal(key, value, true);
}
}
///
/// Gets or sets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[ReadOnlySpan key]
{
get
{
if (!this.TryRetrieveInternal(key, out var value))
throw new KeyNotFoundException($"The given key was not present in the dictionary.");
return value;
}
set
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
this.TryInsertInternal(new string(chars, 0, key.Length), value, true);
}
}
}
object IDictionary.this[object key]
{
get
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!this.TryRetrieveInternal(tkey.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{tkey}' was not present in the dictionary.");
return value;
}
set
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!(value is TValue tvalue))
{
tvalue = default;
if (tvalue != null)
throw new ArgumentException($"Value needs to be an instance of {typeof(TValue)}.");
}
this.TryInsertInternal(tkey, tvalue, true);
}
}
///
/// Gets the internal buckets.
///
private readonly Dictionary _internalBuckets;
///
/// Creates a new, empty with string keys and items of type .
///
public CharSpanLookupDictionary()
{
this._internalBuckets = new Dictionary();
}
///
/// Creates a new, empty with string keys and items of type and sets its initial capacity to specified value.
///
/// Initial capacity of the dictionary.
public CharSpanLookupDictionary(int initialCapacity)
{
this._internalBuckets = new Dictionary(initialCapacity);
}
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupDictionary(IDictionary values)
: this(values.Count)
{
foreach (var (k, v) in values)
this.Add(k, v);
}
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupDictionary(IReadOnlyDictionary values)
: this(values.Count)
{
foreach (var (k, v) in values)
this.Add(k, v);
}
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied key-value collection.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupDictionary(IEnumerable> values)
: this()
{
foreach (var (k, v) in values)
this.Add(k, v);
}
///
/// Inserts a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
public void Add(string key, TValue value)
{
if (!this.TryInsertInternal(key, value, false))
throw new ArgumentException("Given key is already present in the dictionary.", nameof(key));
}
///
/// Inserts a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
public void Add(ReadOnlySpan key, TValue value)
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
if (!this.TryInsertInternal(new string(chars, 0, key.Length), value, false))
throw new ArgumentException("Given key is already present in the dictionary.", nameof(key));
}
}
///
/// Attempts to insert a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
/// Whether the operation was successful.
public bool TryAdd(string key, TValue value)
=> this.TryInsertInternal(key, value, false);
///
/// Attempts to insert a specific key and corresponding value into this dictionary.
///
/// Key to insert.
/// Value corresponding to this key.
/// Whether the operation was successful.
public bool TryAdd(ReadOnlySpan key, TValue value)
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
return this.TryInsertInternal(new string(chars, 0, key.Length), value, false);
}
}
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRetrieveInternal(key.AsSpan(), out value);
}
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(ReadOnlySpan key, out TValue value)
=> this.TryRetrieveInternal(key, out value);
///
/// Attempts to remove a value corresponding to the supplied key from this dictionary.
///
/// Key to remove the value for.
/// Removed value.
/// Whether the operation was successful.
public bool TryRemove(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRemoveInternal(key.AsSpan(), out value);
}
///
/// Attempts to remove a value corresponding to the supplied key from this dictionary.
///
/// Key to remove the value for.
/// Removed value.
/// Whether the operation was successful.
public bool TryRemove(ReadOnlySpan key, out TValue value)
=> this.TryRemoveInternal(key, out value);
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(string key)
=> this.ContainsKeyInternal(key.AsSpan());
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(ReadOnlySpan key)
=> this.ContainsKeyInternal(key);
///
/// Removes all items from this dictionary.
///
public void Clear()
{
this._internalBuckets.Clear();
this.Count = 0;
}
///
/// Gets an enumerator over key-value pairs in this dictionary.
///
///
public IEnumerator> GetEnumerator()
=> new Enumerator(this);
///
/// Removes the.
///
/// The key.
/// A bool.
bool IDictionary.Remove(string key)
=> this.TryRemove(key.AsSpan(), out _);
///
/// Adds the.
///
/// The key.
/// The value.
void IDictionary.Add(object key, object value)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!(value is TValue tvalue))
{
tvalue = default;
if (tvalue != null)
throw new ArgumentException($"Value needs to be an instance of {typeof(TValue)}.");
}
this.Add(tkey, tvalue);
}
///
/// Removes the.
///
/// The key.
void IDictionary.Remove(object key)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
this.TryRemove(tkey, out _);
}
///
/// Contains the.
///
/// The key.
/// A bool.
bool IDictionary.Contains(object key)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
return this.ContainsKey(tkey);
}
///
/// Gets the enumerator.
///
/// An IDictionaryEnumerator.
IDictionaryEnumerator IDictionary.GetEnumerator()
=> new Enumerator(this);
///
/// Adds the.
///
/// The item.
void ICollection>.Add(KeyValuePair item)
=> this.Add(item.Key, item.Value);
///
/// Removes the.
///
/// The item.
/// A bool.
bool ICollection>.Remove(KeyValuePair item)
=> this.TryRemove(item.Key, out _);
///
/// Contains the.
///
/// The item.
/// A bool.
bool ICollection>.Contains(KeyValuePair item)
=> this.TryGetValue(item.Key, out var value) && EqualityComparer.Default.Equals(value, item.Value);
///
/// Copies the to.
///
/// The array.
/// The array index.
void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
{
if (array.Length - arrayIndex < this.Count)
throw new ArgumentException("Target array is too small.", nameof(array));
var i = arrayIndex;
foreach (var (k, v) in this._internalBuckets)
{
var kdv = v;
while (kdv != null)
{
array[i++] = new KeyValuePair(kdv.Key, kdv.Value);
kdv = kdv.Next;
}
}
}
///
/// Copies the to.
///
/// The array.
/// The array index.
void ICollection.CopyTo(Array array, int arrayIndex)
{
if (array is KeyValuePair[] tarray)
{
(this as ICollection>).CopyTo(tarray, arrayIndex);
return;
}
if (array is not object[])
throw new ArgumentException($"Array needs to be an instance of {typeof(TValue[])} or object[].");
var i = arrayIndex;
foreach (var (k, v) in this._internalBuckets)
{
var kdv = v;
while (kdv != null)
{
array.SetValue(new KeyValuePair(kdv.Key, kdv.Value), i++);
kdv = kdv.Next;
}
}
}
///
/// Gets the enumerator.
///
/// An IEnumerator.
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
///
/// Tries the insert internal.
///
/// The key.
/// The value.
/// If true, replace.
/// A bool.
private bool TryInsertInternal(string key, TValue value, bool replace)
{
if (key == null)
throw new ArgumentNullException(nameof(key), "Key cannot be null.");
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.ContainsKey(hash))
{
this._internalBuckets.Add(hash, new KeyedValue(key, hash, value));
this.Count++;
return true;
}
var kdv = this._internalBuckets[hash];
var kdvLast = kdv;
while (kdv != null)
{
if (kdv.Key == key)
{
if (!replace)
return false;
kdv.Value = value;
return true;
}
kdvLast = kdv;
kdv = kdv.Next;
}
kdvLast.Next = new KeyedValue(key, hash, value);
this.Count++;
return true;
}
///
/// Tries the retrieve internal.
///
/// The key.
/// The value.
/// A bool.
private bool TryRetrieveInternal(ReadOnlySpan key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
value = kdv.Value;
return true;
}
}
return false;
}
///
/// Tries the remove internal.
///
/// The key.
/// The value.
/// A bool.
private bool TryRemoveInternal(ReadOnlySpan key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
if (kdv.Next == null && key.SequenceEqual(kdv.Key.AsSpan()))
{
// Only bucket under this hash and key matches, pop the entire bucket
value = kdv.Value;
this._internalBuckets.Remove(hash);
this.Count--;
return true;
}
else if (kdv.Next == null)
{
// Only bucket under this hash and key does not match, cannot remove
return false;
}
else if (key.SequenceEqual(kdv.Key.AsSpan()))
{
// First key in the bucket matches, pop it and set its child as current bucket
value = kdv.Value;
this._internalBuckets[hash] = kdv.Next;
this.Count--;
return true;
}
var kdvLast = kdv;
kdv = kdv.Next;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
// Key matched, remove this bucket from the chain
value = kdv.Value;
kdvLast.Next = kdv.Next;
this.Count--;
return true;
}
kdvLast = kdv;
kdv = kdv.Next;
}
return false;
}
///
/// Contains the key internal.
///
/// The key.
/// A bool.
private bool ContainsKeyInternal(ReadOnlySpan key)
{
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
return true;
kdv = kdv.Next;
}
return false;
}
///
/// Gets the keys internal.
///
/// An ImmutableArray.
private ImmutableArray GetKeysInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Key);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// Gets the values internal.
///
/// An ImmutableArray.
private ImmutableArray GetValuesInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Value);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// The keyed value.
///
private class KeyedValue
{
///
/// Gets the key hash.
///
public ulong KeyHash { get; }
///
/// Gets the key.
///
public string Key { get; }
///
/// Gets or sets the value.
///
public TValue Value { get; set; }
///
/// Gets or sets the next.
///
public KeyedValue Next { get; set; }
///
/// Initializes a new instance of the class.
///
/// The key.
/// The key hash.
/// The value.
public KeyedValue(string key, ulong keyHash, TValue value)
{
this.KeyHash = keyHash;
this.Key = key;
this.Value = value;
}
}
///
/// The enumerator.
///
private class Enumerator :
IEnumerator>,
IDictionaryEnumerator
{
///
/// Gets the current.
///
public KeyValuePair Current { get; private set; }
///
/// Gets the current.
///
object IEnumerator.Current => this.Current;
///
/// Gets the key.
///
object IDictionaryEnumerator.Key => this.Current.Key;
///
/// Gets the value.
///
object IDictionaryEnumerator.Value => this.Current.Value;
///
/// Gets the entry.
///
DictionaryEntry IDictionaryEnumerator.Entry => new(this.Current.Key, this.Current.Value);
///
/// Gets the internal dictionary.
///
private readonly CharSpanLookupDictionary _internalDictionary;
///
/// Gets the internal enumerator.
///
private readonly IEnumerator> _internalEnumerator;
///
/// Gets or sets the current value.
///
private KeyedValue _currentValue;
///
/// Initializes a new instance of the class.
///
/// The sp dict.
public Enumerator(CharSpanLookupDictionary spDict)
{
this._internalDictionary = spDict;
this._internalEnumerator = this._internalDictionary._internalBuckets.GetEnumerator();
}
///
/// Moves the next.
///
/// A bool.
public bool MoveNext()
{
var kdv = this._currentValue;
if (kdv == null)
{
if (!this._internalEnumerator.MoveNext())
return false;
kdv = this._internalEnumerator.Current.Value;
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
///
/// Resets the.
///
public void Reset()
{
this._internalEnumerator.Reset();
this.Current = default;
this._currentValue = null;
}
///
/// Disposes the.
///
public void Dispose() => this.Reset();
}
}
diff --git a/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs b/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
index b8c3b2a1d..e10dd97ff 100644
--- a/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
+++ b/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
@@ -1,415 +1,415 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
namespace DisCatSharp.Common;
///
-/// Represents collection of string keys and values, allowing the use of for dictionary operations.
+/// Represents collection of string keys and values, allowing the use of for dictionary operations.
///
/// Type of items in this dictionary.
public sealed class CharSpanLookupReadOnlyDictionary : IReadOnlyDictionary
{
///
/// Gets the collection of all keys present in this dictionary.
///
public IEnumerable Keys => this.GetKeysInternal();
///
/// Gets the collection of all values present in this dictionary.
///
public IEnumerable Values => this.GetValuesInternal();
///
/// Gets the total number of items in this dictionary.
///
public int Count { get; }
///
/// Gets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[string key]
{
get
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (!this.TryRetrieveInternal(key.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
return value;
}
}
///
/// Gets a value corresponding to given key in this dictionary.
///
/// Key to get or set the value for.
/// Value matching the supplied key, if applicable.
public TValue this[ReadOnlySpan key]
{
get
{
if (!this.TryRetrieveInternal(key, out var value))
throw new KeyNotFoundException($"The given key was not present in the dictionary.");
return value;
}
}
///
/// Gets the internal buckets.
///
private readonly IReadOnlyDictionary _internalBuckets;
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupReadOnlyDictionary(IDictionary values)
: this(values as IEnumerable>)
{ }
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied dictionary.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupReadOnlyDictionary(IReadOnlyDictionary values)
: this(values as IEnumerable>)
{ }
///
/// Creates a new with string keys and items of type and populates it with key-value pairs from supplied key-value collection.
///
/// Dictionary containing items to populate this dictionary with.
public CharSpanLookupReadOnlyDictionary(IEnumerable> values)
{
this._internalBuckets = PrepareItems(values, out var count);
this.Count = count;
}
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRetrieveInternal(key.AsSpan(), out value);
}
///
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
///
/// Key to retrieve the value for.
/// Retrieved value.
/// Whether the operation was successful.
public bool TryGetValue(ReadOnlySpan key, out TValue value)
=> this.TryRetrieveInternal(key, out value);
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(string key)
=> this.ContainsKeyInternal(key.AsSpan());
///
/// Checks whether this dictionary contains the specified key.
///
/// Key to check for in this dictionary.
/// Whether the key was present in the dictionary.
public bool ContainsKey(ReadOnlySpan key)
=> this.ContainsKeyInternal(key);
///
/// Gets an enumerator over key-value pairs in this dictionary.
///
///
public IEnumerator> GetEnumerator()
=> new Enumerator(this);
///
/// Gets the enumerator.
///
/// An IEnumerator.
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
///
/// Tries the retrieve internal.
///
/// The key.
/// The value.
/// A bool.
private bool TryRetrieveInternal(ReadOnlySpan key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
value = kdv.Value;
return true;
}
}
return false;
}
///
/// Contains the key internal.
///
/// The key.
/// A bool.
private bool ContainsKeyInternal(ReadOnlySpan key)
{
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
return true;
kdv = kdv.Next;
}
return false;
}
///
/// Gets the keys internal.
///
/// An ImmutableArray.
private ImmutableArray GetKeysInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Key);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// Gets the values internal.
///
/// An ImmutableArray.
private ImmutableArray GetValuesInternal()
{
var builder = ImmutableArray.CreateBuilder(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Value);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
///
/// Prepares the items.
///
/// The items.
/// The count.
/// An IReadOnlyDictionary.
private static IReadOnlyDictionary PrepareItems(IEnumerable> items, out int count)
{
count = 0;
var dict = new Dictionary();
foreach (var (k, v) in items)
{
if (k == null)
throw new ArgumentException("Keys cannot be null.", nameof(items));
var hash = k.CalculateKnuthHash();
if (!dict.ContainsKey(hash))
{
dict.Add(hash, new KeyedValue(k, hash, v));
count++;
continue;
}
var kdv = dict[hash];
var kdvLast = kdv;
while (kdv != null)
{
if (kdv.Key == k)
throw new ArgumentException("Given key is already present in the dictionary.", nameof(items));
kdvLast = kdv;
kdv = kdv.Next;
}
kdvLast.Next = new KeyedValue(k, hash, v);
count++;
}
return new ReadOnlyDictionary(dict);
}
///
/// The keyed value.
///
private class KeyedValue
{
///
/// Gets the key hash.
///
public ulong KeyHash { get; }
///
/// Gets the key.
///
public string Key { get; }
///
/// Gets or sets the value.
///
public TValue Value { get; set; }
///
/// Gets or sets the next.
///
public KeyedValue Next { get; set; }
///
/// Initializes a new instance of the class.
///
/// The key.
/// The key hash.
/// The value.
public KeyedValue(string key, ulong keyHash, TValue value)
{
this.KeyHash = keyHash;
this.Key = key;
this.Value = value;
}
}
///
/// The enumerator.
///
private class Enumerator : IEnumerator>
{
///
/// Gets the current.
///
public KeyValuePair Current { get; private set; }
///
/// Gets the current.
///
object IEnumerator.Current => this.Current;
///
/// Gets the internal dictionary.
///
private readonly CharSpanLookupReadOnlyDictionary _internalDictionary;
///
/// Gets the internal enumerator.
///
private readonly IEnumerator> _internalEnumerator;
///
/// Gets or sets the current value.
///
private KeyedValue _currentValue;
///
/// Initializes a new instance of the class.
///
/// The sp dict.
public Enumerator(CharSpanLookupReadOnlyDictionary spDict)
{
this._internalDictionary = spDict;
this._internalEnumerator = this._internalDictionary._internalBuckets.GetEnumerator();
}
///
/// Moves the next.
///
/// A bool.
public bool MoveNext()
{
var kdv = this._currentValue;
if (kdv == null)
{
if (!this._internalEnumerator.MoveNext())
return false;
kdv = this._internalEnumerator.Current.Value;
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
this.Current = new KeyValuePair(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
///
/// Resets the.
///
public void Reset()
{
this._internalEnumerator.Reset();
this.Current = default;
this._currentValue = null;
}
///
/// Disposes the.
///
public void Dispose() => this.Reset();
}
}
diff --git a/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs b/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
index 0368bb08d..95937b8fb 100644
--- a/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
+++ b/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
@@ -1,254 +1,254 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace DisCatSharp.Common.Types;
///
/// Provides a resizable memory buffer analogous to , using a single continuous memory region instead.
///
/// Type of item to hold in the buffer.
public sealed class ContinuousMemoryBuffer : IMemoryBuffer where T : unmanaged
{
///
public ulong Capacity => (ulong)this._buff.Length;
///
public ulong Length => (ulong)this._pos;
///
public ulong Count => (ulong)(this._pos / this._itemSize);
private readonly MemoryPool _pool;
private IMemoryOwner _buffOwner;
private Memory _buff;
private readonly bool _clear;
private int _pos;
private readonly int _itemSize;
private bool _isDisposed;
///
/// Creates a new buffer with a specified segment size, specified number of initially-allocated segments, and supplied memory pool.
///
/// Initial size of the buffer in bytes. Defaults to 64KiB.
- /// Memory pool to use for renting buffers. Defaults to .
+ /// Memory pool to use for renting buffers. Defaults to .
/// Determines whether the underlying buffers should be cleared on exit. If dealing with sensitive data, it might be a good idea to set this option to true.
public ContinuousMemoryBuffer(int initialSize = 65536, MemoryPool memPool = default, bool clearOnDispose = false)
{
this._itemSize = Unsafe.SizeOf();
this._pool = memPool ?? MemoryPool.Shared;
this._clear = clearOnDispose;
this._buffOwner = this._pool.Rent(initialSize);
this._buff = this._buffOwner.Memory;
this._isDisposed = false;
}
///
public void Write(ReadOnlySpan data)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var bytes = MemoryMarshal.AsBytes(data);
this.EnsureSize(this._pos + bytes.Length);
bytes.CopyTo(this._buff[this._pos..].Span);
this._pos += bytes.Length;
}
///
public void Write(T[] data, int start, int count)
=> this.Write(data.AsSpan(start, count));
///
public void Write(ArraySegment data)
=> this.Write(data.AsSpan());
///
public void Write(Stream stream)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
if (stream.CanSeek)
this.WriteStreamSeekable(stream);
else
this.WriteStreamUnseekable(stream);
}
///
/// Writes the stream seekable.
///
/// The stream.
private void WriteStreamSeekable(Stream stream)
{
if (stream.Length > int.MaxValue)
throw new ArgumentException("Stream is too long.", nameof(stream));
this.EnsureSize(this._pos + (int)stream.Length);
var memo = ArrayPool.Shared.Rent((int)stream.Length);
try
{
var br = stream.Read(memo, 0, memo.Length);
memo.AsSpan(0, br).CopyTo(this._buff[this._pos..].Span);
}
finally
{
ArrayPool.Shared.Return(memo);
}
this._pos += (int)stream.Length;
}
///
/// Writes the stream unseekable.
///
/// The stream.
private void WriteStreamUnseekable(Stream stream)
{
var memo = ArrayPool.Shared.Rent(4096);
try
{
var br = 0;
while ((br = stream.Read(memo, 0, memo.Length)) != 0)
{
this.EnsureSize(this._pos + br);
memo.AsSpan(0, br).CopyTo(this._buff[this._pos..].Span);
this._pos += br;
}
}
finally
{
ArrayPool.Shared.Return(memo);
}
}
///
public bool Read(Span destination, ulong source, out int itemsWritten)
{
itemsWritten = 0;
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
source *= (ulong)this._itemSize;
if (source > this.Count)
throw new ArgumentOutOfRangeException(nameof(source), "Cannot copy data from beyond the buffer.");
var start = (int)source;
var sbuff = this._buff[start..this._pos ].Span;
var dbuff = MemoryMarshal.AsBytes(destination);
if (sbuff.Length > dbuff.Length)
sbuff = sbuff[..dbuff.Length];
itemsWritten = sbuff.Length / this._itemSize;
sbuff.CopyTo(dbuff);
return this.Length - source != (ulong)itemsWritten;
}
///
public bool Read(T[] data, int start, int count, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(start, count), source, out itemsWritten);
///
public bool Read(ArraySegment data, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(), source, out itemsWritten);
///
public T[] ToArray()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
return MemoryMarshal.Cast(this._buff[..this._pos].Span).ToArray();
}
///
public void CopyTo(Stream destination)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var buff = this._buff[..this._pos].ToArray();
destination.Write(buff, 0, buff.Length);
}
///
public void Clear()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
this._pos = 0;
}
///
/// Disposes of any resources claimed by this buffer.
///
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
if (this._clear)
this._buff.Span.Clear();
this._buffOwner.Dispose();
this._buff = default;
}
///
/// Ensures the size.
///
/// The new capacity.
private void EnsureSize(int newCapacity)
{
var cap = this._buff.Length;
if (cap >= newCapacity)
return;
var factor = newCapacity / cap;
if (newCapacity % cap != 0)
++factor;
var newActualCapacity = cap * factor;
var newBuffOwner = this._pool.Rent(newActualCapacity);
var newBuff = newBuffOwner.Memory;
this._buff.Span.CopyTo(newBuff.Span);
if (this._clear)
this._buff.Span.Clear();
this._buffOwner.Dispose();
this._buffOwner = newBuffOwner;
this._buff = newBuff;
}
}
diff --git a/DisCatSharp.Common/Types/MemoryBuffer.cs b/DisCatSharp.Common/Types/MemoryBuffer.cs
index bc90b25aa..fd91cdd1f 100644
--- a/DisCatSharp.Common/Types/MemoryBuffer.cs
+++ b/DisCatSharp.Common/Types/MemoryBuffer.cs
@@ -1,339 +1,339 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace DisCatSharp.Common.Types;
///
/// Provides a resizable memory buffer, which can be read from and written to. It will automatically resize whenever required.
///
/// Type of item to hold in the buffer.
public sealed class MemoryBuffer : IMemoryBuffer where T : unmanaged
{
///
public ulong Capacity => this._segments.Aggregate(0UL, (a, x) => a + (ulong)x.Memory.Length); // .Sum() does only int
///
public ulong Length { get; private set; }
///
public ulong Count => this.Length / (ulong)this._itemSize;
private readonly MemoryPool _pool;
private readonly int _segmentSize;
private int _lastSegmentLength;
private int _segNo;
private readonly bool _clear;
private readonly List> _segments;
private readonly int _itemSize;
private bool _isDisposed;
///
/// Creates a new buffer with a specified segment size, specified number of initially-allocated segments, and supplied memory pool.
///
/// Byte size of an individual segment. Defaults to 64KiB.
/// Number of segments to allocate. Defaults to 0.
- /// Memory pool to use for renting buffers. Defaults to .
+ /// Memory pool to use for renting buffers. Defaults to .
/// Determines whether the underlying buffers should be cleared on exit. If dealing with sensitive data, it might be a good idea to set this option to true.
public MemoryBuffer(int segmentSize = 65536, int initialSegmentCount = 0, MemoryPool memPool = default, bool clearOnDispose = false)
{
this._itemSize = Unsafe.SizeOf();
if (segmentSize % this._itemSize != 0)
throw new ArgumentException("Segment size must match size of individual item.");
this._pool = memPool ?? MemoryPool.Shared;
this._segmentSize = segmentSize;
this._segNo = 0;
this._lastSegmentLength = 0;
this._clear = clearOnDispose;
this._segments = Enumerable.Range(0, initialSegmentCount)
.Select(x => this._pool.Rent(this._segmentSize))
.ToList();
this.Length = 0;
this._isDisposed = false;
}
///
public void Write(ReadOnlySpan data)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var src = MemoryMarshal.AsBytes(data);
this.Grow(src.Length);
while (this._segNo < this._segments.Count && src.Length > 0)
{
var seg = this._segments[this._segNo];
var mem = seg.Memory;
var avs = mem.Length - this._lastSegmentLength;
avs = avs > src.Length
? src.Length
: avs;
var dmem = mem[this._lastSegmentLength..];
src[..avs].CopyTo(dmem.Span);
src = src[avs..];
this.Length += (ulong)avs;
this._lastSegmentLength += avs;
if (this._lastSegmentLength == mem.Length)
{
this._segNo++;
this._lastSegmentLength = 0;
}
}
}
///
public void Write(T[] data, int start, int count)
=> this.Write(data.AsSpan(start, count));
///
public void Write(ArraySegment data)
=> this.Write(data.AsSpan());
///
public void Write(Stream stream)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
if (stream.CanSeek)
this.WriteStreamSeekable(stream);
else
this.WriteStreamUnseekable(stream);
}
///
/// Writes the stream seekable.
///
/// The stream.
private void WriteStreamSeekable(Stream stream)
{
var len = (int)(stream.Length - stream.Position);
this.Grow(len);
var buff = new byte[this._segmentSize];
while (this._segNo < this._segments.Count && len > 0)
{
var seg = this._segments[this._segNo];
var mem = seg.Memory;
var avs = mem.Length - this._lastSegmentLength;
avs = avs > len
? len
: avs;
var dmem = mem[this._lastSegmentLength..];
var lsl = this._lastSegmentLength;
var slen = dmem.Span.Length - lsl;
stream.Read(buff, 0, slen);
buff.AsSpan(0, slen).CopyTo(dmem.Span);
len -= dmem.Span.Length;
this.Length += (ulong)avs;
this._lastSegmentLength += avs;
if (this._lastSegmentLength == mem.Length)
{
this._segNo++;
this._lastSegmentLength = 0;
}
}
}
///
/// Writes the stream unseekable.
///
/// The stream.
private void WriteStreamUnseekable(Stream stream)
{
var read = 0;
var buff = new byte[this._segmentSize];
var buffs = buff.AsSpan();
while ((read = stream.Read(buff, 0, buff.Length - this._lastSegmentLength)) != 0)
this.Write(MemoryMarshal.Cast(buffs[..read]));
}
///
public bool Read(Span destination, ulong source, out int itemsWritten)
{
itemsWritten = 0;
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
source *= (ulong)this._itemSize;
if (source > this.Count)
throw new ArgumentOutOfRangeException(nameof(source), "Cannot copy data from beyond the buffer.");
// Find where to begin
var i = 0;
for (; i < this._segments.Count; i++)
{
var seg = this._segments[i];
var mem = seg.Memory;
if ((ulong)mem.Length > source)
break;
source -= (ulong)mem.Length;
}
// Do actual copy
var dl = (int)(this.Length - source);
var sri = (int)source;
var dst = MemoryMarshal.AsBytes(destination);
for (; i < this._segments.Count && dst.Length > 0; i++)
{
var seg = this._segments[i];
var mem = seg.Memory;
var src = mem.Span;
if (sri != 0)
{
src = src[sri..];
sri = 0;
}
if (itemsWritten + src.Length > dl)
src = src[..(dl - itemsWritten)];
if (src.Length > dst.Length)
src = src[..dst.Length];
src.CopyTo(dst);
dst = dst[src.Length..];
itemsWritten += src.Length;
}
itemsWritten /= this._itemSize;
return this.Length - source != (ulong)itemsWritten;
}
///
public bool Read(T[] data, int start, int count, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(start, count), source, out itemsWritten);
///
public bool Read(ArraySegment data, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(), source, out itemsWritten);
///
public T[] ToArray()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var bytes = new T[this.Count];
this.Read(bytes, 0, out _);
return bytes;
}
///
public void CopyTo(Stream destination)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var longest = this._segments.Max(x => x.Memory.Length);
var buff = new byte[longest];
foreach (var seg in this._segments)
{
var mem = seg.Memory.Span;
var spn = buff.AsSpan(0, mem.Length);
mem.CopyTo(spn);
destination.Write(buff, 0, spn.Length);
}
}
///
public void Clear()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
this._segNo = 0;
this._lastSegmentLength = 0;
this.Length = 0;
}
///
/// Disposes of any resources claimed by this buffer.
///
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
foreach (var segment in this._segments)
{
if (this._clear)
segment.Memory.Span.Clear();
segment.Dispose();
}
}
///
/// Grows the.
///
/// The min amount.
private void Grow(int minAmount)
{
var capacity = this.Capacity;
var length = this.Length;
var totalAmt = length + (ulong)minAmount;
if (capacity >= totalAmt)
return; // we're good
var amt = (int)(totalAmt - capacity);
var segCount = amt / this._segmentSize;
if (amt % this._segmentSize != 0)
segCount++;
// Basically List.EnsureCapacity
// Default grow behaviour is minimum current*2
var segCap = this._segments.Count + segCount;
if (segCap > this._segments.Capacity)
this._segments.Capacity = segCap < this._segments.Capacity * 2
? this._segments.Capacity * 2
: segCap;
for (var i = 0; i < segCount; i++)
this._segments.Add(this._pool.Rent(this._segmentSize));
}
}
diff --git a/DisCatSharp.Common/Types/SecureRandom.cs b/DisCatSharp.Common/Types/SecureRandom.cs
index 917747b7c..c7a800252 100644
--- a/DisCatSharp.Common/Types/SecureRandom.cs
+++ b/DisCatSharp.Common/Types/SecureRandom.cs
@@ -1,331 +1,331 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace DisCatSharp.Common;
///
-/// Provides a cryptographically-secure pseudorandom number generator (CSPRNG) implementation compatible with .
+/// Provides a cryptographically-secure pseudorandom number generator (CSPRNG) implementation compatible with .
///
public sealed class SecureRandom : Random, IDisposable
{
///
/// Gets the r n g.
///
private readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
private volatile bool _isDisposed;
///
/// Creates a new instance of .
///
public SecureRandom()
{ }
///
/// Finalizes this instance by disposing it.
///
~SecureRandom()
{
this.Dispose();
}
///
/// Fills a supplied buffer with random bytes.
///
/// Buffer to fill with random bytes.
public void GetBytes(byte[] buffer) => this._rng.GetBytes(buffer);
///
/// Fills a supplied buffer with random nonzero bytes.
///
/// Buffer to fill with random nonzero bytes.
public void GetNonZeroBytes(byte[] buffer) => this._rng.GetNonZeroBytes(buffer);
///
/// Fills a supplied memory region with random bytes.
///
/// Memory region to fill with random bytes.
public void GetBytes(Span buffer)
{
var buff = ArrayPool.Shared.Rent(buffer.Length);
try
{
var buffSpan = buff.AsSpan(0, buffer.Length);
this._rng.GetBytes(buff);
buffSpan.CopyTo(buffer);
}
finally
{
ArrayPool.Shared.Return(buff);
}
}
///
/// Fills a supplied memory region with random nonzero bytes.
///
/// Memory region to fill with random nonzero bytes.
public void GetNonZeroBytes(Span buffer)
{
var buff = ArrayPool.Shared.Rent(buffer.Length);
try
{
var buffSpan = buff.AsSpan(0, buffer.Length);
this._rng.GetNonZeroBytes(buff);
buffSpan.CopyTo(buffer);
}
finally
{
ArrayPool.Shared.Return(buff);
}
}
///
/// Generates a signed 8-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public sbyte GetInt8(sbyte min = 0, sbyte max = sbyte.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
var offset = (sbyte)(min < 0 ? -min : 0);
min += offset;
max += offset;
return (sbyte)(Math.Abs(this.Generate()) % (max - min) + min - offset);
}
///
/// Generates a unsigned 8-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public byte GetUInt8(byte min = 0, byte max = byte.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
return (byte)(this.Generate() % (max - min) + min);
}
///
/// Generates a signed 16-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public short GetInt16(short min = 0, short max = short.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
var offset = (short)(min < 0 ? -min : 0);
min += offset;
max += offset;
return (short)(Math.Abs(this.Generate()) % (max - min) + min - offset);
}
///
/// Generates a unsigned 16-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public ushort GetUInt16(ushort min = 0, ushort max = ushort.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
return (ushort)(this.Generate() % (max - min) + min);
}
///
/// Generates a signed 32-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public int GetInt32(int min = 0, int max = int.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
var offset = min < 0 ? -min : 0;
min += offset;
max += offset;
return Math.Abs(this.Generate()) % (max - min) + min - offset;
}
///
/// Generates a unsigned 32-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public uint GetUInt32(uint min = 0, uint max = uint.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
return this.Generate() % (max - min) + min;
}
///
/// Generates a signed 64-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public long GetInt64(long min = 0, long max = long.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
var offset = min < 0 ? -min : 0;
min += offset;
max += offset;
return Math.Abs(this.Generate()) % (max - min) + min - offset;
}
///
/// Generates a unsigned 64-bit integer within specified range.
///
/// Minimum value to generate. Defaults to 0.
/// Maximum value to generate. Defaults to .
/// Generated random value.
public ulong GetUInt64(ulong min = 0, ulong max = ulong.MaxValue)
{
if (max <= min)
throw new ArgumentException("Maximum needs to be greater than minimum.", nameof(max));
return this.Generate() % (max - min) + min;
}
///
/// Generates a 32-bit floating-point number between 0.0 and 1.0.
///
/// Generated 32-bit floating-point number.
public float GetSingle()
{
var (i1, i2) = ((float)this.GetInt32(), (float)this.GetInt32());
return i1 / i2 % 1.0F;
}
///
/// Generates a 64-bit floating-point number between 0.0 and 1.0.
///
/// Generated 64-bit floating-point number.
public double GetDouble()
{
var (i1, i2) = ((double)this.GetInt64(), (double)this.GetInt64());
return i1 / i2 % 1.0;
}
///
/// Generates a 32-bit integer between 0 and . Upper end exclusive.
///
/// Generated 32-bit integer.
public override int Next()
=> this.GetInt32();
///
/// Generates a 32-bit integer between 0 and . Upper end exclusive.
///
/// Maximum value of the generated integer.
/// Generated 32-bit integer.
public override int Next(int maxValue)
=> this.GetInt32(0, maxValue);
///
/// Generates a 32-bit integer between and . Upper end exclusive.
///
/// Minimum value of the generate integer.
/// Maximum value of the generated integer.
/// Generated 32-bit integer.
public override int Next(int minValue, int maxValue)
=> this.GetInt32(minValue, maxValue);
///
/// Generates a 64-bit floating-point number between 0.0 and 1.0. Upper end exclusive.
///
/// Generated 64-bit floating-point number.
public override double NextDouble()
=> this.GetDouble();
///
/// Fills specified buffer with random bytes.
///
/// Buffer to fill with bytes.
public override void NextBytes(byte[] buffer)
=> this.GetBytes(buffer);
///
/// Fills specified memory region with random bytes.
///
/// Memory region to fill with bytes.
public new void NextBytes(Span buffer)
=> this.GetBytes(buffer);
///
/// Disposes this instance and its resources.
///
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
this._rng.Dispose();
GC.SuppressFinalize(this);
}
///
/// Generates a random 64-bit floating-point number between 0.0 and 1.0. Upper end exclusive.
///
/// Generated 64-bit floating-point number.
protected override double Sample()
=> this.GetDouble();
///
/// Generates the.
///
/// A T.
private T Generate() where T : struct
{
var size = Unsafe.SizeOf();
Span buff = stackalloc byte[size];
this.GetBytes(buff);
return MemoryMarshal.Read(buff);
}
}
diff --git a/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs b/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs
index dc7f30976..b6c5e5e39 100644
--- a/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs
+++ b/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs
@@ -1,114 +1,114 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Numerics;
namespace DisCatSharp.Common.Serialization;
///
-/// Decomposes numbers into tuples (arrays of 2).
+/// Decomposes numbers into tuples (arrays of 2).
///
public sealed class ComplexDecomposer : IDecomposer
{
///
/// Gets the t complex.
///
private static Type s_complex { get; } = typeof(Complex);
///
/// Gets the t double array.
///
private static Type s_doubleArray { get; } = typeof(double[]);
///
/// Gets the t double enumerable.
///
private static Type s_doubleEnumerable { get; } = typeof(IEnumerable);
///
/// Gets the t object array.
///
private static Type s_objectArray { get; } = typeof(object[]);
///
/// Gets the t object enumerable.
///
private static Type s_objectEnumerable { get; } = typeof(IEnumerable);
///
public bool CanDecompose(Type t)
=> t == s_complex;
///
public bool CanRecompose(Type t)
=> t == s_doubleArray
|| t == s_objectArray
|| s_doubleEnumerable.IsAssignableFrom(t)
|| s_objectEnumerable.IsAssignableFrom(t);
///
public bool TryDecompose(object obj, Type tobj, out object decomposed, out Type tdecomposed)
{
decomposed = null;
tdecomposed = s_doubleArray;
if (tobj != s_complex || obj is not Complex c)
return false;
decomposed = new[] { c.Real, c.Imaginary };
return true;
}
///
public bool TryRecompose(object obj, Type tobj, Type trecomposed, out object recomposed)
{
recomposed = null;
if (trecomposed != s_complex)
return false;
// ie
if (s_doubleEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable ied)
{
if (!ied.TryFirstTwo(out var values))
return false;
var (real, imag) = values;
recomposed = new Complex(real, imag);
return true;
}
// ie
if (s_objectEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable ieo)
{
if (!ieo.TryFirstTwo(out var values))
return false;
var (real, imag) = values;
if (real is not double dreal || imag is not double dimag)
return false;
recomposed = new Complex(dreal, dimag);
return true;
}
return false;
}
}
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
index 55496f243..d838d23af 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
@@ -1,199 +1,199 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace DisCatSharp.Common.Utilities;
///
/// ABC for , allowing for using instances thereof without knowing the underlying instance's type parameters.
///
public abstract class AsyncEvent
{
///
/// Gets the name of this event.
///
public string Name { get; }
///
/// Prevents a default instance of the class from being created.
///
/// The name.
private protected AsyncEvent(string name)
{
this.Name = name;
}
}
///
/// Implementation of asynchronous event. The handlers of such events are executed asynchronously, but sequentially.
///
/// Type of the object that dispatches this event.
/// Type of event argument object passed to this event's handlers.
public sealed class AsyncEvent : AsyncEvent
where TArgs : AsyncEventArgs
{
///
- /// Gets the maximum allotted execution time for all handlers. Any event which causes the handler to time out
+ /// Gets the maximum allotted execution time for all handlers. Any event which causes the handler to time out
/// will raise a non-fatal .
///
public TimeSpan MaximumExecutionTime { get; }
private readonly object _lock = new();
private ImmutableArray> _handlers;
private readonly AsyncEventExceptionHandler _exceptionHandler;
///
/// Creates a new asynchronous event with specified name and exception handler.
///
/// Name of this event.
- /// Maximum handler execution time. A value of means infinite.
+ /// Maximum handler execution time. A value of means infinite.
/// Delegate which handles exceptions caused by this event.
public AsyncEvent(string name, TimeSpan maxExecutionTime, AsyncEventExceptionHandler exceptionHandler)
: base(name)
{
this._handlers = ImmutableArray>.Empty;
this._exceptionHandler = exceptionHandler;
this.MaximumExecutionTime = maxExecutionTime;
}
///
/// Registers a new handler for this event.
///
/// Handler to register for this event.
public void Register(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this._lock)
this._handlers = this._handlers.Add(handler);
}
///
/// Unregisters an existing handler from this event.
///
/// Handler to unregister from the event.
public void Unregister(AsyncEventHandler handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this._lock)
this._handlers = this._handlers.Remove(handler);
}
///
/// Unregisters all existing handlers from this event.
///
public void UnregisterAll() => this._handlers = ImmutableArray>.Empty;
///
/// Raises this event by invoking all of its registered handlers, in order of registration.
/// All exceptions throw during invocation will be handled by the event's registered exception handler.
///
/// Object which raised this event.
/// Arguments for this event.
/// Defines what to do with exceptions caught from handlers.
///
public async Task InvokeAsync(TSender sender, TArgs e, AsyncEventExceptionMode exceptionMode = AsyncEventExceptionMode.Default)
{
var handlers = this._handlers;
if (handlers.Length == 0)
return;
// Collect exceptions
List exceptions = null;
if ((exceptionMode & AsyncEventExceptionMode.ThrowAll) != 0)
exceptions = new List(handlers.Length * 2 /* timeout + regular */);
// If we have a timeout configured, start the timeout task
var timeout = this.MaximumExecutionTime > TimeSpan.Zero ? Task.Delay(this.MaximumExecutionTime) : null;
foreach (var handler in handlers)
{
try
{
// Start the handler execution
var handlerTask = handler(sender, e);
if (handlerTask != null && timeout != null)
{
// If timeout is configured, wait for any task to finish
// If the timeout task finishes first, the handler is causing a timeout
var result = await Task.WhenAny(timeout, handlerTask).ConfigureAwait(false);
if (result == timeout)
{
timeout = null;
var timeoutEx = new AsyncEventTimeoutException(this, handler);
// Notify about the timeout and complete execution
if ((exceptionMode & AsyncEventExceptionMode.HandleNonFatal) == AsyncEventExceptionMode.HandleNonFatal)
this.HandleException(timeoutEx, handler, sender, e);
if ((exceptionMode & AsyncEventExceptionMode.ThrowNonFatal) == AsyncEventExceptionMode.ThrowNonFatal)
exceptions.Add(timeoutEx);
await handlerTask.ConfigureAwait(false);
}
}
else if (handlerTask != null)
{
// No timeout is configured, or timeout already expired, proceed as usual
await handlerTask.ConfigureAwait(false);
}
if (e.Handled)
break;
}
catch (Exception ex)
{
e.Handled = false;
if ((exceptionMode & AsyncEventExceptionMode.HandleFatal) == AsyncEventExceptionMode.HandleFatal)
this.HandleException(ex, handler, sender, e);
if ((exceptionMode & AsyncEventExceptionMode.ThrowFatal) == AsyncEventExceptionMode.ThrowFatal)
exceptions.Add(ex);
}
}
if ((exceptionMode & AsyncEventExceptionMode.ThrowAll) != 0 && exceptions.Count > 0)
throw new AggregateException("Exceptions were thrown during execution of the event's handlers.", exceptions);
}
///
/// Handles the exception.
///
/// The ex.
/// The handler.
/// The sender.
/// The args.
private void HandleException(Exception ex, AsyncEventHandler handler, TSender sender, TArgs args)
{
if (this._exceptionHandler != null)
this._exceptionHandler(this, ex, handler, sender, args);
}
}
diff --git a/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs b/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
index 1dd49f83d..129a9fdb1 100644
--- a/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
+++ b/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System.Threading;
using System.Threading.Tasks;
namespace DisCatSharp.Common.Utilities;
///
-/// Represents a thread synchronization event that, when signaled, must be reset manually. Unlike , this event is asynchronous.
+/// Represents a thread synchronization event that, when signaled, must be reset manually. Unlike , this event is asynchronous.
///
public sealed class AsyncManualResetEvent
{
///
/// Gets whether this event has been signaled.
///
public bool IsSet => this._resetTcs?.Task?.IsCompleted == true;
private volatile TaskCompletionSource _resetTcs;
///
/// Creates a new asynchronous synchronization event with initial state.
///
/// Initial state of this event.
public AsyncManualResetEvent(bool initialState)
{
this._resetTcs = new TaskCompletionSource();
if (initialState)
this._resetTcs.TrySetResult(initialState);
}
// Spawn a threadpool thread instead of making a task
// Maybe overkill, but I am less unsure of this than awaits and
// potentially cross-scheduler interactions
///
/// Asynchronously signal this event.
///
///
public Task SetAsync()
=> Task.Run(() => this._resetTcs.TrySetResult(true));
///
/// Asynchronously wait for this event to be signaled.
///
///
public Task WaitAsync()
=> this._resetTcs.Task;
///
/// Reset this event's signal state to unsignaled.
///
public void Reset()
{
while (true)
{
var tcs = this._resetTcs;
if (!tcs.Task.IsCompleted || Interlocked.CompareExchange(ref this._resetTcs, new TaskCompletionSource(), tcs) == tcs)
return;
}
}
}
diff --git a/DisCatSharp.Common/Utilities/Extensions.cs b/DisCatSharp.Common/Utilities/Extensions.cs
index 7777aa83f..ce4dee7a5 100644
--- a/DisCatSharp.Common/Utilities/Extensions.cs
+++ b/DisCatSharp.Common/Utilities/Extensions.cs
@@ -1,530 +1,530 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace DisCatSharp.Common;
///
/// Assortment of various extension and utility methods, designed to make working with various types a little easier.
///
public static class Extensions
{
///
- /// Deconstructs a key-value pair item ( ) into 2 separate variables.
- /// This allows for enumerating over dictionaries in foreach blocks by using a (k, v) tuple as the enumerator variable, instead of having to use a directly.
+ /// Deconstructs a key-value pair item ( ) into 2 separate variables.
+ /// This allows for enumerating over dictionaries in foreach blocks by using a (k, v) tuple as the enumerator variable, instead of having to use a directly.
///
/// Type of dictionary item key.
/// Type of dictionary item value.
/// Key-value pair to deconstruct.
/// Deconstructed key.
/// Deconstructed value.
public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value)
{
key = kvp.Key;
value = kvp.Value;
}
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this sbyte num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == sbyte.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this byte num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this short num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == short.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this ushort num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this int num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == int.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this uint num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this long num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == long.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
///
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
///
/// Number to calculate the length of.
/// Calculated number length.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this ulong num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this sbyte num, sbyte min, sbyte max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this byte num, byte min, byte max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this short num, short min, short max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this ushort num, ushort min, ushort max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this int num, int min, int max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this uint num, uint min, uint max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this long num, long min, long max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this ulong num, ulong min, ulong max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this float num, float min, float max, bool inclusive = true)
{
if (min > max)
return false;
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
///
/// Number to test.
/// Lower bound of the range.
/// Upper bound of the range.
/// Whether the check is to be inclusive.
/// Whether the value is in range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this double num, double min, double max, bool inclusive = true)
{
if (min > max)
return false;
return inclusive ? num >= min && num <= max : num > min && num < max;
}
///
/// Returns whether supplied character is in any of the following ranges: a-z, A-Z, 0-9.
///
/// Character to test.
/// Whether the character is in basic alphanumeric character range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicAlphanumeric(this char c)
=> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
///
/// Returns whether supplied character is in the 0-9 range.
///
/// Character to test.
/// Whether the character is in basic numeric digit character range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicDigit(this char c)
=> c >= '0' && c <= '9';
///
/// Returns whether supplied character is in the a-z or A-Z range.
///
/// Character to test.
/// Whether the character is in basic letter character range.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicLetter(this char c)
=> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
///
/// Tests whether given string ends with given character.
///
/// String to test.
/// Character to test for.
/// Whether the supplied string ends with supplied character.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWithCharacter(this string s, char c)
=> s.Length >= 1 && s[^1] == c;
///
/// Tests whether given string starts with given character.
///
/// String to test.
/// Character to test for.
/// Whether the supplied string starts with supplied character.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWithCharacter(this string s, char c)
=> s.Length >= 1 && s[0] == c;
// https://stackoverflow.com/questions/9545619/a-fast-hash-function-for-string-in-c-sharp
// Calls are inlined to call the underlying method directly
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ReadOnlySpan chars)
=> Knuth(chars);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this Span chars)
=> Knuth(chars);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ReadOnlyMemory chars)
=> Knuth(chars.Span);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this Memory chars)
=> Knuth(chars.Span);
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ArraySegment chars)
=> Knuth(chars.AsSpan());
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this char[] chars)
=> Knuth(chars.AsSpan());
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Offset in the array to start calculating from.
/// Number of characters to compute the hash from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this char[] chars, int start, int count)
=> Knuth(chars.AsSpan(start, count));
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this string chars)
=> Knuth(chars.AsSpan());
///
/// Computes a 64-bit Knuth hash from supplied characters.
///
/// Characters to compute the hash value from.
/// Offset in the array to start calculating from.
/// Number of characters to compute the hash from.
/// Computer 64-bit Knuth hash.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this string chars, int start, int count)
=> Knuth(chars.AsSpan(start, count));
///
/// Gets the two first elements of the , if they exist.
///
/// The enumerable.
/// The output values. Undefined if false
is returned.
/// Whether the contained enough elements.
internal static bool TryFirstTwo(this IEnumerable enumerable, out (T first, T second) values)
{
values = default;
using var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
return false;
var first = enumerator.Current;
if (!enumerator.MoveNext())
return false;
values = (first, enumerator.Current);
return true;
}
///
/// Knuths the.
///
/// The chars.
/// An ulong.
private static ulong Knuth(ReadOnlySpan chars)
{
var hash = 3074457345618258791ul;
foreach (var ch in chars)
hash = (hash + ch) * 3074457345618258799ul;
return hash;
}
///
/// Removes the first item matching the predicate from the list.
///
///
///
///
/// Whether an item was removed.
internal static bool RemoveFirst(this IList list, Predicate predicate)
{
for (var i = 0; i < list.Count; i++)
{
if (predicate(list[i]))
{
list.RemoveAt(i);
return true;
}
}
return false;
}
///
/// Populates an array with the given value.
///
///
///
///
internal static void Populate(this T[] arr, T value)
{
for (var i = 0; i < arr.Length; i++)
{
arr[i] = value;
}
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs
index dd2856620..5cc48ff5a 100644
--- a/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs
@@ -1,320 +1,320 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Interactivity.Enums;
namespace DisCatSharp.Interactivity.EventHandling
{
///
/// The pagination request.
///
internal class PaginationRequest : IPaginationRequest
{
private TaskCompletionSource _tcs;
private readonly CancellationTokenSource _ct;
private readonly TimeSpan _timeout;
private readonly List _pages;
private readonly PaginationBehaviour _behaviour;
private readonly DiscordMessage _message;
private readonly PaginationEmojis _emojis;
private readonly DiscordUser _user;
private int _index;
///
/// Creates a new Pagination request
///
/// Message to paginate
/// User to allow control for
/// Behaviour during pagination
/// Behavior on pagination end
/// Emojis for this pagination object
/// Timeout time
/// Pagination pages
internal PaginationRequest(DiscordMessage message, DiscordUser user, PaginationBehaviour behaviour, PaginationDeletion deletion,
PaginationEmojis emojis, TimeSpan timeout, IEnumerable pages)
{
this._tcs = new TaskCompletionSource();
this._ct = new CancellationTokenSource(timeout);
this._ct.Token.Register(() => this._tcs.TrySetResult(true));
this._timeout = timeout;
this._message = message;
this._user = user;
this.PaginationDeletion = deletion;
this._behaviour = behaviour;
this._emojis = emojis;
this._pages = new List();
foreach (var p in pages)
{
this._pages.Add(p);
}
}
///
/// Gets the page count.
///
public int PageCount => this._pages.Count;
///
/// Gets the pagination deletion.
///
public PaginationDeletion PaginationDeletion { get; }
///
/// Gets the page async.
///
/// A Task.
public async Task GetPageAsync()
{
await Task.Yield();
return this._pages[this._index];
}
///
/// Skips the left async.
///
/// A Task.
public async Task SkipLeftAsync()
{
await Task.Yield();
this._index = 0;
}
///
/// Skips the right async.
///
/// A Task.
public async Task SkipRightAsync()
{
await Task.Yield();
this._index = this._pages.Count - 1;
}
///
/// Nexts the page async.
///
/// A Task.
public async Task NextPageAsync()
{
await Task.Yield();
switch (this._behaviour)
{
case PaginationBehaviour.Ignore:
if (this._index == this._pages.Count - 1)
break;
else
this._index++;
break;
case PaginationBehaviour.WrapAround:
if (this._index == this._pages.Count - 1)
this._index = 0;
else
this._index++;
break;
}
}
///
/// Previous the page async.
///
/// A Task.
public async Task PreviousPageAsync()
{
await Task.Yield();
switch (this._behaviour)
{
case PaginationBehaviour.Ignore:
if (this._index == 0)
break;
else
this._index--;
break;
case PaginationBehaviour.WrapAround:
if (this._index == 0)
this._index = this._pages.Count - 1;
else
this._index--;
break;
}
}
///
/// Gets the buttons async.
///
- ///
+ ///
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task> GetButtonsAsync()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
=> throw new NotSupportedException("This request does not support buttons.");
///
/// Gets the emojis async.
///
/// A Task.
public async Task GetEmojisAsync()
{
await Task.Yield();
return this._emojis;
}
///
/// Gets the message async.
///
/// A Task.
public async Task GetMessageAsync()
{
await Task.Yield();
return this._message;
}
///
/// Gets the user async.
///
/// A Task.
public async Task GetUserAsync()
{
await Task.Yield();
return this._user;
}
///
/// Dos the cleanup async.
///
/// A Task.
public async Task DoCleanupAsync()
{
switch (this.PaginationDeletion)
{
case PaginationDeletion.DeleteEmojis:
await this._message.DeleteAllReactionsAsync().ConfigureAwait(false);
break;
case PaginationDeletion.DeleteMessage:
await this._message.DeleteAsync().ConfigureAwait(false);
break;
case PaginationDeletion.KeepEmojis:
break;
}
}
///
/// Gets the task completion source async.
///
/// A Task.
public async Task> GetTaskCompletionSourceAsync()
{
await Task.Yield();
return this._tcs;
}
~PaginationRequest()
{
this.Dispose();
}
///
/// Disposes this PaginationRequest.
///
public void Dispose()
{
this._ct.Dispose();
this._tcs = null;
}
}
}
namespace DisCatSharp.Interactivity
{
///
/// The pagination emojis.
///
public class PaginationEmojis
{
public DiscordEmoji SkipLeft;
public DiscordEmoji SkipRight;
public DiscordEmoji Left;
public DiscordEmoji Right;
public DiscordEmoji Stop;
///
/// Initializes a new instance of the class.
///
public PaginationEmojis()
{
this.Left = DiscordEmoji.FromUnicode("◀");
this.Right = DiscordEmoji.FromUnicode("▶");
this.SkipLeft = DiscordEmoji.FromUnicode("⏮");
this.SkipRight = DiscordEmoji.FromUnicode("⏭");
this.Stop = DiscordEmoji.FromUnicode("⏹");
}
}
///
/// The page.
///
public class Page
{
///
/// Gets or sets the content.
///
public string Content { get; set; }
///
/// Gets or sets the embed.
///
public DiscordEmbed Embed { get; set; }
///
/// Initializes a new instance of the class.
///
/// The content.
/// The embed.
public Page(string content = "", DiscordEmbedBuilder embed = null)
{
this.Content = content;
this.Embed = embed?.Build();
}
}
}
diff --git a/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs b/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs
index 2f9e96daa..5710cd186 100644
--- a/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs
+++ b/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs
@@ -1,150 +1,150 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using DisCatSharp.Interactivity.EventHandling;
namespace DisCatSharp.Interactivity.Extensions;
///
/// Interactivity extension methods for .
///
public static class ChannelExtensions
{
///
/// Waits for the next message sent in this channel that satisfies the predicate.
///
/// The channel to monitor.
/// A predicate that should return if a message matches.
/// Overrides the timeout set in
- /// Thrown if interactivity is not enabled for the client associated with the channel.
+ /// Thrown if interactivity is not enabled for the client associated with the channel.
public static Task> GetNextMessageAsync(this DiscordChannel channel, Func predicate, TimeSpan? timeoutOverride = null)
=> GetInteractivity(channel).WaitForMessageAsync(msg => msg.ChannelId == channel.Id && predicate(msg), timeoutOverride);
///
/// Waits for the next message sent in this channel.
///
/// The channel to monitor.
/// Overrides the timeout set in
- /// Thrown if interactivity is not enabled for the client associated with the channel.
+ /// Thrown if interactivity is not enabled for the client associated with the channel.
public static Task> GetNextMessageAsync(this DiscordChannel channel, TimeSpan? timeoutOverride = null)
=> channel.GetNextMessageAsync(msg => true, timeoutOverride);
///
/// Waits for the next message sent in this channel from a specific user.
///
/// The channel to monitor.
/// The target user.
/// Overrides the timeout set in
- /// Thrown if interactivity is not enabled for the client associated with the channel.
+ /// Thrown if interactivity is not enabled for the client associated with the channel.
public static Task> GetNextMessageAsync(this DiscordChannel channel, DiscordUser user, TimeSpan? timeoutOverride = null)
=> channel.GetNextMessageAsync(msg => msg.Author.Id == user.Id, timeoutOverride);
///
/// Waits for a specific user to start typing in this channel.
///
/// The target channel.
/// The target user.
/// Overrides the timeout set in
- /// Thrown if interactivity is not enabled for the client associated with the channel.
+ /// Thrown if interactivity is not enabled for the client associated with the channel.
public static Task> WaitForUserTypingAsync(this DiscordChannel channel, DiscordUser user, TimeSpan? timeoutOverride = null)
=> GetInteractivity(channel).WaitForUserTypingAsync(user, channel, timeoutOverride);
///
/// Sends a new paginated message.
///
/// Target channel.
/// The user that will be able to control the pages.
/// A collection of to display.
/// Pagination emojis.
/// Pagination behaviour (when hitting max and min indices).
/// Deletion behaviour.
/// Override timeout period.
- /// Thrown if interactivity is not enabled for the client associated with the channel.
+ /// Thrown if interactivity is not enabled for the client associated with the channel.
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationEmojis emojis, PaginationBehaviour? behaviour = default, PaginationDeletion? deletion = default, TimeSpan? timeoutOverride = null)
=> GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, emojis, behaviour, deletion, timeoutOverride);
///
/// Sends a new paginated message with buttons.
///
/// Target channel.
/// The user that will be able to control the pages.
/// A collection of to display.
/// Pagination buttons (leave null to default to ones on configuration).
/// Pagination behaviour.
/// Deletion behaviour
/// A custom cancellation token that can be cancelled at any point.
- /// Thrown if interactivity is not enabled for the client associated with the channel.
+ /// Thrown if interactivity is not enabled for the client associated with the channel.
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationButtons buttons, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default)
=> GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, buttons, behaviour, deletion, token);
///
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default)
=> channel.SendPaginatedMessageAsync(user, pages, default, behaviour, deletion, token);
///
/// Sends a new paginated message with buttons.
///
/// Target channel.
/// The user that will be able to control the pages.
/// A collection of to display.
/// Pagination buttons (leave null to default to ones on configuration).
/// Pagination behaviour.
/// Deletion behaviour.
/// Override timeout period.
- /// Thrown if interactivity is not enabled for the client associated with the channel.
+ /// Thrown if interactivity is not enabled for the client associated with the channel.
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, PaginationButtons buttons, TimeSpan? timeoutOverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default)
=> GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, buttons, timeoutOverride, behaviour, deletion);
///
/// Sends the paginated message async.
///
/// The channel.
/// The user.
/// The pages.
/// Override timeout period.
/// The behaviour.
/// The deletion.
/// A Task.
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable pages, TimeSpan? timeoutOverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default)
=> channel.SendPaginatedMessageAsync(user, pages, default, timeoutOverride, behaviour, deletion);
///
/// Retrieves an interactivity instance from a channel instance.
///
private static InteractivityExtension GetInteractivity(DiscordChannel channel)
{
var client = (DiscordClient)channel.Discord;
var interactivity = client.GetInteractivity();
return interactivity ?? throw new InvalidOperationException($"Interactivity is not enabled for this {(client.IsShard ? "shard" : "client")}.");
}
}
diff --git a/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs b/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs
index 036fe3757..80279d85e 100644
--- a/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs
+++ b/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs
@@ -1,99 +1,99 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace DisCatSharp.Interactivity.Extensions;
///
/// Interactivity extension methods for and .
///
public static class ClientExtensions
{
///
/// Enables interactivity for this instance.
///
/// The client to enable interactivity for.
/// A configuration instance. Default configuration values will be used if none is provided.
/// A brand new instance.
- /// Thrown if interactivity has already been enabled for the client instance.
+ /// Thrown if interactivity has already been enabled for the client instance.
public static InteractivityExtension UseInteractivity(this DiscordClient client, InteractivityConfiguration configuration = null)
{
if (client.GetExtension() != null) throw new InvalidOperationException($"Interactivity is already enabled for this {(client.IsShard ? "shard" : "client")}.");
configuration ??= new InteractivityConfiguration();
var extension = new InteractivityExtension(configuration);
client.AddExtension(extension);
return extension;
}
///
/// Enables interactivity for each shard.
///
/// The shard client to enable interactivity for.
/// Configuration to use for all shards. If one isn't provided, default configuration values will be used.
/// A dictionary containing new instances for each shard.
public static async Task> UseInteractivityAsync(this DiscordShardedClient client, InteractivityConfiguration configuration = null)
{
var extensions = new Dictionary();
await client.InitializeShardsAsync().ConfigureAwait(false);
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
var extension = shard.GetExtension() ?? shard.UseInteractivity(configuration);
extensions.Add(shard.ShardId, extension);
}
return new ReadOnlyDictionary(extensions);
}
///
/// Retrieves the registered instance for this client.
///
/// The client to retrieve an instance from.
/// An existing instance, or if interactivity is not enabled for the instance.
public static InteractivityExtension GetInteractivity(this DiscordClient client)
=> client.GetExtension();
///
/// Retrieves a instance for each shard.
///
/// The shard client to retrieve interactivity instances from.
/// A dictionary containing instances for each shard.
public static async Task> GetInteractivityAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync().ConfigureAwait(false);
var extensions = new Dictionary();
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
extensions.Add(shard.ShardId, shard.GetExtension());
}
return new ReadOnlyDictionary(extensions);
}
}
diff --git a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
index 215cb40a3..735170684 100644
--- a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
+++ b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
@@ -1,243 +1,243 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
// Copyright (c) 2021-2022 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using DisCatSharp.Interactivity.EventHandling;
namespace DisCatSharp.Interactivity.Extensions;
///
/// Interactivity extension methods for .
///
public static class MessageExtensions
{
///
/// Waits for the next message that has the same author and channel as this message.
///
/// Original message.
/// Overrides the timeout set in
public static Task> GetNextMessageAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> message.Channel.GetNextMessageAsync(message.Author, timeoutOverride);
///
/// Waits for the next message with the same author and channel as this message, which also satisfies a predicate.
///
/// Original message.
/// A predicate that should return if a message matches.
/// Overrides the timeout set in
public static Task> GetNextMessageAsync(this DiscordMessage message, Func predicate, TimeSpan? timeoutOverride = null)
=> message.Channel.GetNextMessageAsync(msg => msg.Author.Id == message.Author.Id && message.ChannelId == msg.ChannelId && predicate(msg), timeoutOverride);
///
/// Waits for any button to be pressed on the specified message.
///
/// The message to wait on.
public static Task> WaitForButtonAsync(this DiscordMessage message)
=> GetInteractivity(message).WaitForButtonAsync(message);
///
/// Waits for any button to be pressed on the specified message.
///
/// The message to wait on.
/// Overrides the timeout set in
public static Task> WaitForButtonAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, timeoutOverride);
///
/// Waits for any button to be pressed on the specified message.
///
/// The message to wait on.
/// A custom cancellation token that can be cancelled at any point.
public static Task> WaitForButtonAsync(this DiscordMessage message, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, token);
///
/// Waits for a button with the specified Id to be pressed on the specified message.
///
/// The message to wait on.
/// The Id of the button to wait for.
/// Overrides the timeout set in
public static Task> WaitForButtonAsync(this DiscordMessage message, string id, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, id, timeoutOverride);
///
/// Waits for a button with the specified Id to be pressed on the specified message.
///
/// The message to wait on.
/// The Id of the button to wait for.
/// A custom cancellation token that can be cancelled at any point.
public static Task> WaitForButtonAsync(this DiscordMessage message, string id, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, id, token);
///
/// Waits for any button to be pressed on the specified message by the specified user.
///
/// The message to wait on.
/// The user to wait for button input from.
/// Overrides the timeout set in
public static Task> WaitForButtonAsync(this DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, user, timeoutOverride);
///