diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs
index 05da07187..4354cbb88 100644
--- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs
+++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs
@@ -32,7 +32,7 @@
if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> "
ulong userId;
- if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 2), out userId)) return false;
+ if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out userId)) return false;
if (userId == user.Id)
{
argPos = endPos + 2;
diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs
index 97e58355c..72608ec6a 100644
--- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs
@@ -5,6 +5,9 @@ namespace Discord
{
public interface IChannel : ISnowflakeEntity
{
+ /// Gets the name of this channel.
+ string Name { get; }
+
/// Gets a collection of all users in this channel.
IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
index bb9f39c71..81bf42d8e 100644
--- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs
@@ -7,8 +7,6 @@ namespace Discord
{
public interface IGuildChannel : IChannel, IDeletable
{
- /// Gets the name of this channel.
- string Name { get; }
/// Gets the position of this channel in the guild's channel list, relative to others of the same type.
int Position { get; }
diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs
index 8cc650ce6..64b13e8e3 100644
--- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs
+++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs
@@ -1,7 +1,9 @@
-using Model = Discord.API.EmbedProvider;
+using System.Diagnostics;
+using Model = Discord.API.EmbedProvider;
namespace Discord
{
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedProvider
{
public string Name { get; }
@@ -16,5 +18,8 @@ namespace Discord
{
return new EmbedProvider(model.Name, model.Url);
}
+
+ private string DebuggerDisplay => $"{Name} ({Url})";
+ public override string ToString() => Name;
}
}
diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs
index 43a37548c..6a5fc4163 100644
--- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs
+++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs
@@ -1,7 +1,9 @@
-using Model = Discord.API.EmbedThumbnail;
+using System.Diagnostics;
+using Model = Discord.API.EmbedThumbnail;
namespace Discord
{
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct EmbedThumbnail
{
public string Url { get; }
@@ -22,5 +24,8 @@ namespace Discord
model.Height.IsSpecified ? model.Height.Value : (int?)null,
model.Width.IsSpecified ? model.Width.Value : (int?)null);
}
+
+ private string DebuggerDisplay => $"{ToString()} ({Url})";
+ public override string ToString() => Width != null && Height != null ? $"{Width}x{Height}" : "0x0";
}
}
diff --git a/src/Discord.Net.Core/Entities/Messages/Emoji.cs b/src/Discord.Net.Core/Entities/Messages/Emoji.cs
index 5750b7ed8..612e99f29 100644
--- a/src/Discord.Net.Core/Entities/Messages/Emoji.cs
+++ b/src/Discord.Net.Core/Entities/Messages/Emoji.cs
@@ -1,22 +1,54 @@
using Discord.API;
+using System;
+using System.Diagnostics;
+using System.Globalization;
namespace Discord
{
+ [DebuggerDisplay("{DebuggerDisplay,nq}")]
public struct Emoji
{
public ulong Id { get; }
public string Name { get; }
- public int Index { get; }
- public int Length { get; }
public string Url => CDN.GetEmojiUrl(Id);
- internal Emoji(ulong id, string name, int index, int length)
+ internal Emoji(ulong id, string name)
{
Id = id;
Name = name;
- Index = index;
- Length = length;
}
+
+ public static Emoji Parse(string text)
+ {
+ Emoji result;
+ if (TryParse(text, out result))
+ return result;
+ throw new ArgumentException("Invalid emoji format", nameof(text));
+ }
+
+ public static bool TryParse(string text, out Emoji result)
+ {
+ result = default(Emoji);
+ if (text.Length >= 4 && text[0] == '<' && text[1] == ':' && text[text.Length - 1] == '>')
+ {
+ int splitIndex = text.IndexOf(':', 2);
+ if (splitIndex == -1)
+ return false;
+
+ ulong id;
+ if (!ulong.TryParse(text.Substring(splitIndex + 1, text.Length - splitIndex - 2), NumberStyles.None, CultureInfo.InvariantCulture, out id))
+ return false;
+
+ string name = text.Substring(2, splitIndex - 2);
+ result = new Emoji(id, name);
+ return true;
+ }
+ return false;
+
+ }
+
+ private string DebuggerDisplay => $"{Name} ({Id})";
+ public override string ToString() => Name;
}
}
diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs
index 6291c1a3d..6f0a60e0f 100644
--- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs
+++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs
@@ -27,8 +27,8 @@ namespace Discord
IReadOnlyCollection Attachments { get; }
/// Returns a collection of all embeds included in this message.
IReadOnlyCollection Embeds { get; }
- /// Returns a collection of all custom emoji included in this message.
- IReadOnlyCollection Emojis { get; }
+ /// Returns a collection of all tags included in this message's content.
+ IReadOnlyCollection Tags { get; }
/// Returns a collection of channel ids mentioned in this message.
IReadOnlyCollection MentionedChannelIds { get; }
/// Returns a collection of roles mentioned in this message.
diff --git a/src/Discord.Net.Core/Entities/Messages/ITag.cs b/src/Discord.Net.Core/Entities/Messages/ITag.cs
new file mode 100644
index 000000000..27824e6d3
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Messages/ITag.cs
@@ -0,0 +1,11 @@
+namespace Discord
+{
+ public interface ITag
+ {
+ int Index { get; }
+ int Length { get; }
+ TagType Type { get; }
+ ulong Key { get; }
+ object Value { get; }
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
index 3faa31419..661a6d59a 100644
--- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
+++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
@@ -12,18 +12,13 @@ namespace Discord
Task PinAsync(RequestOptions options = null);
/// Removes this message from its channel's pinned messages.
Task UnpinAsync(RequestOptions options = null);
-
- /// Transforms this message's text into a human readable form, resolving mentions to that object's name.
- string Resolve(int startIndex, int length,
- UserMentionHandling userHandling = UserMentionHandling.Name,
- ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
- RoleMentionHandling roleHandling = RoleMentionHandling.Name,
- EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore);
- /// Transforms this message's text into a human readable form, resolving mentions to that object's name.
+
+ /// Transforms this message's text into a human readable form by resolving its tags.
string Resolve(
- UserMentionHandling userHandling = UserMentionHandling.Name,
- ChannelMentionHandling channelHandling = ChannelMentionHandling.Name,
- RoleMentionHandling roleHandling = RoleMentionHandling.Name,
- EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore);
+ TagHandling userHandling = TagHandling.Name,
+ TagHandling channelHandling = TagHandling.Name,
+ TagHandling roleHandling = TagHandling.Name,
+ TagHandling everyoneHandling = TagHandling.Ignore,
+ TagHandling emojiHandling = TagHandling.Name);
}
}
diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs
deleted file mode 100644
index 5e05606e5..000000000
--- a/src/Discord.Net.Core/Entities/Messages/Mentions/EveryoneMentionHandling.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Discord
-{
- public enum EveryoneMentionHandling
- {
- Ignore = 0,
- Remove,
- Sanitize
- }
-}
diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs
deleted file mode 100644
index 94d4a382f..000000000
--- a/src/Discord.Net.Core/Entities/Messages/Mentions/RoleMentionHandling.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Discord
-{
- public enum RoleMentionHandling
- {
- Ignore = 0,
- Remove,
- Name,
- Sanitize
- }
-}
diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs
deleted file mode 100644
index 42914f393..000000000
--- a/src/Discord.Net.Core/Entities/Messages/Mentions/UserMentionHandling.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Discord
-{
- public enum UserMentionHandling
- {
- Ignore = 0,
- Remove,
- Name,
- NameAndDiscriminator,
- Sanitize
- }
-}
diff --git a/src/Discord.Net.Core/Entities/Messages/Tag.cs b/src/Discord.Net.Core/Entities/Messages/Tag.cs
new file mode 100644
index 000000000..06d995e73
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Messages/Tag.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics;
+
+namespace Discord
+{
+ [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
+ public class Tag : ITag
+ {
+ public TagType Type { get; }
+ public int Index { get; }
+ public int Length { get; }
+ public ulong Key { get; }
+ public T Value { get; }
+
+ internal Tag(TagType type, int index, int length, ulong key, T value)
+ {
+ Type = type;
+ Index = index;
+ Length = length;
+ Key = key;
+ Value = value;
+ }
+
+ private string DebuggerDisplay => $"{Value?.ToString() ?? "null"} ({Type})";
+ public override string ToString() => $"{Value?.ToString() ?? "null"} ({Type})";
+
+ object ITag.Value => Value;
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs
similarity index 69%
rename from src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs
rename to src/Discord.Net.Core/Entities/Messages/TagHandling.cs
index ea1b91688..3572a37a5 100644
--- a/src/Discord.Net.Core/Entities/Messages/Mentions/ChannelMentionHandling.cs
+++ b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs
@@ -1,10 +1,11 @@
namespace Discord
{
- public enum ChannelMentionHandling
+ public enum TagHandling
{
Ignore = 0,
Remove,
Name,
+ FullName,
Sanitize
}
}
diff --git a/src/Discord.Net.Core/Entities/Messages/TagType.cs b/src/Discord.Net.Core/Entities/Messages/TagType.cs
new file mode 100644
index 000000000..2d93bb3e3
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Messages/TagType.cs
@@ -0,0 +1,12 @@
+namespace Discord
+{
+ public enum TagType
+ {
+ UserMention,
+ ChannelMention,
+ RoleMention,
+ EveryoneMention,
+ HereMention,
+ Emoji
+ }
+}
diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs
index 51463cdae..58e3650fe 100644
--- a/src/Discord.Net.Core/Utils/MentionUtils.cs
+++ b/src/Discord.Net.Core/Utils/MentionUtils.cs
@@ -1,9 +1,6 @@
using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Globalization;
-using System.Linq;
-using System.Text.RegularExpressions;
+using System.Text;
namespace Discord
{
@@ -11,10 +8,6 @@ namespace Discord
{
private const char SanitizeChar = '\x200b';
- private static readonly Regex _userRegex = new Regex(@"<@!?([0-9]+)>", RegexOptions.Compiled);
- private static readonly Regex _channelRegex = new Regex(@"<#([0-9]+)>", RegexOptions.Compiled);
- private static readonly Regex _roleRegex = new Regex(@"<@&([0-9]+)>", RegexOptions.Compiled);
-
//If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake)
internal static string MentionUser(string id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>";
public static string MentionUser(ulong id) => MentionUser(id.ToString(), true);
@@ -24,25 +17,24 @@ namespace Discord
public static string MentionRole(ulong id) => MentionRole(id.ToString());
/// Parses a provided user mention string.
- public static ulong ParseUser(string mentionText)
+ public static ulong ParseUser(string text)
{
ulong id;
- if (TryParseUser(mentionText, out id))
+ if (TryParseUser(text, out id))
return id;
- throw new ArgumentException("Invalid mention format", nameof(mentionText));
+ throw new ArgumentException("Invalid mention format", nameof(text));
}
/// Tries to parse a provided user mention string.
- public static bool TryParseUser(string mentionText, out ulong userId)
+ public static bool TryParseUser(string text, out ulong userId)
{
- mentionText = mentionText.Trim();
- if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[mentionText.Length - 1] == '>')
+ if (text.Length >= 3 && text[0] == '<' && text[1] == '@' && text[text.Length - 1] == '>')
{
- if (mentionText.Length >= 4 && mentionText[2] == '!')
- mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@!123>
+ if (text.Length >= 4 && text[2] == '!')
+ text = text.Substring(3, text.Length - 4); //<@!123>
else
- mentionText = mentionText.Substring(2, mentionText.Length - 3); //<@123>
+ text = text.Substring(2, text.Length - 3); //<@123>
- if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out userId))
+ if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out userId))
return true;
}
userId = 0;
@@ -50,22 +42,21 @@ namespace Discord
}
/// Parses a provided channel mention string.
- public static ulong ParseChannel(string mentionText)
+ public static ulong ParseChannel(string text)
{
ulong id;
- if (TryParseChannel(mentionText, out id))
+ if (TryParseChannel(text, out id))
return id;
- throw new ArgumentException("Invalid mention format", nameof(mentionText));
+ throw new ArgumentException("Invalid mention format", nameof(text));
}
/// Tries to parse a provided channel mention string.
- public static bool TryParseChannel(string mentionText, out ulong channelId)
+ public static bool TryParseChannel(string text, out ulong channelId)
{
- mentionText = mentionText.Trim();
- if (mentionText.Length >= 3 && mentionText[0] == '<' && mentionText[1] == '#' && mentionText[mentionText.Length - 1] == '>')
+ if (text.Length >= 3 && text[0] == '<' && text[1] == '#' && text[text.Length - 1] == '>')
{
- mentionText = mentionText.Substring(2, mentionText.Length - 3); //<#123>
+ text = text.Substring(2, text.Length - 3); //<#123>
- if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out channelId))
+ if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out channelId))
return true;
}
channelId = 0;
@@ -73,212 +64,175 @@ namespace Discord
}
/// Parses a provided role mention string.
- public static ulong ParseRole(string mentionText)
+ public static ulong ParseRole(string text)
{
ulong id;
- if (TryParseRole(mentionText, out id))
+ if (TryParseRole(text, out id))
return id;
- throw new ArgumentException("Invalid mention format", nameof(mentionText));
+ throw new ArgumentException("Invalid mention format", nameof(text));
}
/// Tries to parse a provided role mention string.
- public static bool TryParseRole(string mentionText, out ulong roleId)
+ public static bool TryParseRole(string text, out ulong roleId)
{
- mentionText = mentionText.Trim();
- if (mentionText.Length >= 4 && mentionText[0] == '<' && mentionText[1] == '@' && mentionText[2] == '&' && mentionText[mentionText.Length - 1] == '>')
+ if (text.Length >= 4 && text[0] == '<' && text[1] == '@' && text[2] == '&' && text[text.Length - 1] == '>')
{
- mentionText = mentionText.Substring(3, mentionText.Length - 4); //<@&123>
+ text = text.Substring(3, text.Length - 4); //<@&123>
- if (ulong.TryParse(mentionText, NumberStyles.None, CultureInfo.InvariantCulture, out roleId))
+ if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out roleId))
return true;
}
roleId = 0;
return false;
}
- internal static ImmutableArray GetUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentionedUsers)
- where TUser : class, IUser
+ internal static string Resolve(IMessage msg, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling)
{
- var matches = _userRegex.Matches(text);
- var builder = ImmutableArray.CreateBuilder(matches.Count);
- foreach (var match in matches.OfType())
+ var text = new StringBuilder(msg.Content);
+ var tags = msg.Tags;
+ int indexOffset = 0;
+
+ foreach (var tag in tags)
{
- ulong id;
- if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id))
+ string newText = "";
+ switch (tag.Type)
{
- TUser user = null;
-
- //Verify this user was actually mentioned
- foreach (var userMention in mentionedUsers)
- {
- if (userMention.Id == id)
- {
- user = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult() as TUser;
- if (user == null) //User not found, fallback to basic mention info
- user = userMention;
- break;
- }
- }
-
- if (user != null)
- builder.Add(user);
+ case TagType.UserMention:
+ if (userHandling == TagHandling.Ignore) continue;
+ newText = ResolveUserMention(tag, userHandling);
+ break;
+ case TagType.ChannelMention:
+ if (channelHandling == TagHandling.Ignore) continue;
+ newText = ResolveChannelMention(tag, channelHandling);
+ break;
+ case TagType.RoleMention:
+ if (roleHandling == TagHandling.Ignore) continue;
+ newText = ResolveRoleMention(tag, roleHandling);
+ break;
+ case TagType.EveryoneMention:
+ if (everyoneHandling == TagHandling.Ignore) continue;
+ newText = ResolveEveryoneMention(tag, everyoneHandling);
+ break;
+ case TagType.HereMention:
+ if (everyoneHandling == TagHandling.Ignore) continue;
+ newText = ResolveHereMention(tag, everyoneHandling);
+ break;
+ case TagType.Emoji:
+ if (emojiHandling == TagHandling.Ignore) continue;
+ newText = ResolveEmoji(tag, emojiHandling);
+ break;
}
+ text.Remove(tag.Index, tag.Length);
+ text.Insert(tag.Index, newText);
+ indexOffset += newText.Length - tag.Length;
}
- return builder.ToImmutable();
+ return text.ToString();
}
- internal static ImmutableArray GetChannelMentions(string text, IGuild guild)
+ internal static string ResolveUserMention(ITag tag, TagHandling mode)
{
- var matches = _channelRegex.Matches(text);
- var builder = ImmutableArray.CreateBuilder(matches.Count);
- foreach (var match in matches.OfType())
+ if (mode != TagHandling.Remove)
{
- ulong id;
- if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id))
- builder.Add(id);
+ var user = tag.Value as IUser;
+ switch (mode)
+ {
+ case TagHandling.Name:
+ if (user != null)
+ return $"@{(user as IGuildUser)?.Nickname ?? user?.Username}";
+ else
+ return $"@unknown-user";
+ case TagHandling.FullName:
+ if (user != null)
+ return $"@{(user as IGuildUser)?.Nickname ?? user?.Username}#{user.Discriminator}";
+ else
+ return $"@unknown-user";
+ case TagHandling.Sanitize:
+ return MentionUser($"{SanitizeChar}{tag.Key}");
+ }
}
- return builder.ToImmutable();
+ return "";
}
- internal static ImmutableArray GetRoleMentions(string text, IGuild guild)
- where TRole : class, IRole
+ internal static string ResolveChannelMention(ITag tag, TagHandling mode)
{
- if (guild == null)
- return ImmutableArray.Create();
-
- var matches = _roleRegex.Matches(text);
- var builder = ImmutableArray.CreateBuilder(matches.Count);
- foreach (var match in matches.OfType())
+ if (mode != TagHandling.Remove)
{
- ulong id;
- if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id))
+ var channel = tag.Value as IChannel;
+ switch (mode)
{
- var role = guild.GetRole(id) as TRole;
- if (role != null)
- builder.Add(role);
+ case TagHandling.Name:
+ case TagHandling.FullName:
+ if (channel != null)
+ return $"#{channel.Name}";
+ else
+ return $"#deleted-channel";
+ case TagHandling.Sanitize:
+ return MentionChannel($"{SanitizeChar}{tag.Key}");
}
}
- return builder.ToImmutable();
+ return "";
}
-
- internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection mentions, UserMentionHandling mode)
+ internal static string ResolveRoleMention(ITag tag, TagHandling mode)
{
- if (mode == UserMentionHandling.Ignore) return text;
-
- return _userRegex.Replace(text, new MatchEvaluator(e =>
+ if (mode != TagHandling.Remove)
{
- ulong id;
- if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id))
+ var role = tag.Value as IRole;
+ switch (mode)
{
- IUser user = null;
- foreach (var mention in mentions)
- {
- if (mention.Id == id)
- {
- user = mention;
- break;
- }
- }
- if (user != null)
- {
- string name = user.Username;
-
- var guildUser = user as IGuildUser;
- if (e.Value[2] == '!')
- {
- if (guildUser != null && guildUser.Nickname != null)
- name = guildUser.Nickname;
- }
-
- switch (mode)
- {
- case UserMentionHandling.Name:
- return $"@{name}";
- case UserMentionHandling.NameAndDiscriminator:
- return $"@{name}#{user.Discriminator}";
- case UserMentionHandling.Sanitize:
- return MentionUser($"{SanitizeChar}{id}");
- case UserMentionHandling.Remove:
- default:
- return "";
- }
- }
+ case TagHandling.Name:
+ case TagHandling.FullName:
+ if (role != null)
+ return $"@{role.Name}";
+ else
+ return $"@deleted-role";
+ case TagHandling.Sanitize:
+ return MentionRole($"{SanitizeChar}{tag.Key}");
}
- return e.Value;
- }));
+ }
+ return "";
}
- internal static string ResolveChannelMentions(string text, IGuild guild, ChannelMentionHandling mode)
+ internal static string ResolveEveryoneMention(ITag tag, TagHandling mode)
{
- if (mode == ChannelMentionHandling.Ignore) return text;
-
- return _channelRegex.Replace(text, new MatchEvaluator(e =>
+ if (mode != TagHandling.Remove)
{
- ulong id;
- if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id))
+ switch (mode)
{
- switch (mode)
- {
- case ChannelMentionHandling.Name:
- IGuildChannel channel = null;
- channel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult();
- if (channel != null)
- return $"#{channel.Name}";
- else
- return $"#deleted-channel";
- case ChannelMentionHandling.Sanitize:
- return MentionChannel($"{SanitizeChar}{id}");
- case ChannelMentionHandling.Remove:
- default:
- return "";
- }
+ case TagHandling.Name:
+ case TagHandling.FullName:
+ return "@everyone";
+ case TagHandling.Sanitize:
+ return $"@{SanitizeChar}everyone";
}
- return e.Value;
- }));
+ }
+ return "";
}
- internal static string ResolveRoleMentions(string text, IReadOnlyCollection mentions, RoleMentionHandling mode)
+ internal static string ResolveHereMention(ITag tag, TagHandling mode)
{
- if (mode == RoleMentionHandling.Ignore) return text;
-
- return _roleRegex.Replace(text, new MatchEvaluator(e =>
+ if (mode != TagHandling.Remove)
{
- ulong id;
- if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id))
+ switch (mode)
{
- switch (mode)
- {
- case RoleMentionHandling.Name:
- IRole role = null;
- foreach (var mention in mentions)
- {
- if (mention.Id == id)
- {
- role = mention;
- break;
- }
- }
- if (role != null)
- return $"{role.Name}";
- else
- return $"deleted-role";
- case RoleMentionHandling.Sanitize:
- return MentionRole($"{SanitizeChar}{id}");
- case RoleMentionHandling.Remove:
- default:
- return "";
- }
+ case TagHandling.Name:
+ case TagHandling.FullName:
+ return "@everyone";
+ case TagHandling.Sanitize:
+ return $"@{SanitizeChar}everyone";
}
- return e.Value;
- }));
+ }
+ return "";
}
- internal static string ResolveEveryoneMentions(string text, EveryoneMentionHandling mode)
+ internal static string ResolveEmoji(ITag tag, TagHandling mode)
{
- if (mode == EveryoneMentionHandling.Ignore) return text;
-
- switch (mode)
+ if (mode != TagHandling.Remove)
{
- case EveryoneMentionHandling.Sanitize:
- return text.Replace("@everyone", $"@{SanitizeChar}everyone").Replace("@here", $"@{SanitizeChar}here");
- case EveryoneMentionHandling.Remove:
- default:
- return text.Replace("@everyone", "").Replace("@here", "");
+ Emoji emoji = (Emoji)tag.Value;
+ switch (mode)
+ {
+ case TagHandling.Name:
+ case TagHandling.FullName:
+ return $":{emoji.Name}:";
+ case TagHandling.Sanitize:
+ return $"<@{SanitizeChar}everyone";
+ }
}
+ return "";
}
}
}
diff --git a/src/Discord.Net.Core/project.json b/src/Discord.Net.Core/project.json
index 69d3fccb7..ea17308ae 100644
--- a/src/Discord.Net.Core/project.json
+++ b/src/Discord.Net.Core/project.json
@@ -35,8 +35,7 @@
"System.Net.WebSockets.Client": {
"version": "4.0.0",
"type": "build"
- },
- "System.Text.RegularExpressions": "4.1.0"
+ }
},
"frameworks": {
diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
index a6c172918..93e996747 100644
--- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
@@ -63,13 +63,13 @@ namespace Discord.Rest
//Messages
public static async Task GetMessageAsync(IChannel channel, BaseDiscordClient client,
- ulong id, RequestOptions options)
+ ulong id, IGuild guild, RequestOptions options)
{
var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false);
- return RestMessage.Create(client, model);
+ return RestMessage.Create(client, guild, model);
}
public static IAsyncEnumerable> GetMessagesAsync(IChannel channel, BaseDiscordClient client,
- ulong? fromMessageId, Direction dir, int limit, RequestOptions options)
+ ulong? fromMessageId, Direction dir, int limit, IGuild guild, RequestOptions options)
{
//TODO: Test this with Around direction
return new PagedAsyncEnumerable(
@@ -84,7 +84,7 @@ namespace Discord.Rest
if (info.Position != null)
args.RelativeMessageId = info.Position.Value;
var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options);
- return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray(); ;
+ return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray(); ;
},
nextPage: (info, lastPage) =>
{
@@ -99,34 +99,34 @@ namespace Discord.Rest
count: limit
);
}
- public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client,
- RequestOptions options)
+ public static async Task> GetPinnedMessagesAsync(IChannel channel, BaseDiscordClient client,
+ IGuild guild, RequestOptions options)
{
var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false);
- return models.Select(x => RestMessage.Create(client, x)).ToImmutableArray();
+ return models.Select(x => RestMessage.Create(client, guild, x)).ToImmutableArray();
}
public static async Task SendMessageAsync(IChannel channel, BaseDiscordClient client,
- string text, bool isTTS, RequestOptions options)
+ string text, bool isTTS, IGuild guild, RequestOptions options)
{
var args = new CreateMessageParams(text) { IsTTS = isTTS };
var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false);
- return RestUserMessage.Create(client, model);
+ return RestUserMessage.Create(client, guild, model);
}
public static Task SendFileAsync(IChannel channel, BaseDiscordClient client,
- string filePath, string text, bool isTTS, RequestOptions options)
+ string filePath, string text, bool isTTS, IGuild guild, RequestOptions options)
{
string filename = Path.GetFileName(filePath);
using (var file = File.OpenRead(filePath))
- return SendFileAsync(channel, client, file, filename, text, isTTS, options);
+ return SendFileAsync(channel, client, file, filename, text, isTTS, guild, options);
}
public static async Task SendFileAsync(IChannel channel, BaseDiscordClient client,
- Stream stream, string filename, string text, bool isTTS, RequestOptions options)
+ Stream stream, string filename, string text, bool isTTS, IGuild guild, RequestOptions options)
{
var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS };
var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false);
- return RestUserMessage.Create(client, model);
+ return RestUserMessage.Create(client, guild, model);
}
public static async Task DeleteMessagesAsync(IChannel channel, BaseDiscordClient client,
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
index 04305c6b8..342dd6898 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs
@@ -43,7 +43,9 @@ namespace Discord.Rest
public abstract Task UpdateAsync(RequestOptions options = null);
- //IChannel
+ //IChannel
+ string IChannel.Name => null;
+
Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult(null); //Overriden
IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
index 9732c65e8..355bd656c 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs
@@ -53,22 +53,22 @@ namespace Discord.Rest
}
public Task GetMessageAsync(ulong id, RequestOptions options = null)
- => ChannelHelper.GetMessageAsync(this, Discord, id, options);
+ => ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
public Task> GetPinnedMessagesAsync(RequestOptions options = null)
- => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
+ => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options);
+ => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
@@ -131,7 +131,9 @@ namespace Discord.Rest
IDisposable IMessageChannel.EnterTypingState(RequestOptions options)
=> EnterTypingState(options);
- //IChannel
+ //IChannel
+ string IChannel.Name => $"@{Recipient}";
+
Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult(GetUser(id));
IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options)
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
index 5e9c0dcbf..9aeb99447 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs
@@ -66,22 +66,22 @@ namespace Discord.Rest
}
public Task GetMessageAsync(ulong id, RequestOptions options = null)
- => ChannelHelper.GetMessageAsync(this, Discord, id, options);
+ => ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
public Task> GetPinnedMessagesAsync(RequestOptions options = null)
- => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
+ => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options);
+ => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
index e8c82fcb5..2cb2a7d19 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
@@ -43,22 +43,22 @@ namespace Discord.Rest
=> ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options);
public Task GetMessageAsync(ulong id, RequestOptions options = null)
- => ChannelHelper.GetMessageAsync(this, Discord, id, options);
+ => ChannelHelper.GetMessageAsync(this, Discord, id, null, options);
public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, null, options);
public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, null, options);
public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null)
- => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options);
+ => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, null, options);
public Task> GetPinnedMessagesAsync(RequestOptions options = null)
- => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options);
+ => ChannelHelper.GetPinnedMessagesAsync(this, Discord, null, options);
public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options);
+ => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, null, options);
public Task SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, options);
+ => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, null, options);
public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS, RequestOptions options = null)
- => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, options);
+ => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, null, options);
public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null)
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options);
diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
index 373c6bb52..c62f59071 100644
--- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@@ -11,8 +10,6 @@ namespace Discord.Rest
{
internal static class MessageHelper
{
- private static readonly Regex _emojiRegex = new Regex(@"<:(.+?):(\d+?)>", RegexOptions.Compiled);
-
public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func,
RequestOptions options)
{
@@ -37,17 +34,94 @@ namespace Discord.Rest
await client.ApiClient.RemovePinAsync(msg.ChannelId, msg.Id, options);
}
- public static ImmutableArray GetEmojis(string text)
+ public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, ImmutableArray userMentions)
{
- var matches = _emojiRegex.Matches(text);
- var builder = ImmutableArray.CreateBuilder(matches.Count);
- foreach (var match in matches.OfType())
+ var tags = new SortedList();
+
+ int index = 0;
+ while (true)
{
+ index = text.IndexOf('<', index);
+ if (index == -1) break;
+ int endIndex = text.IndexOf('>', index + 1);
+ if (endIndex == -1) break;
+ string content = text.Substring(index, endIndex - index + 1);
+
ulong id;
- if (ulong.TryParse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id))
- builder.Add(new Emoji(id, match.Groups[1].Value, match.Index, match.Length));
+ if (MentionUtils.TryParseUser(content, out id))
+ {
+ IUser mentionedUser = null;
+ foreach (var mention in userMentions)
+ {
+ if (mention.Id == id)
+ {
+ mentionedUser = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult();
+ if (mentionedUser == null)
+ mentionedUser = mention;
+ break;
+ }
+ }
+ tags.Add(index, new Tag(TagType.UserMention, index, content.Length, id, mentionedUser));
+ }
+ else if (MentionUtils.TryParseChannel(content, out id))
+ {
+ IChannel mentionedChannel = null;
+ if (guild != null)
+ mentionedChannel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult();
+ tags.Add(index, new Tag(TagType.ChannelMention, index, content.Length, id, mentionedChannel));
+ }
+ else if (MentionUtils.TryParseRole(content, out id))
+ {
+ IRole mentionedRole = null;
+ if (guild != null)
+ mentionedRole = guild.GetRole(id);
+ tags.Add(index, new Tag(TagType.RoleMention, index, content.Length, id, mentionedRole));
+ }
+ else
+ {
+ Emoji emoji;
+ if (Emoji.TryParse(content, out emoji))
+ tags.Add(index, new Tag(TagType.Emoji, index, content.Length, id, emoji));
+ }
+ index = endIndex + 1;
+ }
+
+ index = 0;
+ while (true)
+ {
+ index = text.IndexOf("@everyone", index);
+ if (index == -1) break;
+
+ tags.Add(index, new Tag