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(TagType.EveryoneMention, index, "@everyone".Length, 0, null)); + index++; } - return builder.ToImmutable(); + + index = 0; + while (true) + { + index = text.IndexOf("@here", index); + if (index == -1) break; + + tags.Add(index, new Tag(TagType.HereMention, index, "@here".Length, 0, null)); + index++; + } + + return tags.Values.ToImmutableArray(); + } + public static ImmutableArray FilterTagsByKey(TagType type, ImmutableArray tags) + { + return tags + .Where(x => x.Type == type) + .Select(x => x.Key) + .ToImmutableArray(); + } + public static ImmutableArray FilterTagsByValue(TagType type, ImmutableArray tags) + { + return tags + .Where(x => x.Type == type) + .Select(x => (T)x.Value) + .Where(x => x != null) + .ToImmutableArray(); } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 3da05a05d..87999caa0 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -8,6 +8,7 @@ namespace Discord.Rest { public abstract class RestMessage : RestEntity, IMessage, IUpdateable { + internal readonly IGuild _guild; private long _timestampTicks; public ulong ChannelId { get; } @@ -21,25 +22,26 @@ namespace Discord.Rest public virtual DateTimeOffset? EditedTimestamp => null; public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); - public virtual IReadOnlyCollection Emojis => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal RestMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) + internal RestMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) : base(discord, id) { ChannelId = channelId; Author = author; + _guild = guild; } - internal static RestMessage Create(BaseDiscordClient discord, Model model) + internal static RestMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { if (model.Type == MessageType.Default) - return RestUserMessage.Create(discord, model); + return RestUserMessage.Create(discord, guild, model); else - return RestSystemMessage.Create(discord, model); + return RestSystemMessage.Create(discord, guild, model); } internal virtual void Update(Model model) { diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index 0142aa314..58aac4129 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -8,13 +8,13 @@ namespace Discord.Rest { public MessageType Type { get; private set; } - internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) - : base(discord, id, channelId, author) + internal RestSystemMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) + : base(discord, id, channelId, author, guild) { } - internal new static RestSystemMessage Create(BaseDiscordClient discord, Model model) + internal new static RestSystemMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestSystemMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value)); + var entity = new RestSystemMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value), guild); entity.Update(model); return entity; } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index e97db2a0e..682704b22 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -15,10 +16,7 @@ namespace Discord.Rest private long? _editedTimestampTicks; private ImmutableArray _attachments; private ImmutableArray _embeds; - private ImmutableArray _emojis; - private ImmutableArray _mentionedChannelIds; - private ImmutableArray _mentionedRoles; - private ImmutableArray _mentionedUsers; + private ImmutableArray _tags; public ulong? WebhookId { get; private set; } @@ -28,18 +26,18 @@ namespace Discord.Rest public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; - public override IReadOnlyCollection Emojis => _emojis; - public override IReadOnlyCollection MentionedChannelIds => _mentionedChannelIds; - public override IReadOnlyCollection MentionedRoles => _mentionedRoles; - public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + public override IReadOnlyCollection MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); + public override IReadOnlyCollection MentionedRoles => MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); + public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); + public override IReadOnlyCollection Tags => _tags; - internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author) - : base(discord, id, channelId, author) + internal RestUserMessage(BaseDiscordClient discord, ulong id, ulong channelId, RestUser author, IGuild guild) + : base(discord, id, channelId, author, guild) { } - internal new static RestUserMessage Create(BaseDiscordClient discord, Model model) + internal new static RestUserMessage Create(BaseDiscordClient discord, IGuild guild, Model model) { - var entity = new RestUserMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value)); + var entity = new RestUserMessage(discord, model.Id, model.ChannelId, RestUser.Create(discord, model.Author.Value), guild); entity.Update(model); return entity; } @@ -87,13 +85,13 @@ namespace Discord.Rest _embeds = ImmutableArray.Create(); } - ImmutableArray mentions = ImmutableArray.Create(); + ImmutableArray mentions = ImmutableArray.Create(); if (model.Mentions.IsSpecified) { var value = model.Mentions.Value; if (value.Length > 0) { - var newMentions = ImmutableArray.CreateBuilder(value.Length); + var newMentions = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) newMentions.Add(RestUser.Create(Discord, value[i])); mentions = newMentions.ToImmutable(); @@ -103,11 +101,7 @@ namespace Discord.Rest if (model.Content.IsSpecified) { var text = model.Content.Value; - - _mentionedUsers = MentionUtils.GetUserMentions(text, null, mentions); - _mentionedChannelIds = MentionUtils.GetChannelMentions(text, null); - _mentionedRoles = MentionUtils.GetRoleMentions(text, null); - _emojis = MessageHelper.GetEmojis(text); + _tags = MessageHelper.ParseTags(text, null, _guild, mentions); model.Content = text; } } @@ -122,21 +116,9 @@ namespace Discord.Rest public Task UnpinAsync(RequestOptions options) => MessageHelper.UnpinAsync(this, Discord, options); - public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, - RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) - { - text = MentionUtils.ResolveUserMentions(text, null, MentionedUsers, userHandling); - text = MentionUtils.ResolveChannelMentions(text, null, channelHandling); - text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); - text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); - return text; - } + public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 998e576f2..9601d5323 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -38,7 +38,9 @@ namespace Discord.WebSocket internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; - //IChannel + //IChannel + string IChannel.Name => null; + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); //Overridden IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 5c02d457f..f91ab4c2d 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -9,7 +9,7 @@ namespace Discord.WebSocket internal static class SocketChannelHelper { public static IAsyncEnumerable> GetMessagesAsync(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, - ulong? fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) + ulong? fromMessageId, Direction dir, int limit, CacheMode mode, IGuild guild, RequestOptions options) { if (dir == Direction.Around) throw new NotImplementedException(); //TODO: Impl @@ -36,7 +36,7 @@ namespace Discord.WebSocket return result; //Download remaining messages - var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, cachedMessages.Min(x => x.Id), dir, limit, options); + var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, cachedMessages.Min(x => x.Id), dir, limit, guild, options); return result.Concat(downloadedMessages); } else @@ -45,7 +45,7 @@ namespace Discord.WebSocket return result; //Dont use cache in this case - return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); + return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, guild, options); } } public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 00849537c..5e481d3f2 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -48,15 +48,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, null, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, null, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, null, options); public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) @@ -64,14 +64,14 @@ namespace Discord.WebSocket public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); 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); @@ -123,11 +123,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, null, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, null, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, null, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) @@ -140,6 +140,8 @@ namespace Discord.WebSocket => EnterTypingState(options); //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.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index cc75139a1..00cf00082 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -71,15 +71,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, null, options); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, null, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, null, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, null, options); public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) @@ -87,14 +87,14 @@ namespace Discord.WebSocket public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); 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); @@ -186,11 +186,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, null, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, null, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, null, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index 4f84e6ee4..c35c6b370 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -54,15 +54,15 @@ namespace Discord.WebSocket { IMessage msg = _messages?.Get(id); if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options); + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, Guild, options); return msg; } public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, Guild, options); public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, Guild, options); public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, Guild, options); public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) @@ -70,14 +70,14 @@ namespace Discord.WebSocket public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, Guild, options); public Task SendMessageAsync(string text, bool isTTS, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, options); + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, Guild, 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, Guild, 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, Guild, options); public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages, options); @@ -124,11 +124,11 @@ namespace Discord.WebSocket return GetCachedMessage(id); } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, Guild, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, Guild, options); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, Guild, options); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, RequestOptions options) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 9badd0e48..89bfbfb01 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -22,10 +22,10 @@ namespace Discord.WebSocket public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); - public virtual IReadOnlyCollection Emojis => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedChannels => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index 8a5b9e34c..249e7b3ae 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Message; @@ -17,10 +16,7 @@ namespace Discord.WebSocket private long? _editedTimestampTicks; private ImmutableArray _attachments; private ImmutableArray _embeds; - private ImmutableArray _emojis; - private ImmutableArray _mentionedChannels; - private ImmutableArray _mentionedRoles; - private ImmutableArray _mentionedUsers; + private ImmutableArray _tags; public ulong? WebhookId { get; private set; } @@ -31,10 +27,10 @@ namespace Discord.WebSocket public override IReadOnlyCollection Attachments => _attachments; public override IReadOnlyCollection Embeds => _embeds; - public override IReadOnlyCollection Emojis => _emojis; - public override IReadOnlyCollection MentionedChannels => _mentionedChannels; - public override IReadOnlyCollection MentionedRoles => _mentionedRoles; - public override IReadOnlyCollection MentionedUsers => _mentionedUsers; + public override IReadOnlyCollection Tags => _tags; + public override IReadOnlyCollection MentionedChannels => MessageHelper.FilterTagsByValue(TagType.ChannelMention, _tags); + public override IReadOnlyCollection MentionedRoles => MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); + public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) : base(discord, id, channel, author) @@ -90,13 +86,13 @@ namespace Discord.WebSocket _embeds = ImmutableArray.Create(); } - ImmutableArray mentions = ImmutableArray.Create(); + ImmutableArray mentions = ImmutableArray.Create(); if (model.Mentions.IsSpecified) { var value = model.Mentions.Value; if (value.Length > 0) { - var newMentions = ImmutableArray.CreateBuilder(value.Length); + var newMentions = ImmutableArray.CreateBuilder(value.Length); for (int i = 0; i < value.Length; i++) newMentions.Add(SocketSimpleUser.Create(Discord, Discord.State, value[i])); mentions = newMentions.ToImmutable(); @@ -107,13 +103,7 @@ namespace Discord.WebSocket { var text = model.Content.Value; var guild = (Channel as SocketGuildChannel)?.Guild; - - _mentionedUsers = MentionUtils.GetUserMentions(text, Channel, mentions); - _mentionedChannels = MentionUtils.GetChannelMentions(text, guild) - .Select(x => guild?.GetChannel(x)) - .Where(x => x != null).ToImmutableArray(); - _mentionedRoles = MentionUtils.GetRoleMentions(text, guild); - _emojis = MessageHelper.GetEmojis(text); + _tags = MessageHelper.ParseTags(text, Channel, guild, mentions); model.Content = text; } } @@ -128,21 +118,9 @@ namespace Discord.WebSocket public Task UnpinAsync(RequestOptions options = null) => MessageHelper.UnpinAsync(this, Discord, options); - public string Resolve(UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content, userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(int startIndex, int length, UserMentionHandling userHandling = UserMentionHandling.Name, ChannelMentionHandling channelHandling = ChannelMentionHandling.Name, - RoleMentionHandling roleHandling = RoleMentionHandling.Name, EveryoneMentionHandling everyoneHandling = EveryoneMentionHandling.Ignore) - => Resolve(Content.Substring(startIndex, length), userHandling, channelHandling, roleHandling, everyoneHandling); - public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, - RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) - { - text = MentionUtils.ResolveUserMentions(text, null, MentionedUsers, userHandling); - text = MentionUtils.ResolveChannelMentions(text, null, channelHandling); - text = MentionUtils.ResolveRoleMentions(text, MentionedRoles, roleHandling); - text = MentionUtils.ResolveEveryoneMentions(text, everyoneHandling); - return text; - } + public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage;