| @@ -4,6 +4,7 @@ | |||||
| { | { | ||||
| Ignore = 0, | Ignore = 0, | ||||
| Remove, | Remove, | ||||
| Name | |||||
| Name, | |||||
| Sanitize | |||||
| } | } | ||||
| } | } | ||||
| @@ -4,6 +4,7 @@ | |||||
| { | { | ||||
| Ignore = 0, | Ignore = 0, | ||||
| Remove, | Remove, | ||||
| Name | |||||
| Name, | |||||
| Sanitize | |||||
| } | } | ||||
| } | } | ||||
| @@ -5,6 +5,7 @@ | |||||
| Ignore = 0, | Ignore = 0, | ||||
| Remove, | Remove, | ||||
| Name, | Name, | ||||
| NameAndDiscriminator | |||||
| NameAndDiscriminator, | |||||
| Sanitize | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,14 +1,27 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Globalization; | using System.Globalization; | ||||
| using System.Linq; | |||||
| using System.Text.RegularExpressions; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| public static class MentionUtils | public static class MentionUtils | ||||
| { | { | ||||
| 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) | //If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake) | ||||
| public static string MentionUser(ulong id) => MentionsHelper.MentionUser(id, true); | |||||
| public static string MentionChannel(ulong id) => MentionsHelper.MentionChannel(id); | |||||
| public static string MentionRole(ulong id) => MentionsHelper.MentionRole(id); | |||||
| internal static string MentionUser(string id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>"; | |||||
| public static string MentionUser(ulong id) => MentionUser(id.ToString(), true); | |||||
| internal static string MentionChannel(string id) => $"<#{id}>"; | |||||
| public static string MentionChannel(ulong id) => MentionChannel(id.ToString()); | |||||
| internal static string MentionRole(string id) => $"<@&{id}>"; | |||||
| public static string MentionRole(ulong id) => MentionRole(id.ToString()); | |||||
| /// <summary> Parses a provided user mention string. </summary> | /// <summary> Parses a provided user mention string. </summary> | ||||
| public static ulong ParseUser(string mentionText) | public static ulong ParseUser(string mentionText) | ||||
| @@ -81,5 +94,191 @@ namespace Discord | |||||
| roleId = 0; | roleId = 0; | ||||
| return false; | return false; | ||||
| } | } | ||||
| internal static ImmutableArray<TUser> GetUserMentions<TUser>(string text, IMessageChannel channel, IReadOnlyCollection<TUser> mentionedUsers) | |||||
| where TUser : class, IUser | |||||
| { | |||||
| var matches = _userRegex.Matches(text); | |||||
| var builder = ImmutableArray.CreateBuilder<TUser>(matches.Count); | |||||
| foreach (var match in matches.OfType<Match>()) | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| 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); | |||||
| } | |||||
| } | |||||
| return builder.ToImmutable(); | |||||
| } | |||||
| internal static ImmutableArray<ulong> GetChannelMentions(string text, IGuild guild) | |||||
| { | |||||
| var matches = _channelRegex.Matches(text); | |||||
| var builder = ImmutableArray.CreateBuilder<ulong>(matches.Count); | |||||
| foreach (var match in matches.OfType<Match>()) | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| builder.Add(id); | |||||
| } | |||||
| return builder.ToImmutable(); | |||||
| } | |||||
| internal static ImmutableArray<TRole> GetRoleMentions<TRole>(string text, IGuild guild) | |||||
| where TRole : class, IRole | |||||
| { | |||||
| if (guild == null) | |||||
| return ImmutableArray.Create<TRole>(); | |||||
| var matches = _roleRegex.Matches(text); | |||||
| var builder = ImmutableArray.CreateBuilder<TRole>(matches.Count); | |||||
| foreach (var match in matches.OfType<Match>()) | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| var role = guild.GetRole(id) as TRole; | |||||
| if (role != null) | |||||
| builder.Add(role); | |||||
| } | |||||
| } | |||||
| return builder.ToImmutable(); | |||||
| } | |||||
| internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection<IUser> mentions, UserMentionHandling mode) | |||||
| { | |||||
| if (mode == UserMentionHandling.Ignore) return text; | |||||
| return _userRegex.Replace(text, new MatchEvaluator(e => | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| 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 ""; | |||||
| } | |||||
| } | |||||
| } | |||||
| return e.Value; | |||||
| })); | |||||
| } | |||||
| internal static string ResolveChannelMentions(string text, IGuild guild, ChannelMentionHandling mode) | |||||
| { | |||||
| if (mode == ChannelMentionHandling.Ignore) return text; | |||||
| return _channelRegex.Replace(text, new MatchEvaluator(e => | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| 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 ""; | |||||
| } | |||||
| } | |||||
| return e.Value; | |||||
| })); | |||||
| } | |||||
| internal static string ResolveRoleMentions(string text, IReadOnlyCollection<IRole> mentions, RoleMentionHandling mode) | |||||
| { | |||||
| if (mode == RoleMentionHandling.Ignore) return text; | |||||
| return _roleRegex.Replace(text, new MatchEvaluator(e => | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| 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 ""; | |||||
| } | |||||
| } | |||||
| return e.Value; | |||||
| })); | |||||
| } | |||||
| internal static string ResolveEveryoneMentions(string text, EveryoneMentionHandling mode) | |||||
| { | |||||
| if (mode == EveryoneMentionHandling.Ignore) return text; | |||||
| switch (mode) | |||||
| { | |||||
| case EveryoneMentionHandling.Sanitize: | |||||
| return text.Replace("@everyone", $"@{SanitizeChar}everyone").Replace("@here", $"@{SanitizeChar}here"); | |||||
| case EveryoneMentionHandling.Remove: | |||||
| default: | |||||
| return text.Replace("@everyone", "").Replace("@here", ""); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,198 +0,0 @@ | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | |||||
| using System.Globalization; | |||||
| using System.Linq; | |||||
| using System.Text.RegularExpressions; | |||||
| namespace Discord | |||||
| { | |||||
| internal static class MentionsHelper | |||||
| { | |||||
| 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(ulong id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>"; | |||||
| internal static string MentionChannel(ulong id) => $"<#{id}>"; | |||||
| internal static string MentionRole(ulong id) => $"<@&{id}>"; | |||||
| internal static ImmutableArray<TUser> GetUserMentions<TUser>(string text, IMessageChannel channel, IReadOnlyCollection<TUser> mentionedUsers) | |||||
| where TUser : class, IUser | |||||
| { | |||||
| var matches = _userRegex.Matches(text); | |||||
| var builder = ImmutableArray.CreateBuilder<TUser>(matches.Count); | |||||
| foreach (var match in matches.OfType<Match>()) | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| 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); | |||||
| } | |||||
| } | |||||
| return builder.ToImmutable(); | |||||
| } | |||||
| internal static ImmutableArray<ulong> GetChannelMentions(string text, IGuild guild) | |||||
| { | |||||
| var matches = _channelRegex.Matches(text); | |||||
| var builder = ImmutableArray.CreateBuilder<ulong>(matches.Count); | |||||
| foreach (var match in matches.OfType<Match>()) | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| builder.Add(id); | |||||
| } | |||||
| return builder.ToImmutable(); | |||||
| } | |||||
| internal static ImmutableArray<TRole> GetRoleMentions<TRole>(string text, IGuild guild) | |||||
| where TRole : class, IRole | |||||
| { | |||||
| if (guild == null) | |||||
| return ImmutableArray.Create<TRole>(); | |||||
| var matches = _roleRegex.Matches(text); | |||||
| var builder = ImmutableArray.CreateBuilder<TRole>(matches.Count); | |||||
| foreach (var match in matches.OfType<Match>()) | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| var role = guild.GetRole(id) as TRole; | |||||
| if (role != null) | |||||
| builder.Add(role); | |||||
| } | |||||
| } | |||||
| return builder.ToImmutable(); | |||||
| } | |||||
| internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection<IUser> mentions, UserMentionHandling mode) | |||||
| { | |||||
| if (mode == UserMentionHandling.Ignore) return text; | |||||
| return _userRegex.Replace(text, new MatchEvaluator(e => | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| 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.Remove: | |||||
| default: | |||||
| return ""; | |||||
| case UserMentionHandling.Name: | |||||
| return $"@{name}"; | |||||
| case UserMentionHandling.NameAndDiscriminator: | |||||
| return $"@{name}#{user.Discriminator}"; | |||||
| } | |||||
| } | |||||
| } | |||||
| return e.Value; | |||||
| })); | |||||
| } | |||||
| internal static string ResolveChannelMentions(string text, IGuild guild, ChannelMentionHandling mode) | |||||
| { | |||||
| if (mode == ChannelMentionHandling.Ignore) return text; | |||||
| return _channelRegex.Replace(text, new MatchEvaluator(e => | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| switch (mode) | |||||
| { | |||||
| case ChannelMentionHandling.Remove: | |||||
| return ""; | |||||
| case ChannelMentionHandling.Name: | |||||
| IGuildChannel channel = null; | |||||
| channel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); | |||||
| if (channel != null) | |||||
| return $"#{channel.Name}"; | |||||
| else | |||||
| return $"#deleted-channel"; | |||||
| } | |||||
| } | |||||
| return e.Value; | |||||
| })); | |||||
| } | |||||
| internal static string ResolveRoleMentions(string text, IReadOnlyCollection<IRole> mentions, RoleMentionHandling mode) | |||||
| { | |||||
| if (mode == RoleMentionHandling.Ignore) return text; | |||||
| return _roleRegex.Replace(text, new MatchEvaluator(e => | |||||
| { | |||||
| ulong id; | |||||
| if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||||
| { | |||||
| switch (mode) | |||||
| { | |||||
| case RoleMentionHandling.Remove: | |||||
| return ""; | |||||
| 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"; | |||||
| } | |||||
| } | |||||
| return e.Value; | |||||
| })); | |||||
| } | |||||
| internal static string ResolveEveryoneMentions(string text, EveryoneMentionHandling mode) | |||||
| { | |||||
| if (mode == EveryoneMentionHandling.Ignore) return text; | |||||
| switch (mode) | |||||
| { | |||||
| case EveryoneMentionHandling.Sanitize: | |||||
| return text.Replace("@everyone", "@\x200beveryone").Replace("@here", "@\x200bhere"); | |||||
| case EveryoneMentionHandling.Remove: | |||||
| default: | |||||
| return text.Replace("@everyone", "").Replace("@here", ""); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -102,9 +102,9 @@ namespace Discord.Rest | |||||
| { | { | ||||
| var text = model.Content.Value; | var text = model.Content.Value; | ||||
| _mentionedUsers = MentionsHelper.GetUserMentions(text, null, mentions); | |||||
| _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, null); | |||||
| _mentionedRoles = MentionsHelper.GetRoleMentions<RestRole>(text, null); | |||||
| _mentionedUsers = MentionUtils.GetUserMentions(text, null, mentions); | |||||
| _mentionedChannelIds = MentionUtils.GetChannelMentions(text, null); | |||||
| _mentionedRoles = MentionUtils.GetRoleMentions<RestRole>(text, null); | |||||
| model.Content = text; | model.Content = text; | ||||
| } | } | ||||
| } | } | ||||
| @@ -128,10 +128,10 @@ namespace Discord.Rest | |||||
| public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | ||||
| RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | ||||
| { | { | ||||
| text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling); | |||||
| text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling); | |||||
| text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling); | |||||
| text = MentionsHelper.ResolveEveryoneMentions(text, 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; | return text; | ||||
| } | } | ||||
| @@ -1,7 +1,6 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | |||||
| namespace Discord.API.Gateway | namespace Discord.API.Gateway | ||||
| { | { | ||||
| @@ -14,6 +13,6 @@ namespace Discord.API.Gateway | |||||
| public int Limit { get; set; } | public int Limit { get; set; } | ||||
| [JsonProperty("guild_id")] | [JsonProperty("guild_id")] | ||||
| private ulong[] GuildIds { get; set; } | |||||
| public IEnumerable<ulong> GuildIds { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -105,9 +105,9 @@ namespace Discord.WebSocket | |||||
| var text = model.Content.Value; | var text = model.Content.Value; | ||||
| var guild = (Channel as SocketGuildChannel)?.Guild; | var guild = (Channel as SocketGuildChannel)?.Guild; | ||||
| _mentionedUsers = MentionsHelper.GetUserMentions(text, Channel, mentions); | |||||
| _mentionedChannelIds = MentionsHelper.GetChannelMentions(text, guild); | |||||
| _mentionedRoles = MentionsHelper.GetRoleMentions<RestRole>(text, guild); | |||||
| _mentionedUsers = MentionUtils.GetUserMentions(text, Channel, mentions); | |||||
| _mentionedChannelIds = MentionUtils.GetChannelMentions(text, guild); | |||||
| _mentionedRoles = MentionUtils.GetRoleMentions<RestRole>(text, guild); | |||||
| model.Content = text; | model.Content = text; | ||||
| } | } | ||||
| } | } | ||||
| @@ -131,10 +131,10 @@ namespace Discord.WebSocket | |||||
| public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | ||||
| RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | RoleMentionHandling roleHandling, EveryoneMentionHandling everyoneHandling) | ||||
| { | { | ||||
| text = MentionsHelper.ResolveUserMentions(text, null, MentionedUsers, userHandling); | |||||
| text = MentionsHelper.ResolveChannelMentions(text, null, channelHandling); | |||||
| text = MentionsHelper.ResolveRoleMentions(text, MentionedRoles, roleHandling); | |||||
| text = MentionsHelper.ResolveEveryoneMentions(text, 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; | return text; | ||||
| } | } | ||||