@@ -225,7 +225,7 @@ namespace Discord.Commands | |||
return false; | |||
} | |||
public SearchResult Search(IMessage message, int argPos) => Search(message, message.RawText.Substring(argPos)); | |||
public SearchResult Search(IMessage message, int argPos) => Search(message, message.Text.Substring(argPos)); | |||
public SearchResult Search(IMessage message, string input) | |||
{ | |||
string lowerInput = input.ToLowerInvariant(); | |||
@@ -237,7 +237,7 @@ namespace Discord.Commands | |||
return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | |||
} | |||
public Task<IResult> Execute(IMessage message, int argPos) => Execute(message, message.RawText.Substring(argPos)); | |||
public Task<IResult> Execute(IMessage message, int argPos) => Execute(message, message.Text.Substring(argPos)); | |||
public async Task<IResult> Execute(IMessage message, string input) | |||
{ | |||
var searchResult = Search(message, input); | |||
@@ -4,7 +4,7 @@ | |||
{ | |||
public static bool HasCharPrefix(this IMessage msg, char c, ref int argPos) | |||
{ | |||
var text = msg.RawText; | |||
var text = msg.Text; | |||
if (text.Length > 0 && text[0] == c) | |||
{ | |||
argPos = 1; | |||
@@ -14,7 +14,7 @@ | |||
} | |||
public static bool HasStringPrefix(this IMessage msg, string str, ref int argPos) | |||
{ | |||
var text = msg.RawText; | |||
var text = msg.Text; | |||
//str = str + ' '; | |||
if (text.StartsWith(str)) | |||
{ | |||
@@ -25,7 +25,7 @@ | |||
} | |||
public static bool HasMentionPrefix(this IMessage msg, IUser user, ref int argPos) | |||
{ | |||
var text = msg.RawText; | |||
var text = msg.Text; | |||
string mention = user.Mention + ' '; | |||
if (text.StartsWith(mention)) | |||
{ | |||
@@ -13,9 +13,7 @@ namespace Discord | |||
bool IsTTS { get; } | |||
/// <summary> Returns true if this message was added to its channel's pinned messages. </summary> | |||
bool IsPinned { get; } | |||
/// <summary> Returns the original, unprocessed text for this message. </summary> | |||
string RawText { get; } | |||
/// <summary> Returns the text for this message after mention processing. </summary> | |||
/// <summary> Returns the text for this message. </summary> | |||
string Text { get; } | |||
/// <summary> Gets the time this message was sent. </summary> | |||
DateTimeOffset Timestamp { get; } | |||
@@ -41,5 +39,10 @@ namespace Discord | |||
Task PinAsync(); | |||
/// <summary> Removes this message from its channel's pinned messages. </summary> | |||
Task UnpinAsync(); | |||
/// <summary> Transforms this message's text into a human readable form, resolving things like mentions to that object's name. </summary> | |||
string Resolve(int startIndex, int length, UserResolveMode userMode = UserResolveMode.NameOnly); | |||
/// <summary> Transforms this message's text into a human readable form, resolving things like mentions to that object's name. </summary> | |||
string Resolve(UserResolveMode userMode = UserResolveMode.NameOnly); | |||
} | |||
} |
@@ -16,7 +16,6 @@ namespace Discord | |||
private long? _editedTimestampTicks; | |||
public bool IsTTS { get; private set; } | |||
public string RawText { get; private set; } | |||
public string Text { get; private set; } | |||
public bool IsPinned { get; private set; } | |||
@@ -111,29 +110,15 @@ namespace Discord | |||
if (model.Content.IsSpecified) | |||
{ | |||
RawText = model.Content.Value; | |||
var text = model.Content.Value; | |||
if (guildChannel != null) | |||
{ | |||
var orderedMentionedUsers = ImmutableArray.CreateBuilder<IUser>(5); | |||
Text = MentionUtils.CleanUserMentions(RawText, Channel.IsAttached ? Channel : null, MentionedUsers, orderedMentionedUsers); | |||
MentionedUsers = orderedMentionedUsers.ToImmutable(); | |||
var roles = ImmutableArray.CreateBuilder<IRole>(5); | |||
Text = MentionUtils.CleanRoleMentions(Text, guildChannel.Guild, roles); | |||
MentionedRoles = roles.ToImmutable(); | |||
if (guildChannel.IsAttached) //It's too expensive to do a channel lookup in REST mode | |||
{ | |||
var channelIds = ImmutableArray.CreateBuilder<ulong>(5); | |||
Text = MentionUtils.CleanChannelMentions(Text, guildChannel.Guild, channelIds); | |||
MentionedChannelIds = channelIds.ToImmutable(); | |||
} | |||
else | |||
MentionedChannelIds = MentionUtils.GetChannelMentions(RawText); | |||
MentionedUsers = MentionUtils.GetUserMentions(text, Channel.IsAttached ? Channel : null, MentionedUsers); | |||
MentionedChannelIds = MentionUtils.GetChannelMentions(text, guildChannel.Guild); | |||
MentionedRoles = MentionUtils.GetRoleMentions(text, guildChannel.Guild); | |||
} | |||
else | |||
Text = RawText; | |||
Text = text; | |||
} | |||
} | |||
@@ -168,16 +153,32 @@ namespace Discord | |||
else | |||
await Discord.ApiClient.DeleteDMMessageAsync(Channel.Id, Id).ConfigureAwait(false); | |||
} | |||
/// <summary> Adds this message to its channel's pinned messages. </summary> | |||
public async Task PinAsync() | |||
{ | |||
await Discord.ApiClient.AddPinAsync(Channel.Id, Id).ConfigureAwait(false); | |||
} | |||
/// <summary> Removes this message from its channel's pinned messages. </summary> | |||
public async Task UnpinAsync() | |||
{ | |||
await Discord.ApiClient.RemovePinAsync(Channel.Id, Id).ConfigureAwait(false); | |||
} | |||
public string Resolve(int startIndex, int length, UserResolveMode userMode = UserResolveMode.NameOnly) | |||
=> Resolve(Text.Substring(startIndex, length), userMode); | |||
public string Resolve(UserResolveMode userMode = UserResolveMode.NameOnly) | |||
=> Resolve(Text, userMode); | |||
private string Resolve(string text, UserResolveMode userMode = UserResolveMode.NameOnly) | |||
{ | |||
var guild = (Channel as IGuildChannel)?.Guild; | |||
text = MentionUtils.ResolveUserMentions(text, Channel, MentionedUsers, userMode); | |||
if (guild != null) | |||
{ | |||
if (guild.IsAttached) //It's too expensive to do a channel lookup in REST mode | |||
text = MentionUtils.ResolveChannelMentions(text, guild); | |||
text = MentionUtils.ResolveRoleMentions(text, guild, MentionedRoles); | |||
} | |||
return text; | |||
} | |||
public override string ToString() => Text; | |||
private string DebuggerDisplay => $"{Author}: {Text}{(Attachments.Count > 0 ? $" [{Attachments.Count} Attachments]" : "")}"; | |||
@@ -0,0 +1,8 @@ | |||
namespace Discord | |||
{ | |||
public enum UserResolveMode | |||
{ | |||
NameOnly = 0, | |||
NameAndDiscriminator | |||
} | |||
} |
@@ -65,6 +65,7 @@ namespace Discord | |||
channelId = 0; | |||
return false; | |||
} | |||
/// <summary> Parses a provided role mention string. </summary> | |||
public static ulong ParseRole(string mentionText) | |||
{ | |||
@@ -87,44 +88,72 @@ namespace Discord | |||
roleId = 0; | |||
return false; | |||
} | |||
internal static ImmutableArray<IUser> GetUserMentions(string text, IMessageChannel channel, IReadOnlyCollection<IUser> fallbackUsers) | |||
{ | |||
var matches = _userRegex.Matches(text); | |||
var builder = ImmutableArray.CreateBuilder<IUser>(matches.Count); | |||
foreach (var match in matches.OfType<Match>()) | |||
{ | |||
ulong id; | |||
if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||
{ | |||
IUser user = null; | |||
if (channel != null) | |||
user = channel.GetUserAsync(id).GetAwaiter().GetResult() as IUser; | |||
if (user == null) | |||
{ | |||
foreach (var fallbackUser in fallbackUsers) | |||
{ | |||
if (fallbackUser.Id == id) | |||
{ | |||
user = fallbackUser; | |||
break; | |||
} | |||
} | |||
} | |||
/// <summary> Gets the ids of all users mentioned in a provided text.</summary> | |||
public static ImmutableArray<ulong> GetUserMentions(string text) => GetMentions(text, _userRegex).ToImmutable(); | |||
/// <summary> Gets the ids of all channels mentioned in a provided text.</summary> | |||
public static ImmutableArray<ulong> GetChannelMentions(string text) => GetMentions(text, _channelRegex).ToImmutable(); | |||
/// <summary> Gets the ids of all roles mentioned in a provided text.</summary> | |||
public static ImmutableArray<ulong> GetRoleMentions(string text) => GetMentions(text, _roleRegex).ToImmutable(); | |||
private static ImmutableArray<ulong>.Builder GetMentions(string text, Regex regex) | |||
if (user != null) | |||
builder.Add(user); | |||
} | |||
} | |||
return builder.ToImmutable(); | |||
} | |||
internal static ImmutableArray<ulong> GetChannelMentions(string text, IGuild guild) | |||
{ | |||
var matches = regex.Matches(text); | |||
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)) | |||
{ | |||
/*var channel = guild.GetChannelAsync(id).GetAwaiter().GetResult(); | |||
if (channel != null) | |||
builder.Add(channel);*/ | |||
builder.Add(id); | |||
} | |||
} | |||
return builder; | |||
return builder.ToImmutable(); | |||
} | |||
/*internal static string CleanUserMentions(string text, ImmutableArray<User> mentions) | |||
internal static ImmutableArray<IRole> GetRoleMentions(string text, IGuild guild) | |||
{ | |||
return _userRegex.Replace(text, new MatchEvaluator(e => | |||
var matches = _roleRegex.Matches(text); | |||
var builder = ImmutableArray.CreateBuilder<IRole>(matches.Count); | |||
foreach (var match in matches.OfType<Match>()) | |||
{ | |||
ulong id; | |||
if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||
if (ulong.TryParse(match.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||
{ | |||
for (int i = 0; i < mentions.Length; i++) | |||
{ | |||
var mention = mentions[i]; | |||
if (mention.Id == id) | |||
return '@' + mention.Username; | |||
} | |||
var role = guild.GetRole(id); | |||
if (role != null) | |||
builder.Add(role); | |||
} | |||
return e.Value; | |||
})); | |||
}*/ | |||
internal static string CleanUserMentions(string text, IMessageChannel channel, IReadOnlyCollection<IUser> fallbackUsers, ImmutableArray<IUser>.Builder mentions = null) | |||
} | |||
return builder.ToImmutable(); | |||
} | |||
internal static string ResolveUserMentions(string text, IMessageChannel channel, IReadOnlyCollection<IUser> mentions, UserResolveMode mode) | |||
{ | |||
return _userRegex.Replace(text, new MatchEvaluator(e => | |||
{ | |||
@@ -132,67 +161,71 @@ namespace Discord | |||
if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||
{ | |||
IUser user = null; | |||
if (channel != null) | |||
user = channel.GetUserAsync(id).GetAwaiter().GetResult() as IUser; | |||
if (user == null) | |||
foreach (var mention in mentions) | |||
{ | |||
foreach (var fallbackUser in fallbackUsers) | |||
if (mention.Id == id) | |||
{ | |||
if (fallbackUser.Id == id) | |||
{ | |||
user = fallbackUser; | |||
break; | |||
} | |||
user = mention; | |||
break; | |||
} | |||
} | |||
if (user != null) | |||
{ | |||
mentions.Add(user); | |||
string name = user.Username; | |||
var guildUser = user as IGuildUser; | |||
if (e.Value[2] == '!') | |||
{ | |||
var guildUser = user as IGuildUser; | |||
if (guildUser != null && guildUser.Nickname != null) | |||
return '@' + guildUser.Nickname; | |||
name = guildUser.Nickname; | |||
} | |||
switch (mode) | |||
{ | |||
case UserResolveMode.NameOnly: | |||
default: | |||
return $"@{name}"; | |||
case UserResolveMode.NameAndDiscriminator: | |||
return $"@{name}#{user.Discriminator}"; | |||
} | |||
return '@' + user.Username; | |||
} | |||
} | |||
return e.Value; | |||
})); | |||
} | |||
internal static string CleanChannelMentions(string text, IGuild guild, ImmutableArray<ulong>.Builder mentions = null) | |||
internal static string ResolveChannelMentions(string text, IGuild guild) | |||
{ | |||
return _channelRegex.Replace(text, new MatchEvaluator(e => | |||
{ | |||
ulong id; | |||
if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||
{ | |||
var channel = guild.GetChannelAsync(id).GetAwaiter().GetResult() as IGuildChannel; | |||
IGuildChannel channel = null; | |||
channel = guild.GetChannelAsync(id).GetAwaiter().GetResult(); | |||
if (channel != null) | |||
{ | |||
if (mentions != null) | |||
mentions.Add(channel.Id); | |||
return '#' + channel.Name; | |||
} | |||
} | |||
return e.Value; | |||
})); | |||
} | |||
internal static string CleanRoleMentions(string text, IGuild guild, ImmutableArray<IRole>.Builder mentions = null) | |||
internal static string ResolveRoleMentions(string text, IGuild guild, IReadOnlyCollection<IRole> mentions) | |||
{ | |||
return _roleRegex.Replace(text, new MatchEvaluator(e => | |||
{ | |||
ulong id; | |||
if (ulong.TryParse(e.Groups[1].Value, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | |||
{ | |||
var role = guild.GetRole(id); | |||
if (role != null) | |||
IRole role = null; | |||
foreach (var mention in mentions) | |||
{ | |||
if (mentions != null) | |||
mentions.Add(role); | |||
return '@' + role.Name; | |||
if (mention.Id == id) | |||
{ | |||
role = mention; | |||
break; | |||
} | |||
} | |||
if (role != null) | |||
return '@' + role.Name; | |||
} | |||
return e.Value; | |||
})); | |||