diff --git a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs index 09d3d9a13..679dcb754 100644 --- a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs +++ b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs @@ -1,588 +1,457 @@ // 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.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 s_userRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordUserConverter() - { - s_userRegex = DiscordRegEx.User; - } - /// /// 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); return Optional.FromNullable(result); } - var m = s_userRegex.Match(value); + var m = DiscordRegEx.User.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); return Optional.FromNullable(result); } var cs = ctx.Config.CaseSensitive; if (!cs) value = value.ToLowerInvariant(); var di = value.IndexOf('#'); var un = di != -1 ? value[..di] : value; var dv = di != -1 ? value[(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 Optional.FromNullable(usr); } } /// /// Represents a discord member converter. /// public class DiscordMemberConverter : IArgumentConverter { - /// - /// Gets the user regex. - /// - private static Regex s_userRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordMemberConverter() - { - s_userRegex = DiscordRegEx.User; - } - /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (ctx.Guild == null) return Optional.None; if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uid)) { var result = await ctx.Guild.GetMemberAsync(uid).ConfigureAwait(false); return Optional.FromNullable(result); } - var m = s_userRegex.Match(value); + var m = DiscordRegEx.User.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); return Optional.FromNullable(result); } var searchResult = await ctx.Guild.SearchMembersAsync(value).ConfigureAwait(false); if (searchResult.Any()) return Optional.Some(searchResult.First()); var cs = ctx.Config.CaseSensitive; if (!cs) value = value.ToLowerInvariant(); var di = value.IndexOf('#'); var un = di != -1 ? value[..di] : value; var dv = di != -1 ? value[(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); return Optional.FromNullable(us.FirstOrDefault()); } } /// /// Represents a discord channel converter. /// public class DiscordChannelConverter : IArgumentConverter { - /// - /// Gets the channel regex. - /// - private static Regex s_channelRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordChannelConverter() - { - s_channelRegex = DiscordRegEx.Channel; - } - /// /// 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); return Optional.FromNullable(result); } - var m = s_channelRegex.Match(value); + var m = DiscordRegEx.Channel.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); return Optional.FromNullable(result); } 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 Optional.FromNullable(chn); } } /// /// Represents a discord thread channel converter. /// public class DiscordThreadChannelConverter : IArgumentConverter { - /// - /// Gets the channel regex. - /// - private static Regex s_channelRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordThreadChannelConverter() - { - s_channelRegex = DiscordRegEx.Channel; - } - /// /// 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); return Optional.FromNullable(result); } - var m = s_channelRegex.Match(value); + var m = DiscordRegEx.Channel.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); return Optional.FromNullable(result); } 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 Optional.FromNullable(tchn); } } /// /// Represents a discord role converter. /// public class DiscordRoleConverter : IArgumentConverter { - /// - /// Gets the role regex. - /// - private static Regex s_roleRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordRoleConverter() - { - s_roleRegex = DiscordRegEx.Role; - } - /// /// 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.None); if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rid)) { var result = ctx.Guild.GetRole(rid); return Task.FromResult(Optional.FromNullable(result)); } - var m = s_roleRegex.Match(value); + var m = DiscordRegEx.Role.Match(value); if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out rid)) { var result = ctx.Guild.GetRole(rid); return Task.FromResult(Optional.FromNullable(result)); } 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(Optional.FromNullable(rol)); } } /// /// 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.Some(result)) : Task.FromResult(Optional.None); } 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(Optional.FromNullable(gld)); } } /// /// Represents a discord invite converter. /// public class DiscordInviteConverter : IArgumentConverter { - /// - /// Gets the invite regex. - /// - private static Regex s_inviteRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordInviteConverter() - { - s_inviteRegex = DiscordRegEx.Invite; - } - /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { - var m = s_inviteRegex.Match(value); + var m = DiscordRegEx.Invite.Match(value); if (m.Success) { - var result = await ctx.Client.GetInviteByCodeAsync(m.Groups[5].Value).ConfigureAwait(false); + ulong? eventId = ulong.TryParse(m.Groups["eventId"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, + out var eid) ? eid : null; + var result = await ctx.Client.GetInviteByCodeAsync(m.Groups["code"].Value, scheduledEventId: eventId).ConfigureAwait(false); return Optional.FromNullable(result); } - var cs = ctx.Config.CaseSensitive; - if (!cs) - value = value?.ToLowerInvariant(); - var inv = await ctx.Client.GetInviteByCodeAsync(value); return Optional.FromNullable(inv); } } /// /// Represents a discord message converter. /// public class DiscordMessageConverter : IArgumentConverter { - /// - /// Gets the message path regex. - /// - private static Regex s_messagePathRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordMessageConverter() - { - s_messagePathRegex = DiscordRegEx.MessageLink; - } - /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (string.IsNullOrWhiteSpace(value)) return Optional.None; var msguri = value.StartsWith("<") && value.EndsWith(">") ? value[1..^1] : 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.None; - - var uripath = s_messagePathRegex.Match(uri.AbsolutePath); + var uripath = DiscordRegEx.MessageLink.Match(uri.AbsoluteUri); 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.None; var chn = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false); if (chn == null) return Optional.None; var msg = await chn.GetMessageAsync(mid).ConfigureAwait(false); return Optional.FromNullable(msg); } if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out mid)) { var result = await ctx.Channel.GetMessageAsync(mid).ConfigureAwait(false); return Optional.FromNullable(result); } return Optional.None; } } /// /// Represents a discord scheduled event converter. /// public class DiscordScheduledEventConverter : IArgumentConverter { - /// - /// Gets the event regex. - /// - private static Regex s_eventRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordScheduledEventConverter() - { - s_eventRegex = DiscordRegEx.Event; - } - /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { if (string.IsNullOrWhiteSpace(value)) return Optional.None; var msguri = value.StartsWith("<") && value.EndsWith(">") ? value[1..^1] : 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")) + var uripath = DiscordRegEx.Event.Match(uri.AbsoluteUri); + 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)) + { + var guild = await ctx.Client.GetGuildAsync(gid).ConfigureAwait(false); + if (guild == null) + return Optional.None; + + var ev = await guild.GetScheduledEventAsync(seid).ConfigureAwait(false); + return Optional.FromNullable(ev); + } + + try + { + var invite = await ctx.CommandsNext.ConvertArgument(value, ctx).ConfigureAwait(false); + return Optional.FromNullable(invite.GuildScheduledEvent); + } + catch + { return Optional.None; - - var uripath = s_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.None; - - var guild = await ctx.Client.GetGuildAsync(gid).ConfigureAwait(false); - if (guild == null) - return Optional.None; - - var ev = await guild.GetScheduledEventAsync(seid).ConfigureAwait(false); - return Optional.FromNullable(ev); + } } if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out seid)) { var result = await ctx.Guild.GetScheduledEventAsync(seid).ConfigureAwait(false); return Optional.FromNullable(result); } return Optional.None; } } /// /// Represents a discord emoji converter. /// public class DiscordEmojiConverter : IArgumentConverter { - /// - /// Gets the emote regex. - /// - private static Regex s_emoteRegex { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordEmojiConverter() - { - s_emoteRegex = DiscordRegEx.Emoji; - } - /// /// 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; return Task.FromResult(Optional.Some(result)); } - var m = s_emoteRegex.Match(value); + var m = DiscordRegEx.Emoji.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.None) : DiscordEmoji.TryFromGuildEmote(ctx.Client, id, out emoji) ? Task.FromResult(Optional.Some(emoji)) : Task.FromResult(Optional.Some(new DiscordEmoji { Discord = ctx.Client, Id = id, Name = name, IsAnimated = anim, RequiresColons = true, IsManaged = false })); } return Task.FromResult(Optional.None); } } /// /// Represents a discord color converter. /// public class DiscordColorConverter : IArgumentConverter { - /// - /// Gets the color regex hex. - /// - private static Regex s_colorRegexHex { get; } - /// - /// Gets the color regex rgb. - /// - private static Regex s_colorRegexRgb { get; } - - /// - /// Initializes a new instance of the class. - /// - static DiscordColorConverter() - { - s_colorRegexHex = CommonRegEx.HexColorString; - s_colorRegexRgb = CommonRegEx.RgbColorString; - } - /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { - var m = s_colorRegexHex.Match(value); + var m = CommonRegEx.HexColorString.Match(value); if (m.Success && int.TryParse(m.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var clr)) return Task.FromResult(Optional.Some(clr)); - m = s_colorRegexRgb.Match(value); + m = CommonRegEx.RgbColorString.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.None) : Task.FromResult(Optional.Some(new DiscordColor(r, g, b))); } return Task.FromResult(Optional.None); } } } diff --git a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs index 7c2bde47a..0475999c1 100644 --- a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs +++ b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs @@ -1,122 +1,124 @@ // 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.Text.RegularExpressions; namespace DisCatSharp.Common.RegularExpressions { /// /// Provides common regex for discord related things. /// public static class DiscordRegEx { + private const string WEBSITE = @"(https?:\/\/)?(www\.|canary\.|ptb\.)?(discord)\.(com|net)\/"; + /// /// 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); + public static readonly Regex Invite + = new(@"^(https?:\/\/)?(www\.)?(discord\.(gg|io|me|li)|discordapp\.(com|net)\/invite)\/(.*\/)*(?[a-zA-Z0-9]*)(\?event=(?\d+))?$", RegexOptions.ECMAScript | RegexOptions.Compiled); /// /// Represents a message link. /// - public static Regex MessageLink - => new(@"(https?:\/\/)?(www\.|canary\.|ptb\.)?(discord)\.(com|net)\/channels\/(?(?:\d+|@me))\/(?\d+)\/(?\d+)\/?", RegexOptions.ECMAScript | RegexOptions.Compiled); + public static readonly Regex MessageLink + = new($@"^{WEBSITE}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); + public static readonly 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); + public static readonly 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); + public static readonly Regex StaticEmoji + = new(@"^<:(?\w{2,32}):(?\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled); /// /// Represents a timestamp. /// - public static Regex Timestamp - => new(@"^-?\d{1,13})(:(?