You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
317 lines
13 KiB
317 lines
13 KiB
using System;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace Discord
|
|
{
|
|
/// <summary>
|
|
/// Provides a series of helper methods for parsing mentions.
|
|
/// </summary>
|
|
public static class MentionUtils
|
|
{
|
|
private const char SanitizeChar = '\x200b';
|
|
|
|
//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}>";
|
|
/// <summary>
|
|
/// Returns a mention string based on the user ID.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A user mention string (e.g. <@80351110224678912>).
|
|
/// </returns>
|
|
public static string MentionUser(ulong id) => MentionUser(id.ToString(), true);
|
|
internal static string MentionChannel(string id) => $"<#{id}>";
|
|
/// <summary>
|
|
/// Returns a mention string based on the channel ID.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A channel mention string (e.g. <#103735883630395392>).
|
|
/// </returns>
|
|
public static string MentionChannel(ulong id) => MentionChannel(id.ToString());
|
|
internal static string MentionRole(string id) => $"<@&{id}>";
|
|
/// <summary>
|
|
/// Returns a mention string based on the role ID.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A role mention string (e.g. <@&165511591545143296>).
|
|
/// </returns>
|
|
public static string MentionRole(ulong id) => MentionRole(id.ToString());
|
|
|
|
/// <summary>
|
|
/// Parses a provided user mention string.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException">Invalid mention format.</exception>
|
|
public static ulong ParseUser(string text)
|
|
{
|
|
if (TryParseUser(text, out ulong id))
|
|
return id;
|
|
throw new ArgumentException(message: "Invalid mention format.", paramName: nameof(text));
|
|
}
|
|
/// <summary>
|
|
/// Tries to parse a provided user mention string.
|
|
/// </summary>
|
|
public static bool TryParseUser(string text, out ulong userId)
|
|
{
|
|
if (text.Length >= 3 && text[0] == '<' && text[1] == '@' && text[text.Length - 1] == '>')
|
|
{
|
|
if (text.Length >= 4 && text[2] == '!')
|
|
text = text.Substring(3, text.Length - 4); //<@!123>
|
|
else
|
|
text = text.Substring(2, text.Length - 3); //<@123>
|
|
|
|
if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out userId))
|
|
return true;
|
|
}
|
|
userId = 0;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a provided channel mention string.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException">Invalid mention format.</exception>
|
|
public static ulong ParseChannel(string text)
|
|
{
|
|
if (TryParseChannel(text, out ulong id))
|
|
return id;
|
|
throw new ArgumentException(message: "Invalid mention format.", paramName: nameof(text));
|
|
}
|
|
/// <summary>
|
|
/// Tries to parse a provided channel mention string.
|
|
/// </summary>
|
|
public static bool TryParseChannel(string text, out ulong channelId)
|
|
{
|
|
if (text.Length >= 3 && text[0] == '<' && text[1] == '#' && text[text.Length - 1] == '>')
|
|
{
|
|
text = text.Substring(2, text.Length - 3); //<#123>
|
|
|
|
if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out channelId))
|
|
return true;
|
|
}
|
|
channelId = 0;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a provided role mention string.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentException">Invalid mention format.</exception>
|
|
public static ulong ParseRole(string text)
|
|
{
|
|
if (TryParseRole(text, out ulong id))
|
|
return id;
|
|
throw new ArgumentException(message: "Invalid mention format.", paramName: nameof(text));
|
|
}
|
|
/// <summary>
|
|
/// Tries to parse a provided role mention string.
|
|
/// </summary>
|
|
public static bool TryParseRole(string text, out ulong roleId)
|
|
{
|
|
if (text.Length >= 4 && text[0] == '<' && text[1] == '@' && text[2] == '&' && text[text.Length - 1] == '>')
|
|
{
|
|
text = text.Substring(3, text.Length - 4); //<@&123>
|
|
|
|
if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out roleId))
|
|
return true;
|
|
}
|
|
roleId = 0;
|
|
return false;
|
|
}
|
|
|
|
internal static string Resolve(IMessage msg, int startIndex, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling)
|
|
{
|
|
var text = new StringBuilder(msg.Content.Substring(startIndex));
|
|
var tags = msg.Tags;
|
|
int indexOffset = -startIndex;
|
|
|
|
foreach (var tag in tags)
|
|
{
|
|
if (tag.Index < startIndex)
|
|
continue;
|
|
|
|
string newText = "";
|
|
switch (tag.Type)
|
|
{
|
|
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 + indexOffset, tag.Length);
|
|
text.Insert(tag.Index + indexOffset, newText);
|
|
indexOffset += newText.Length - tag.Length;
|
|
}
|
|
return text.ToString();
|
|
}
|
|
internal static string ResolveUserMention(ITag tag, TagHandling mode)
|
|
{
|
|
if (mode != TagHandling.Remove)
|
|
{
|
|
var user = tag.Value as IUser;
|
|
var guildUser = user as IGuildUser;
|
|
switch (mode)
|
|
{
|
|
case TagHandling.Name:
|
|
if (user != null)
|
|
return $"@{guildUser?.Nickname ?? user?.Username}";
|
|
else
|
|
return "";
|
|
case TagHandling.NameNoPrefix:
|
|
if (user != null)
|
|
return $"{guildUser?.Nickname ?? user?.Username}";
|
|
else
|
|
return "";
|
|
case TagHandling.FullName:
|
|
if (user != null)
|
|
return $"@{user.Username}#{user.Discriminator}";
|
|
else
|
|
return "";
|
|
case TagHandling.FullNameNoPrefix:
|
|
if (user != null)
|
|
return $"{user.Username}#{user.Discriminator}";
|
|
else
|
|
return "";
|
|
case TagHandling.Sanitize:
|
|
if (guildUser != null && guildUser.Nickname == null)
|
|
return MentionUser($"{SanitizeChar}{tag.Key}", false);
|
|
else
|
|
return MentionUser($"{SanitizeChar}{tag.Key}", true);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
internal static string ResolveChannelMention(ITag tag, TagHandling mode)
|
|
{
|
|
if (mode != TagHandling.Remove)
|
|
{
|
|
var channel = tag.Value as IChannel;
|
|
switch (mode)
|
|
{
|
|
case TagHandling.Name:
|
|
case TagHandling.FullName:
|
|
if (channel != null)
|
|
return $"#{channel.Name}";
|
|
else
|
|
return "";
|
|
case TagHandling.NameNoPrefix:
|
|
case TagHandling.FullNameNoPrefix:
|
|
if (channel != null)
|
|
return $"{channel.Name}";
|
|
else
|
|
return "";
|
|
case TagHandling.Sanitize:
|
|
return MentionChannel($"{SanitizeChar}{tag.Key}");
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
internal static string ResolveRoleMention(ITag tag, TagHandling mode)
|
|
{
|
|
if (mode != TagHandling.Remove)
|
|
{
|
|
var role = tag.Value as IRole;
|
|
switch (mode)
|
|
{
|
|
case TagHandling.Name:
|
|
case TagHandling.FullName:
|
|
if (role != null)
|
|
return $"@{role.Name}";
|
|
else
|
|
return "";
|
|
case TagHandling.NameNoPrefix:
|
|
case TagHandling.FullNameNoPrefix:
|
|
if (role != null)
|
|
return $"{role.Name}";
|
|
else
|
|
return "";
|
|
case TagHandling.Sanitize:
|
|
return MentionRole($"{SanitizeChar}{tag.Key}");
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
internal static string ResolveEveryoneMention(ITag tag, TagHandling mode)
|
|
{
|
|
if (mode != TagHandling.Remove)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case TagHandling.Name:
|
|
case TagHandling.FullName:
|
|
case TagHandling.NameNoPrefix:
|
|
case TagHandling.FullNameNoPrefix:
|
|
return "everyone";
|
|
case TagHandling.Sanitize:
|
|
return $"@{SanitizeChar}everyone";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
internal static string ResolveHereMention(ITag tag, TagHandling mode)
|
|
{
|
|
if (mode != TagHandling.Remove)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case TagHandling.Name:
|
|
case TagHandling.FullName:
|
|
case TagHandling.NameNoPrefix:
|
|
case TagHandling.FullNameNoPrefix:
|
|
return "here";
|
|
case TagHandling.Sanitize:
|
|
return $"@{SanitizeChar}here";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
internal static string ResolveEmoji(ITag tag, TagHandling mode)
|
|
{
|
|
if (mode != TagHandling.Remove)
|
|
{
|
|
Emote emoji = (Emote)tag.Value;
|
|
|
|
//Remove if its name contains any bad chars (prevents a few tag exploits)
|
|
for (int i = 0; i < emoji.Name.Length; i++)
|
|
{
|
|
char c = emoji.Name[i];
|
|
if (!char.IsLetterOrDigit(c) && c != '_' && c != '-')
|
|
return "";
|
|
}
|
|
|
|
switch (mode)
|
|
{
|
|
case TagHandling.Name:
|
|
case TagHandling.FullName:
|
|
return $":{emoji.Name}:";
|
|
case TagHandling.NameNoPrefix:
|
|
case TagHandling.FullNameNoPrefix:
|
|
return $"{emoji.Name}";
|
|
case TagHandling.Sanitize:
|
|
return $"<{emoji.Id}{SanitizeChar}:{SanitizeChar}{emoji.Name}>";
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
}
|
|
}
|