diff --git a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs index 669686b2a..fb4a2e434 100644 --- a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs +++ b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs @@ -1,578 +1,578 @@ // 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.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 = new Regex(@"^<@\!?(\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 = new Regex(@"^<@\!?(\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 = new Regex(@"^<#(\d+)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 = new Regex(@"^<#(\d+)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 = new Regex(@"^<@&(\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 = new Regex(@"^(https?:\/\/)?(www\.)?(discord\.(gg|io|me|li)|discordapp\.com\/invite)\/(.+[a-z])$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 = new Regex(@"^\/channels\/(?(?:\d+|@me))\/(?\d+)\/(?\d+)\/?$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 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 = new Regex(@"^<(?a)?:(?[a-zA-Z0-9_]+?):(?\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + 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 = new Regex(@"^#?([a-fA-F0-9]{6})$", RegexOptions.ECMAScript | RegexOptions.Compiled); - ColorRegexRgb = new Regex(@"^(\d{1,3})\s*?,\s*?(\d{1,3}),\s*?(\d{1,3})$", RegexOptions.ECMAScript | RegexOptions.Compiled); + ColorRegexHex = DiscordRegEx.HexColorString; + ColorRegexRgb = DiscordRegEx.RgbColorString; #endif } /// /// 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.Common/DiscordRegEx.cs b/DisCatSharp.Common/DiscordRegEx.cs deleted file mode 100644 index cb74d4dae..000000000 --- a/DisCatSharp.Common/DiscordRegEx.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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; - -namespace DisCatSharp.Common -{ - /// - /// Provides common regex for discord related things. - /// - public static class DiscordRegEx - { - } -} diff --git a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs new file mode 100644 index 000000000..1e0111ef6 --- /dev/null +++ b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs @@ -0,0 +1,83 @@ +// 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\/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 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 role. + /// + public static Regex Role + => new(@"^<@&(\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + + /// + /// Represents a channel. + /// + public static Regex Channel + => new(@"^<#(\d+)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + + /// + /// Represents a user. + /// + public static Regex User + => new(@"^<@\!?(\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); + } +}