Added support for animated emoji (#913)
* Added support for animated emoji This was such a useful feature Discord, I'm glad you added this instead of fixing bugs. * Fix bugs in emote parser * Added unit tests for emotes
This commit is contained in:
@@ -19,8 +19,8 @@ namespace Discord
|
|||||||
=> splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null;
|
=> splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null;
|
||||||
public static string GetChannelIconUrl(ulong channelId, string iconId)
|
public static string GetChannelIconUrl(ulong channelId, string iconId)
|
||||||
=> iconId != null ? $"{DiscordConfig.CDNUrl}channel-icons/{channelId}/{iconId}.jpg" : null;
|
=> iconId != null ? $"{DiscordConfig.CDNUrl}channel-icons/{channelId}/{iconId}.jpg" : null;
|
||||||
public static string GetEmojiUrl(ulong emojiId)
|
public static string GetEmojiUrl(ulong emojiId, bool animated)
|
||||||
=> $"{DiscordConfig.CDNUrl}emojis/{emojiId}.png";
|
=> $"{DiscordConfig.CDNUrl}emojis/{emojiId}.{(animated ? "gif" : "png")}";
|
||||||
|
|
||||||
public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format)
|
public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,13 +16,18 @@ namespace Discord
|
|||||||
/// The ID of this emote
|
/// The ID of this emote
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ulong Id { get; }
|
public ulong Id { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Is this emote animated?
|
||||||
|
/// </summary>
|
||||||
|
public bool Animated { get; }
|
||||||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
|
||||||
public string Url => CDN.GetEmojiUrl(Id);
|
public string Url => CDN.GetEmojiUrl(Id, Animated);
|
||||||
|
|
||||||
internal Emote(ulong id, string name)
|
internal Emote(ulong id, string name, bool animated)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
Animated = animated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Equals(object other)
|
public override bool Equals(object other)
|
||||||
@@ -59,17 +64,20 @@ namespace Discord
|
|||||||
public static bool TryParse(string text, out Emote result)
|
public static bool TryParse(string text, out Emote result)
|
||||||
{
|
{
|
||||||
result = null;
|
result = null;
|
||||||
if (text.Length >= 4 && text[0] == '<' && text[1] == ':' && text[text.Length - 1] == '>')
|
if (text.Length >= 4 && text[0] == '<' && (text[1] == ':' || (text[1] == 'a' && text[2] == ':')) && text[text.Length - 1] == '>')
|
||||||
{
|
{
|
||||||
int splitIndex = text.IndexOf(':', 2);
|
bool animated = text[1] == 'a';
|
||||||
|
int startIndex = animated ? 3 : 2;
|
||||||
|
|
||||||
|
int splitIndex = text.IndexOf(':', startIndex);
|
||||||
if (splitIndex == -1)
|
if (splitIndex == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!ulong.TryParse(text.Substring(splitIndex + 1, text.Length - splitIndex - 2), NumberStyles.None, CultureInfo.InvariantCulture, out ulong id))
|
if (!ulong.TryParse(text.Substring(splitIndex + 1, text.Length - splitIndex - 2), NumberStyles.None, CultureInfo.InvariantCulture, out ulong id))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
string name = text.Substring(2, splitIndex - 2);
|
string name = text.Substring(startIndex, splitIndex - startIndex);
|
||||||
result = new Emote(id, name);
|
result = new Emote(id, name, animated);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -77,6 +85,6 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||||
public override string ToString() => $"<:{Name}:{Id}>";
|
public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Discord
|
|||||||
public bool RequireColons { get; }
|
public bool RequireColons { get; }
|
||||||
public IReadOnlyList<ulong> RoleIds { get; }
|
public IReadOnlyList<ulong> RoleIds { get; }
|
||||||
|
|
||||||
internal GuildEmote(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList<ulong> roleIds) : base(id, name)
|
internal GuildEmote(ulong id, string name, bool animated, bool isManaged, bool requireColons, IReadOnlyList<ulong> roleIds) : base(id, name, animated)
|
||||||
{
|
{
|
||||||
IsManaged = isManaged;
|
IsManaged = isManaged;
|
||||||
RequireColons = requireColons;
|
RequireColons = requireColons;
|
||||||
@@ -21,6 +21,6 @@ namespace Discord
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string DebuggerDisplay => $"{Name} ({Id})";
|
private string DebuggerDisplay => $"{Name} ({Id})";
|
||||||
public override string ToString() => $"<:{Name}:{Id}>";
|
public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ namespace Discord.API
|
|||||||
public ulong? Id { get; set; }
|
public ulong? Id { get; set; }
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
[JsonProperty("animated")]
|
||||||
|
public bool? Animated { get; set; }
|
||||||
[JsonProperty("roles")]
|
[JsonProperty("roles")]
|
||||||
public ulong[] Roles { get; set; }
|
public ulong[] Roles { get; set; }
|
||||||
[JsonProperty("require_colons")]
|
[JsonProperty("require_colons")]
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace Discord.Rest
|
|||||||
{
|
{
|
||||||
IEmote emote;
|
IEmote emote;
|
||||||
if (model.Emoji.Id.HasValue)
|
if (model.Emoji.Id.HasValue)
|
||||||
emote = new Emote(model.Emoji.Id.Value, model.Emoji.Name);
|
emote = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated.GetValueOrDefault());
|
||||||
else
|
else
|
||||||
emote = new Emoji(model.Emoji.Name);
|
emote = new Emoji(model.Emoji.Name);
|
||||||
return new RestReaction(emote, model.Count, model.Me);
|
return new RestReaction(emote, model.Count, model.Me);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Discord.Rest
|
|||||||
{
|
{
|
||||||
public static GuildEmote ToEntity(this API.Emoji model)
|
public static GuildEmote ToEntity(this API.Emoji model)
|
||||||
{
|
{
|
||||||
return new GuildEmote(model.Id.Value, model.Name, model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles));
|
return new GuildEmote(model.Id.Value, model.Name, model.Animated.GetValueOrDefault(), model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Embed ToEntity(this API.Embed model)
|
public static Embed ToEntity(this API.Embed model)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Discord.WebSocket
|
|||||||
{
|
{
|
||||||
IEmote emote;
|
IEmote emote;
|
||||||
if (model.Emoji.Id.HasValue)
|
if (model.Emoji.Id.HasValue)
|
||||||
emote = new Emote(model.Emoji.Id.Value, model.Emoji.Name);
|
emote = new Emote(model.Emoji.Id.Value, model.Emoji.Name, model.Emoji.Animated.GetValueOrDefault());
|
||||||
else
|
else
|
||||||
emote = new Emoji(model.Emoji.Name);
|
emote = new Emoji(model.Emoji.Name);
|
||||||
return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote);
|
return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote);
|
||||||
|
|||||||
44
test/Discord.Net.Tests/Tests.Emotes.cs
Normal file
44
test/Discord.Net.Tests/Tests.Emotes.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
public class EmoteTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Test_Emote_Parse()
|
||||||
|
{
|
||||||
|
Assert.True(Emote.TryParse("<:typingstatus:394207658351263745>", out Emote emote));
|
||||||
|
Assert.NotNull(emote);
|
||||||
|
Assert.Equal("typingstatus", emote.Name);
|
||||||
|
Assert.Equal(394207658351263745UL, emote.Id);
|
||||||
|
Assert.False(emote.Animated);
|
||||||
|
Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1514056829775), emote.CreatedAt);
|
||||||
|
Assert.EndsWith("png", emote.Url);
|
||||||
|
}
|
||||||
|
[Fact]
|
||||||
|
public void Test_Invalid_Emote_Parse()
|
||||||
|
{
|
||||||
|
Assert.False(Emote.TryParse("invalid", out _));
|
||||||
|
Assert.False(Emote.TryParse("<:typingstatus:not_a_number>", out _));
|
||||||
|
Assert.Throws<ArgumentException>(() => Emote.Parse("invalid"));
|
||||||
|
}
|
||||||
|
[Fact]
|
||||||
|
public void Test_Animated_Emote_Parse()
|
||||||
|
{
|
||||||
|
Assert.True(Emote.TryParse("<a:typingstatus:394207658351263745>", out Emote emote));
|
||||||
|
Assert.NotNull(emote);
|
||||||
|
Assert.Equal("typingstatus", emote.Name);
|
||||||
|
Assert.Equal(394207658351263745UL, emote.Id);
|
||||||
|
Assert.True(emote.Animated);
|
||||||
|
Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1514056829775), emote.CreatedAt);
|
||||||
|
Assert.EndsWith("gif", emote.Url);
|
||||||
|
}
|
||||||
|
public void Test_Invalid_Amimated_Emote_Parse()
|
||||||
|
{
|
||||||
|
Assert.False(Emote.TryParse("<x:typingstatus:394207658351263745>", out _));
|
||||||
|
Assert.False(Emote.TryParse("<a:typingstatus>", out _));
|
||||||
|
Assert.False(Emote.TryParse("<a:typingstatus:not_a_number>", out _));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user