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})(:(?