@@ -4,6 +4,7 @@ | |||
{ | |||
Ignore = 0, | |||
Remove, | |||
Name | |||
Name, | |||
Sanitize | |||
} | |||
} |
@@ -4,6 +4,7 @@ | |||
{ | |||
Ignore = 0, | |||
Remove, | |||
Name | |||
Name, | |||
Sanitize | |||
} | |||
} |
@@ -5,6 +5,7 @@ | |||
Ignore = 0, | |||
Remove, | |||
Name, | |||
NameAndDiscriminator | |||
NameAndDiscriminator, | |||
Sanitize | |||
} | |||
} |
@@ -1,14 +1,27 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Globalization; | |||
using System.Linq; | |||
using System.Text.RegularExpressions; | |||
namespace Discord | |||
{ | |||
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) | |||
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> | |||
public static ulong ParseUser(string mentionText) | |||
@@ -81,5 +94,191 @@ namespace Discord | |||
roleId = 0; | |||
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; | |||
_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; | |||
} | |||
} | |||
@@ -128,10 +128,10 @@ namespace Discord.Rest | |||
public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | |||
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; | |||
} | |||
@@ -1,7 +1,6 @@ | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
namespace Discord.API.Gateway | |||
{ | |||
@@ -14,6 +13,6 @@ namespace Discord.API.Gateway | |||
public int Limit { get; set; } | |||
[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 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; | |||
} | |||
} | |||
@@ -131,10 +131,10 @@ namespace Discord.WebSocket | |||
public string Resolve(string text, UserMentionHandling userHandling, ChannelMentionHandling channelHandling, | |||
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; | |||
} | |||