diff --git a/DisCatSharp.CommandsNext/CommandsNextUtilities.cs b/DisCatSharp.CommandsNext/CommandsNextUtilities.cs
index 7873c9b92..4d04b9874 100644
--- a/DisCatSharp.CommandsNext/CommandsNextUtilities.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextUtilities.cs
@@ -1,431 +1,432 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DisCatSharp.CommandsNext.Attributes;
using DisCatSharp.CommandsNext.Converters;
using DisCatSharp.Entities;
+using DisCatSharp.Common.RegularExpressions;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.CommandsNext
{
///
/// Various CommandsNext-related utilities.
///
public static class CommandsNextUtilities
{
///
/// Gets the user regex.
///
- private static Regex UserRegex { get; } = new Regex(@"<@\!?(\d+?)> ", RegexOptions.ECMAScript);
+ private static Regex UserRegex { get; } = DiscordRegEx.User;
///
/// Checks whether the message has a specified string prefix.
///
/// Message to check.
/// String to check for.
/// Method of string comparison for the purposes of finding prefixes.
/// Positive number if the prefix is present, -1 otherwise.
public static int GetStringPrefixLength(this DiscordMessage msg, string str, StringComparison comparisonType = StringComparison.Ordinal)
{
var content = msg.Content;
return str.Length >= content.Length ? -1 : !content.StartsWith(str, comparisonType) ? -1 : str.Length;
}
///
/// Checks whether the message contains a specified mention prefix.
///
/// Message to check.
/// User to check for.
/// Positive number if the prefix is present, -1 otherwise.
public static int GetMentionPrefixLength(this DiscordMessage msg, DiscordUser user)
{
var content = msg.Content;
if (!content.StartsWith("<@"))
return -1;
var cni = content.IndexOf('>');
if (cni == -1 || content.Length <= cni + 2)
return -1;
var cnp = content.Substring(0, cni + 2);
var m = UserRegex.Match(cnp);
if (!m.Success)
return -1;
var userId = ulong.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
return user.Id != userId ? -1 : m.Value.Length;
}
//internal static string ExtractNextArgument(string str, out string remainder)
///
/// Extracts the next argument.
///
/// The string.
/// The start position.
internal static string ExtractNextArgument(this string str, ref int startPos)
{
if (string.IsNullOrWhiteSpace(str))
return null;
var inBacktick = false;
var inTripleBacktick = false;
var inQuote = false;
var inEscape = false;
var removeIndices = new List(str.Length - startPos);
var i = startPos;
for (; i < str.Length; i++)
if (!char.IsWhiteSpace(str[i]))
break;
startPos = i;
var endPosition = -1;
var startPosition = startPos;
for (i = startPosition; i < str.Length; i++)
{
if (char.IsWhiteSpace(str[i]) && !inQuote && !inTripleBacktick && !inBacktick && !inEscape)
endPosition = i;
if (str[i] == '\\' && str.Length > i + 1)
{
if (!inEscape && !inBacktick && !inTripleBacktick)
{
inEscape = true;
if (str.IndexOf("\\`", i) == i || str.IndexOf("\\\"", i) == i || str.IndexOf("\\\\", i) == i || (str.Length >= i && char.IsWhiteSpace(str[i + 1])))
removeIndices.Add(i - startPosition);
i++;
}
else if ((inBacktick || inTripleBacktick) && str.IndexOf("\\`", i) == i)
{
inEscape = true;
removeIndices.Add(i - startPosition);
i++;
}
}
if (str[i] == '`' && !inEscape)
{
var tripleBacktick = str.IndexOf("```", i) == i;
if (inTripleBacktick && tripleBacktick)
{
inTripleBacktick = false;
i += 2;
}
else if (!inBacktick && tripleBacktick)
{
inTripleBacktick = true;
i += 2;
}
if (inBacktick && !tripleBacktick)
inBacktick = false;
else if (!inTripleBacktick && tripleBacktick)
inBacktick = true;
}
if (str[i] == '"' && !inEscape && !inBacktick && !inTripleBacktick)
{
removeIndices.Add(i - startPosition);
inQuote = !inQuote;
}
if (inEscape)
inEscape = false;
if (endPosition != -1)
{
startPos = endPosition;
return startPosition != endPosition ? str.Substring(startPosition, endPosition - startPosition).CleanupString(removeIndices) : null;
}
}
startPos = str.Length;
return startPos != startPosition ? str.Substring(startPosition).CleanupString(removeIndices) : null;
}
///
/// Cleanups the string.
///
/// The string.
/// The indices.
internal static string CleanupString(this string s, IList indices)
{
if (!indices.Any())
return s;
var li = indices.Last();
var ll = 1;
for (var x = indices.Count - 2; x >= 0; x--)
{
if (li - indices[x] == ll)
{
ll++;
continue;
}
s = s.Remove(li - ll + 1, ll);
li = indices[x];
ll = 1;
}
return s.Remove(li - ll + 1, ll);
}
#pragma warning disable IDE1006 // Naming Styles
///
/// Binds the arguments.
///
/// The command context.
/// If true, ignore further text in string.
internal static async Task BindArguments(CommandContext ctx, bool ignoreSurplus)
#pragma warning restore IDE1006 // Naming Styles
{
var command = ctx.Command;
var overload = ctx.Overload;
var args = new object[overload.Arguments.Count + 2];
args[1] = ctx;
var rawArgumentList = new List(overload.Arguments.Count);
var argString = ctx.RawArgumentString;
var foundAt = 0;
var argValue = "";
for (var i = 0; i < overload.Arguments.Count; i++)
{
var arg = overload.Arguments[i];
if (arg.IsCatchAll)
{
if (arg.IsArray)
{
while (true)
{
argValue = ExtractNextArgument(argString, ref foundAt);
if (argValue == null)
break;
rawArgumentList.Add(argValue);
}
break;
}
else
{
if (argString == null)
break;
argValue = argString.Substring(foundAt).Trim();
argValue = argValue == "" ? null : argValue;
foundAt = argString.Length;
rawArgumentList.Add(argValue);
break;
}
}
else
{
argValue = ExtractNextArgument(argString, ref foundAt);
rawArgumentList.Add(argValue);
}
if (argValue == null && !arg.IsOptional && !arg.IsCatchAll)
return new ArgumentBindingResult(new ArgumentException("Not enough arguments supplied to the command."));
else if (argValue == null)
rawArgumentList.Add(null);
}
if (!ignoreSurplus && foundAt < argString.Length)
return new ArgumentBindingResult(new ArgumentException("Too many arguments were supplied to this command."));
for (var i = 0; i < overload.Arguments.Count; i++)
{
var arg = overload.Arguments[i];
if (arg.IsCatchAll && arg.IsArray)
{
var array = Array.CreateInstance(arg.Type, rawArgumentList.Count - i);
var start = i;
while (i < rawArgumentList.Count)
{
try
{
array.SetValue(await ctx.CommandsNext.ConvertArgument(rawArgumentList[i], ctx, arg.Type).ConfigureAwait(false), i - start);
}
catch (Exception ex)
{
return new ArgumentBindingResult(ex);
}
i++;
}
args[start + 2] = array;
break;
}
else
{
try
{
args[i + 2] = rawArgumentList[i] != null ? await ctx.CommandsNext.ConvertArgument(rawArgumentList[i], ctx, arg.Type).ConfigureAwait(false) : arg.DefaultValue;
}
catch (Exception ex)
{
return new ArgumentBindingResult(ex);
}
}
}
return new ArgumentBindingResult(args, rawArgumentList);
}
///
/// Whether this module is a candidate type.
///
/// The type.
internal static bool IsModuleCandidateType(this Type type)
=> type.GetTypeInfo().IsModuleCandidateType();
///
/// Whether this module is a candidate type.
///
/// The type info.
internal static bool IsModuleCandidateType(this TypeInfo ti)
{
// check if compiler-generated
if (ti.GetCustomAttribute(false) != null)
return false;
// check if derives from the required base class
var tmodule = typeof(BaseCommandModule);
var timodule = tmodule.GetTypeInfo();
if (!timodule.IsAssignableFrom(ti))
return false;
// check if anonymous
if (ti.IsGenericType && ti.Name.Contains("AnonymousType") && (ti.Name.StartsWith("<>") || ti.Name.StartsWith("VB$")) && (ti.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic)
return false;
// check if abstract, static, or not a class
if (!ti.IsClass || ti.IsAbstract)
return false;
// check if delegate type
var tdelegate = typeof(Delegate).GetTypeInfo();
if (tdelegate.IsAssignableFrom(ti))
return false;
// qualifies if any method or type qualifies
return ti.DeclaredMethods.Any(xmi => xmi.IsCommandCandidate(out _)) || ti.DeclaredNestedTypes.Any(xti => xti.IsModuleCandidateType());
}
///
/// Whether this is a command candidate.
///
/// The method.
/// The parameters.
internal static bool IsCommandCandidate(this MethodInfo method, out ParameterInfo[] parameters)
{
parameters = null;
// check if exists
if (method == null)
return false;
// check if static, non-public, abstract, a constructor, or a special name
if (method.IsStatic || method.IsAbstract || method.IsConstructor || method.IsSpecialName)
return false;
// check if appropriate return and arguments
parameters = method.GetParameters();
if (!parameters.Any() || parameters.First().ParameterType != typeof(CommandContext) || method.ReturnType != typeof(Task))
return false;
// qualifies
return true;
}
///
/// Creates the instance.
///
/// The type.
/// The services provider.
internal static object CreateInstance(this Type t, IServiceProvider services)
{
var ti = t.GetTypeInfo();
var constructors = ti.DeclaredConstructors
.Where(xci => xci.IsPublic)
.ToArray();
if (constructors.Length != 1)
throw new ArgumentException("Specified type does not contain a public constructor or contains more than one public constructor.");
var constructor = constructors[0];
var constructorArgs = constructor.GetParameters();
var args = new object[constructorArgs.Length];
if (constructorArgs.Length != 0 && services == null)
throw new InvalidOperationException("Dependency collection needs to be specified for parameterized constructors.");
// inject via constructor
if (constructorArgs.Length != 0)
for (var i = 0; i < args.Length; i++)
args[i] = services.GetRequiredService(constructorArgs[i].ParameterType);
var moduleInstance = Activator.CreateInstance(t, args);
// inject into properties
var props = t.GetRuntimeProperties().Where(xp => xp.CanWrite && xp.SetMethod != null && !xp.SetMethod.IsStatic && xp.SetMethod.IsPublic);
foreach (var prop in props)
{
if (prop.GetCustomAttribute() != null)
continue;
var service = services.GetService(prop.PropertyType);
if (service == null)
continue;
prop.SetValue(moduleInstance, service);
}
// inject into fields
var fields = t.GetRuntimeFields().Where(xf => !xf.IsInitOnly && !xf.IsStatic && xf.IsPublic);
foreach (var field in fields)
{
if (field.GetCustomAttribute() != null)
continue;
var service = services.GetService(field.FieldType);
if (service == null)
continue;
field.SetValue(moduleInstance, service);
}
return moduleInstance;
}
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs
index 9590879ea..7f65ec12c 100644
--- a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs
+++ b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs
@@ -1,641 +1,600 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DisCatSharp.Common.RegularExpressions;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters
{
///
/// Represents a discord user converter.
///
public class DiscordUserConverter : IArgumentConverter
{
///
/// Gets the user regex.
///
private static Regex UserRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordUserConverter()
{
-#if NETSTANDARD1_3
- UserRegex = new Regex(@"^<@\!?(\d+?)>$", RegexOptions.ECMAScript);
-#else
UserRegex = DiscordRegEx.User;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uid))
{
var result = await ctx.Client.GetUserAsync(uid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var m = UserRegex.Match(value);
if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out uid))
{
var result = await ctx.Client.GetUserAsync(uid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var cs = ctx.Config.CaseSensitive;
if (!cs)
value = value.ToLowerInvariant();
var di = value.IndexOf('#');
var un = di != -1 ? value.Substring(0, di) : value;
var dv = di != -1 ? value.Substring(di + 1) : null;
var us = ctx.Client.Guilds.Values
.SelectMany(xkvp => xkvp.Members.Values)
.Where(xm => (cs ? xm.Username : xm.Username.ToLowerInvariant()) == un && ((dv != null && xm.Discriminator == dv) || dv == null));
var usr = us.FirstOrDefault();
return usr != null ? Optional.FromValue(usr) : Optional.FromNoValue();
}
}
///
/// Represents a discord member converter.
///
public class DiscordMemberConverter : IArgumentConverter
{
///
/// Gets the user regex.
///
private static Regex UserRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordMemberConverter()
{
-#if NETSTANDARD1_3
- UserRegex = new Regex(@"^<@\!?(\d+?)>$", RegexOptions.ECMAScript);
-#else
UserRegex = DiscordRegEx.User;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (ctx.Guild == null)
return Optional.FromNoValue();
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uid))
{
var result = await ctx.Guild.GetMemberAsync(uid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var m = UserRegex.Match(value);
if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out uid))
{
var result = await ctx.Guild.GetMemberAsync(uid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var searchResult = await ctx.Guild.SearchMembersAsync(value).ConfigureAwait(false);
if (searchResult.Any())
return Optional.FromValue(searchResult.First());
var cs = ctx.Config.CaseSensitive;
if (!cs)
value = value.ToLowerInvariant();
var di = value.IndexOf('#');
var un = di != -1 ? value.Substring(0, di) : value;
var dv = di != -1 ? value.Substring(di + 1) : null;
var us = ctx.Guild.Members.Values
.Where(xm => ((cs ? xm.Username : xm.Username.ToLowerInvariant()) == un && ((dv != null && xm.Discriminator == dv) || dv == null))
|| (cs ? xm.Nickname : xm.Nickname?.ToLowerInvariant()) == value);
var mbr = us.FirstOrDefault();
return mbr != null ? Optional.FromValue(mbr) : Optional.FromNoValue();
}
}
///
/// Represents a discord channel converter.
///
public class DiscordChannelConverter : IArgumentConverter
{
///
/// Gets the channel regex.
///
private static Regex ChannelRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordChannelConverter()
{
-#if NETSTANDARD1_3
- ChannelRegex = new Regex(@"^<#(\d+)>$", RegexOptions.ECMAScript);
-#else
ChannelRegex = DiscordRegEx.Channel;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var cid))
{
var result = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var m = ChannelRegex.Match(value);
if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out cid))
{
var result = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var cs = ctx.Config.CaseSensitive;
if (!cs)
value = value.ToLowerInvariant();
var chn = ctx.Guild?.Channels.Values.FirstOrDefault(xc => (cs ? xc.Name : xc.Name.ToLowerInvariant()) == value);
return chn != null ? Optional.FromValue(chn) : Optional.FromNoValue();
}
}
///
/// Represents a discord thread channel converter.
///
public class DiscordThreadChannelConverter : IArgumentConverter
{
///
/// Gets the channel regex.
///
private static Regex ChannelRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordThreadChannelConverter()
{
-#if NETSTANDARD1_3
- ChannelRegex = new Regex(@"^<#(\d+)>$", RegexOptions.ECMAScript);
-#else
ChannelRegex = DiscordRegEx.Channel;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var tid))
{
var result = await ctx.Client.GetThreadAsync(tid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var m = ChannelRegex.Match(value);
if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out tid))
{
var result = await ctx.Client.GetThreadAsync(tid).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var cs = ctx.Config.CaseSensitive;
if (!cs)
value = value.ToLowerInvariant();
var tchn = ctx.Guild?.Threads.Values.FirstOrDefault(xc => (cs ? xc.Name : xc.Name.ToLowerInvariant()) == value);
return tchn != null ? Optional.FromValue(tchn) : Optional.FromNoValue();
}
}
///
/// Represents a discord role converter.
///
public class DiscordRoleConverter : IArgumentConverter
{
///
/// Gets the role regex.
///
private static Regex RoleRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordRoleConverter()
{
-#if NETSTANDARD1_3
- RoleRegex = new Regex(@"^<@&(\d+?)>$", RegexOptions.ECMAScript);
-#else
RoleRegex = DiscordRegEx.Role;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (ctx.Guild == null)
return Task.FromResult(Optional.FromNoValue());
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rid))
{
var result = ctx.Guild.GetRole(rid);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return Task.FromResult(ret);
}
var m = RoleRegex.Match(value);
if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out rid))
{
var result = ctx.Guild.GetRole(rid);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return Task.FromResult(ret);
}
var cs = ctx.Config.CaseSensitive;
if (!cs)
value = value.ToLowerInvariant();
var rol = ctx.Guild.Roles.Values.FirstOrDefault(xr => (cs ? xr.Name : xr.Name.ToLowerInvariant()) == value);
return Task.FromResult(rol != null ? Optional.FromValue(rol) : Optional.FromNoValue());
}
}
///
/// Represents a discord guild converter.
///
public class DiscordGuildConverter : IArgumentConverter
{
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var gid))
{
return ctx.Client.Guilds.TryGetValue(gid, out var result)
? Task.FromResult(Optional.FromValue(result))
: Task.FromResult(Optional.FromNoValue());
}
var cs = ctx.Config.CaseSensitive;
if (!cs)
value = value?.ToLowerInvariant();
var gld = ctx.Client.Guilds.Values.FirstOrDefault(xg => (cs ? xg.Name : xg.Name.ToLowerInvariant()) == value);
return Task.FromResult(gld != null ? Optional.FromValue(gld) : Optional.FromNoValue());
}
}
///
/// Represents a discord invite converter.
///
public class DiscordInviteConverter : IArgumentConverter
{
///
/// Gets the invite regex.
///
private static Regex InviteRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordInviteConverter()
{
-#if NETSTANDARD1_3
- InviteRegex = new Regex(@"^(https?:\/\/)?(www\.)?(discord\.(gg|io|me|li)|discordapp\.com\/invite)\/(.+[a-z])$", RegexOptions.ECMAScript);
-#else
InviteRegex = DiscordRegEx.Invite;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
var m = InviteRegex.Match(value);
if (m.Success)
{
var result = await ctx.Client.GetInviteByCodeAsync(m.Groups[5].Value).ConfigureAwait(false);
var ret = result != null ? Optional.FromValue(result) : Optional.FromNoValue();
return ret;
}
var cs = ctx.Config.CaseSensitive;
if (!cs)
value = value?.ToLowerInvariant();
var inv = await ctx.Client.GetInviteByCodeAsync(value);
return inv != null ? Optional.FromValue(inv) : Optional.FromNoValue();
}
}
///
/// Represents a discord message converter.
///
public class DiscordMessageConverter : IArgumentConverter
{
///
/// Gets the message path regex.
///
private static Regex MessagePathRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordMessageConverter()
{
-#if NETSTANDARD1_3
- MessagePathRegex = new Regex(@"^\/channels\/(?(?:\d+|@me))\/(?\d+)\/(?\d+)\/?$", RegexOptions.ECMAScript);
-#else
MessagePathRegex = DiscordRegEx.MessageLink;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (string.IsNullOrWhiteSpace(value))
return Optional.FromNoValue();
var msguri = value.StartsWith("<") && value.EndsWith(">") ? value.Substring(1, value.Length - 2) : value;
ulong mid;
if (Uri.TryCreate(msguri, UriKind.Absolute, out var uri))
{
if (uri.Host != "discordapp.com" && uri.Host != "discord.com" && !uri.Host.EndsWith(".discordapp.com") && !uri.Host.EndsWith(".discord.com"))
return Optional.FromNoValue();
var uripath = MessagePathRegex.Match(uri.AbsolutePath);
if (!uripath.Success
|| !ulong.TryParse(uripath.Groups["channel"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var cid)
|| !ulong.TryParse(uripath.Groups["message"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out mid))
return Optional.FromNoValue();
var chn = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false);
if (chn == null)
return Optional.FromNoValue();
var msg = await chn.GetMessageAsync(mid).ConfigureAwait(false);
return msg != null ? Optional.FromValue(msg) : Optional.FromNoValue();
}
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out mid))
{
var result = await ctx.Channel.GetMessageAsync(mid).ConfigureAwait(false);
return result != null ? Optional.FromValue(result) : Optional.FromNoValue();
}
return Optional.FromNoValue();
}
}
///
/// Represents a discord scheduled event converter.
///
public class DiscordScheduledEventConverter : IArgumentConverter
{
///
/// Gets the event regex.
///
private static Regex EventRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordScheduledEventConverter()
{
-#if NETSTANDARD1_3
- EventRegex = new Regex(@"^\/events\/(?\d+)\/(?\d+)$", RegexOptions.ECMAScript);
-#else
EventRegex = DiscordRegEx.Event;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (string.IsNullOrWhiteSpace(value))
return Optional.FromNoValue();
var msguri = value.StartsWith("<") && value.EndsWith(">") ? value.Substring(1, value.Length - 2) : value;
ulong seid;
if (Uri.TryCreate(msguri, UriKind.Absolute, out var uri))
{
if (uri.Host != "discordapp.com" && uri.Host != "discord.com" && !uri.Host.EndsWith(".discordapp.com") && !uri.Host.EndsWith(".discord.com"))
return Optional.FromNoValue();
var uripath = EventRegex.Match(uri.AbsolutePath);
if (!uripath.Success
|| !ulong.TryParse(uripath.Groups["guild"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var gid)
|| !ulong.TryParse(uripath.Groups["event"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out seid))
return Optional.FromNoValue();
var guild = await ctx.Client.GetGuildAsync(gid).ConfigureAwait(false);
if (guild == null)
return Optional.FromNoValue();
var ev = await guild.GetScheduledEventAsync(seid).ConfigureAwait(false);
return ev != null ? Optional.FromValue(ev) : Optional.FromNoValue();
}
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out seid))
{
var result = await ctx.Guild.GetScheduledEventAsync(seid).ConfigureAwait(false);
return result != null ? Optional.FromValue(result) : Optional.FromNoValue();
}
return Optional.FromNoValue();
}
}
///
/// Represents a discord emoji converter.
///
public class DiscordEmojiConverter : IArgumentConverter
{
///
/// Gets the emote regex.
///
private static Regex EmoteRegex { get; }
///
/// Initializes a new instance of the class.
///
static DiscordEmojiConverter()
{
-#if NETSTANDARD1_3
- EmoteRegex = new Regex(@"^$", RegexOptions.ECMAScript);
-#else
EmoteRegex = DiscordRegEx.Emoji;
-#endif
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (DiscordEmoji.TryFromUnicode(ctx.Client, value, out var emoji))
{
var result = emoji;
var ret = Optional.FromValue(result);
return Task.FromResult(ret);
}
var m = EmoteRegex.Match(value);
if (m.Success)
{
var sid = m.Groups["id"].Value;
var name = m.Groups["name"].Value;
var anim = m.Groups["animated"].Success;
return !ulong.TryParse(sid, NumberStyles.Integer, CultureInfo.InvariantCulture, out var id)
? Task.FromResult(Optional.FromNoValue())
: DiscordEmoji.TryFromGuildEmote(ctx.Client, id, out emoji)
? Task.FromResult(Optional.FromValue(emoji))
: Task.FromResult(Optional.FromValue(new DiscordEmoji
{
Discord = ctx.Client,
Id = id,
Name = name,
IsAnimated = anim,
RequiresColons = true,
IsManaged = false
}));
}
return Task.FromResult(Optional.FromNoValue());
}
}
///
/// Represents a discord color converter.
///
public class DiscordColorConverter : IArgumentConverter
{
///
/// Gets the color regex hex.
///
private static Regex ColorRegexHex { get; }
///
/// Gets the color regex rgb.
///
private static Regex ColorRegexRgb { get; }
///
/// Initializes a new instance of the class.
///
static DiscordColorConverter()
{
-#if NETSTANDARD1_3
- ColorRegexHex = new Regex(@"^#?([a-fA-F0-9]{6})$", RegexOptions.ECMAScript);
- ColorRegexRgb = new Regex(@"^(\d{1,3})\s*?,\s*?(\d{1,3}),\s*?(\d{1,3})$", RegexOptions.ECMAScript);
-#else
- ColorRegexHex = DiscordRegEx.HexColorString;
- ColorRegexRgb = DiscordRegEx.RgbColorString;
-#endif
+ ColorRegexHex = CommonRegEx.HexColorString;
+ ColorRegexRgb = CommonRegEx.RgbColorString;
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
var m = ColorRegexHex.Match(value);
if (m.Success && int.TryParse(m.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var clr))
return Task.FromResult(Optional.FromValue(clr));
m = ColorRegexRgb.Match(value);
if (m.Success)
{
var p1 = byte.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var r);
var p2 = byte.TryParse(m.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var g);
var p3 = byte.TryParse(m.Groups[3].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var b);
return !(p1 && p2 && p3)
? Task.FromResult(Optional.FromNoValue())
: Task.FromResult(Optional.FromValue(new DiscordColor(r, g, b)));
}
return Task.FromResult(Optional.FromNoValue());
}
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
index c4675b2c1..f9832adee 100644
--- a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
+++ b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
@@ -1,148 +1,145 @@
// This file is part of the DisCatSharp project.
//
// Copyright (c) 2021 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.Globalization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DisCatSharp.Entities;
+using DisCatSharp.Common.RegularExpressions;
namespace DisCatSharp.CommandsNext.Converters
{
///
/// Represents a date time converter.
///
public class DateTimeConverter : IArgumentConverter
{
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
return DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)
? Task.FromResult(new Optional(result))
: Task.FromResult(Optional.FromNoValue());
}
}
///
/// Represents a date time offset converter.
///
public class DateTimeOffsetConverter : IArgumentConverter
{
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
return DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)
? Task.FromResult(Optional.FromValue(result))
: Task.FromResult(Optional.FromNoValue());
}
}
///
/// Represents a time span converter.
///
public class TimeSpanConverter : IArgumentConverter
{
///
/// Gets or sets the time span regex.
///
private static Regex TimeSpanRegex { get; set; }
///
/// Initializes a new instance of the class.
///
static TimeSpanConverter()
{
-#if NETSTANDARD1_3
- TimeSpanRegex = new Regex(@"^(?\d+d\s*)?(?\d{1,2}h\s*)?(?\d{1,2}m\s*)?(?\d{1,2}s\s*)?$", RegexOptions.ECMAScript);
-#else
- TimeSpanRegex = new Regex(@"^(?\d+d\s*)?(?\d{1,2}h\s*)?(?\d{1,2}m\s*)?(?\d{1,2}s\s*)?$", RegexOptions.ECMAScript | RegexOptions.Compiled);
-#endif
+ TimeSpanRegex = CommonRegEx.TimeSpan;
}
///
/// Converts a string.
///
/// The string to convert.
/// The command context.
Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx)
{
if (value == "0")
return Task.FromResult(Optional.FromValue(TimeSpan.Zero));
if (int.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out _))
return Task.FromResult(Optional.FromNoValue());
if (!ctx.Config.CaseSensitive)
value = value.ToLowerInvariant();
if (TimeSpan.TryParse(value, CultureInfo.InvariantCulture, out var result))
return Task.FromResult(Optional.FromValue(result));
var gps = new string[] { "days", "hours", "minutes", "seconds" };
var mtc = TimeSpanRegex.Match(value);
if (!mtc.Success)
return Task.FromResult(Optional.FromNoValue());
var d = 0;
var h = 0;
var m = 0;
var s = 0;
foreach (var gp in gps)
{
var gpc = mtc.Groups[gp].Value;
if (string.IsNullOrWhiteSpace(gpc))
continue;
var gpt = gpc[gpc.Length - 1];
int.TryParse(gpc.Substring(0, gpc.Length - 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val);
switch (gpt)
{
case 'd':
d = val;
break;
case 'h':
h = val;
break;
case 'm':
m = val;
break;
case 's':
s = val;
break;
}
}
result = new TimeSpan(d, h, m, s);
return Task.FromResult(Optional.FromValue(result));
}
}
}
diff --git a/DisCatSharp.Common/RegularExpressions/CommonRegEx.cs b/DisCatSharp.Common/RegularExpressions/CommonRegEx.cs
new file mode 100644
index 000000000..95744fcaf
--- /dev/null
+++ b/DisCatSharp.Common/RegularExpressions/CommonRegEx.cs
@@ -0,0 +1,53 @@
+// This file is part of the DisCatSharp project, a fork of DSharpPlus.
+//
+// Copyright (c) 2021 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.Text;
+using System.Text.RegularExpressions;
+
+namespace DisCatSharp.Common.RegularExpressions
+{
+ ///
+ /// Provides common regex.
+ ///
+ public static class CommonRegEx
+ {
+ ///
+ /// Represents a hex color string.
+ ///
+ public static Regex HexColorString
+ => new(@"^#?([a-fA-F0-9]{6})$", RegexOptions.ECMAScript | RegexOptions.Compiled);
+
+ ///
+ /// Represents a rgp color string.
+ ///
+ public static Regex RgbColorString
+ => new(@"^(\d{1,3})\s*?,\s*?(\d{1,3}),\s*?(\d{1,3})$", RegexOptions.ECMAScript | RegexOptions.Compiled);
+
+ ///
+ /// Represents a timespan.
+ ///
+ public static Regex TimeSpan
+ => new(@"^(?\d+d\s*)?(?\d{1,2}h\s*)?(?\d{1,2}m\s*)?(?\d{1,2}s\s*)?$", RegexOptions.ECMAScript | RegexOptions.Compiled);
+ }
+}
diff --git a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs
index 85755045b..8350a3964 100644
--- a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs
+++ b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs
@@ -1,137 +1,125 @@
// This file is part of the DisCatSharp project, a fork of DSharpPlus.
//
// Copyright (c) 2021 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.Text;
using System.Text.RegularExpressions;
namespace DisCatSharp.Common.RegularExpressions
{
///
/// Provides common regex for discord related things.
///
public static class DiscordRegEx
{
///
/// Represents a invite.
///
public static Regex Invite
=> new(@"^(https?:\/\/)?(www\.)?(discord\.(gg|io|me|li)|discordapp\.(com|net)\/invite)\/(.+[a-z])$", RegexOptions.ECMAScript | RegexOptions.Compiled);
///
/// Represents a message link.
///
public static Regex MessageLink
=> new(@"^\/channels\/(?(?:\d+|@me))\/(?\d+)\/(?\d+)\/?$", RegexOptions.ECMAScript | RegexOptions.Compiled);
///
/// Represents a emoji.
///
public static Regex Emoji
=> new(@"^<(?a)?:(?[a-zA-Z0-9_]+?):(?\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
///
/// Represents a animated emoji.
///
public static Regex AnimatedEmoji
=> new(@"^<(?a):(?\w{2,32}):(?\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
///
/// Represents a non-animated emoji.
///
public static Regex StaticEmoji
=> new(@"^<:(?\w{2,32}):(?\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
///
/// Represents a timestamp.
///
public static Regex Timestamp
=> new(@"^-?\d{1,13})(:(?