diff --git a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs index 679dcb754..dd7f6967c 100644 --- a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs +++ b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs @@ -1,457 +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 { /// /// 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 = 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 { /// /// 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 = 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 { /// /// 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 = 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 { /// /// 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 = 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 { /// /// 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 = 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 { /// /// Converts a string. /// /// The string to convert. /// The command context. async Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { var m = DiscordRegEx.Invite.Match(value); if (m.Success) { - ulong? eventId = ulong.TryParse(m.Groups["eventId"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, + ulong? eventId = ulong.TryParse(m.Groups["event"].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 inv = await ctx.Client.GetInviteByCodeAsync(value); return Optional.FromNullable(inv); } } /// /// Represents a discord message converter. /// public class DiscordMessageConverter : IArgumentConverter { /// /// 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)) { 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 { /// /// 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)) { 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; } } 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 { /// /// 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 = 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 { /// /// Converts a string. /// /// The string to convert. /// The command context. Task> IArgumentConverter.ConvertAsync(string value, CommandContext ctx) { 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 = 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.Tests/OptionalTests.cs b/DisCatSharp.Common.Tests/OptionalTests.cs deleted file mode 100644 index 3d3be74d1..000000000 --- a/DisCatSharp.Common.Tests/OptionalTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Numerics; - -using DisCatSharp.Entities; - -using Xunit; - -namespace DisCatSharp.Common.Tests -{ - public class Tests - { - class TestClass { } - - struct TestStruct { } - - [Fact] - public void EqualityTests() - { - var c = new TestClass(); - - var c1 = Optional.None; - var c2 = Optional.Some(c); - Assert.NotEqual(c1, c2); - - var c3 = Optional.Some(c); - Assert.Equal(c2, c3); - - var c4 = Optional.Some(new TestClass()); - Assert.NotEqual(c3, c4); - - var s1 = Optional.None; - var s2 = Optional.Some(new TestStruct()); - Assert.NotEqual(s1, s2); - - var s3 = Optional.Some(new TestStruct()); - Assert.Equal(s2, s3); - - var s4 = (Optional)Optional.None; - Assert.Equal(s1, s4); - } - - [Fact] - public void FromNullableTests() - { - var c1 = Optional.Some(null); - Assert.True(c1.HasValue); - Assert.Null(c1.Value); - - var c2 = Optional.FromNullable(null); - Assert.False(c2.HasValue); - - var c3 = Optional.FromNullable(new TestClass()); - Assert.True(c3.HasValue); - Assert.NotNull(c3.Value); - } - - [Fact] - public void MapTests() - { - var c1 = Optional.None; - var s1 = c1.Map(x => new TestStruct()); - Assert.False(s1.HasValue); - - c1 = Optional.Some(new TestClass()); - s1 = c1.Map(x => new TestStruct()); - Assert.True(s1.HasValue); - - var mapped = false; - T Map(T o) - { - mapped = true; - return o; - } - - c1 = null; - var c2 = c1.MapOrNull(Map); - Assert.False(mapped); - Assert.True(c2.HasValue); - Assert.Null((TestClass)c2); - - c1 = new TestClass(); - c2 = c1.MapOrNull(Map); - Assert.True(mapped); - Assert.True(c2.HasValue); - Assert.Equal(c1, c2); - Assert.Equal(c1.Value, c2.Value); - } - } -} diff --git a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs index 0475999c1..4aa3d8ddf 100644 --- a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs +++ b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs @@ -1,124 +1,125 @@ // 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)\/"; + // language=regex + private const string WEBSITE = @"(https?:\/\/)?(www\.|canary\.|ptb\.)?(discord|discordapp)\.com\/"; /// /// Represents a invite. /// 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); + = new($@"^((https?:\/\/)?(www\.)?discord\.gg(\/.*)*|{WEBSITE}invite)\/(?[a-zA-Z0-9]*)(\?event=(?\d+))?$", RegexOptions.ECMAScript | RegexOptions.Compiled); /// /// Represents a message link. /// public static readonly Regex MessageLink = new($@"^{WEBSITE}channels\/(?(?:\d+|@me))\/(?\d+)\/(?\d+)\/?", RegexOptions.ECMAScript | RegexOptions.Compiled); /// /// Represents a emoji. /// public static readonly Regex Emoji = new(@"^<(?a)?:(?[a-zA-Z0-9_]+?):(?\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled); /// /// Represents a animated emoji. /// public static readonly Regex AnimatedEmoji = new(@"^<(?a):(?\w{2,32}):(?\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled); /// /// Represents a non-animated emoji. /// public static readonly Regex StaticEmoji = new(@"^<:(?\w{2,32}):(?\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled); /// /// Represents a timestamp. /// public static readonly Regex Timestamp = new(@"^-?\d{1,13})(:(?