diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs
index 415c0c30d8..070b965ee2 100644
--- a/src/Discord.Net.Core/CDN.cs
+++ b/src/Discord.Net.Core/CDN.cs
@@ -19,8 +19,8 @@ public static string GetGuildSplashUrl(ulong guildId, string splashId)
=> splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null;
public static string GetChannelIconUrl(ulong channelId, string iconId)
=> iconId != null ? $"{DiscordConfig.CDNUrl}channel-icons/{channelId}/{iconId}.jpg" : null;
- public static string GetEmojiUrl(ulong emojiId)
- => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.png";
+ public static string GetEmojiUrl(ulong emojiId, bool animated)
+ => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.{(animated ? "gif" : "png")}";
public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format)
{
diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs
index f498c818e0..e3a228c832 100644
--- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs
+++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs
@@ -16,13 +16,18 @@ public class Emote : IEmote, ISnowflakeEntity
/// The ID of this emote
///
public ulong Id { get; }
+ ///
+ /// Is this emote animated?
+ ///
+ public bool Animated { get; }
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;
Name = name;
+ Animated = animated;
}
public override bool Equals(object other)
@@ -59,17 +64,20 @@ public static Emote Parse(string text)
public static bool TryParse(string text, out Emote result)
{
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)
return false;
if (!ulong.TryParse(text.Substring(splitIndex + 1, text.Length - splitIndex - 2), NumberStyles.None, CultureInfo.InvariantCulture, out ulong id))
return false;
- string name = text.Substring(2, splitIndex - 2);
- result = new Emote(id, name);
+ string name = text.Substring(startIndex, splitIndex - startIndex);
+ result = new Emote(id, name, animated);
return true;
}
return false;
@@ -77,6 +85,6 @@ public static bool TryParse(string text, out Emote result)
}
private string DebuggerDisplay => $"{Name} ({Id})";
- public override string ToString() => $"<:{Name}:{Id}>";
+ public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>";
}
}
diff --git a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs
index 8d776a4cd8..95b062bd23 100644
--- a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs
+++ b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs
@@ -13,7 +13,7 @@ public class GuildEmote : Emote
public bool RequireColons { get; }
public IReadOnlyList RoleIds { get; }
- internal GuildEmote(ulong id, string name, bool isManaged, bool requireColons, IReadOnlyList roleIds) : base(id, name)
+ internal GuildEmote(ulong id, string name, bool animated, bool isManaged, bool requireColons, IReadOnlyList roleIds) : base(id, name, animated)
{
IsManaged = isManaged;
RequireColons = requireColons;
@@ -21,6 +21,6 @@ internal GuildEmote(ulong id, string name, bool isManaged, bool requireColons, I
}
private string DebuggerDisplay => $"{Name} ({Id})";
- public override string ToString() => $"<:{Name}:{Id}>";
+ public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>";
}
}
diff --git a/src/Discord.Net.Rest/API/Common/Emoji.cs b/src/Discord.Net.Rest/API/Common/Emoji.cs
index bd9c4d466b..2bdfdcc36e 100644
--- a/src/Discord.Net.Rest/API/Common/Emoji.cs
+++ b/src/Discord.Net.Rest/API/Common/Emoji.cs
@@ -9,6 +9,8 @@ internal class Emoji
public ulong? Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
+ [JsonProperty("animated")]
+ public bool? Animated { get; set; }
[JsonProperty("roles")]
public ulong[] Roles { get; set; }
[JsonProperty("require_colons")]
diff --git a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs
index 05c8179353..6d3f72419d 100644
--- a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs
+++ b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs
@@ -18,7 +18,7 @@ internal static RestReaction Create(Model model)
{
IEmote emote;
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
emote = new Emoji(model.Emoji.Name);
return new RestReaction(emote, model.Count, model.Me);
diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
index b88a5b5159..74b05dacd1 100644
--- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
+++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
@@ -7,7 +7,7 @@ internal static class EntityExtensions
{
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)
diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs
index 35bee9e686..e8fa17a35b 100644
--- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs
+++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs
@@ -24,7 +24,7 @@ internal static SocketReaction Create(Model model, ISocketMessageChannel channel
{
IEmote emote;
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
emote = new Emoji(model.Emoji.Name);
return new SocketReaction(channel, model.MessageId, message, model.UserId, user, emote);
diff --git a/test/Discord.Net.Tests/Tests.Emotes.cs b/test/Discord.Net.Tests/Tests.Emotes.cs
new file mode 100644
index 0000000000..334975ce4d
--- /dev/null
+++ b/test/Discord.Net.Tests/Tests.Emotes.cs
@@ -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(() => Emote.Parse("invalid"));
+ }
+ [Fact]
+ public void Test_Animated_Emote_Parse()
+ {
+ Assert.True(Emote.TryParse("", 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("", out _));
+ Assert.False(Emote.TryParse("", out _));
+ Assert.False(Emote.TryParse("", out _));
+ }
+ }
+}