Skip to content

Commit 7755b79

Browse files
ShengaeroMinnDevelopment
authored andcommitted
First pass on animated emote support (discord-jda#581)
1 parent e74d55a commit 7755b79

File tree

9 files changed

+85
-25
lines changed

9 files changed

+85
-25
lines changed

src/main/java/net/dv8tion/jda/core/entities/Emote.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ public interface Emote extends ISnowflake, IMentionable, IFakeable
135135
*/
136136
EmoteManagerUpdatable getManagerUpdatable();
137137

138+
/**
139+
* Whether or not this Emote is animated.
140+
*
141+
* <p>Animated Emotes are available to Discord Nitro users as well as Bot accounts.
142+
*
143+
* @return Whether the Emote is animated or not.
144+
*/
145+
boolean isAnimated();
146+
138147
/**
139148
* A String representation of the URL which leads to image displayed within the official Discord&trade; client
140149
* when this Emote is used
@@ -143,7 +152,7 @@ public interface Emote extends ISnowflake, IMentionable, IFakeable
143152
*/
144153
default String getImageUrl()
145154
{
146-
return "https://cdn.discordapp.com/emojis/" + getId() + ".png";
155+
return "https://cdn.discordapp.com/emojis/" + getId() + (isAnimated() ? ".gif" : ".png");
147156
}
148157

149158
/**
@@ -157,7 +166,7 @@ default String getImageUrl()
157166
@Override
158167
default String getAsMention()
159168
{
160-
return "<:" + getName() + ":" + getIdLong() + ">";
169+
return (isAnimated() ? "<a:" : "<:") + getName() + ":" + getIdLong() + ">";
161170
}
162171

163172
/**

src/main/java/net/dv8tion/jda/core/entities/EntityBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ public void createGuildFirstPass(JSONObject guild, Consumer<Guild> secondPassCal
199199
roleSet.add(guildObj.getRoleById(emoteRoles.getString(j)));
200200
emoteMap.put(emoteId, emoteObj
201201
.setName(object.optString("name"))
202+
.setAnimated(object.optBoolean("animated"))
202203
.setManaged(Helpers.optBoolean(object, "managed")));
203204
}
204205
}
@@ -942,6 +943,7 @@ public MessageReaction createMessageReaction(MessageChannel chan, long id, JSONO
942943
JSONObject emoji = obj.getJSONObject("emoji");
943944
final Long emojiID = emoji.isNull("id") ? null : emoji.getLong("id");
944945
final String name = emoji.optString("name", null);
946+
final boolean animated = emoji.optBoolean("animated");
945947
final int count = Helpers.optInt(obj, "count", -1);
946948
final boolean me = Helpers.optBoolean(obj, "me");
947949

@@ -951,7 +953,7 @@ public MessageReaction createMessageReaction(MessageChannel chan, long id, JSONO
951953
Emote emote = api.getEmoteById(emojiID);
952954
// creates fake emoji because no guild has this emoji id
953955
if (emote == null)
954-
emote = new EmoteImpl(emojiID, api).setName(name);
956+
emote = new EmoteImpl(emojiID, api).setAnimated(animated).setName(name);
955957
reactionEmote = new MessageReaction.ReactionEmote(emote);
956958
}
957959
else

src/main/java/net/dv8tion/jda/core/entities/Message.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ enum MentionType
10641064
/**
10651065
* Represents a mention for a {@link net.dv8tion.jda.core.entities.Emote Emote}
10661066
*/
1067-
EMOTE("<:([a-zA-Z0-9_]+):([0-9]+)>"),
1067+
EMOTE("<a?:([a-zA-Z0-9_]+):([0-9]+)>"),
10681068
/**
10691069
* Represents a mention for all active users, literal {@code @here}
10701070
*/

src/main/java/net/dv8tion/jda/core/entities/impl/EmoteImpl.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,7 @@
2929
import net.dv8tion.jda.core.requests.Route;
3030
import net.dv8tion.jda.core.requests.restaction.AuditableRestAction;
3131

32-
import java.util.Collections;
33-
import java.util.HashSet;
34-
import java.util.LinkedList;
35-
import java.util.List;
32+
import java.util.*;
3633

3734
/**
3835
* Represents a Custom Emote. (Emoji in official Discord API terminology)
@@ -46,21 +43,22 @@ public class EmoteImpl implements Emote
4643
private final long id;
4744
private final GuildImpl guild;
4845
private final JDAImpl api;
49-
private final HashSet<Role> roles;
46+
private final Set<Role> roles;
5047

5148
private final Object mngLock = new Object();
5249
private volatile EmoteManager manager = null;
5350
private volatile EmoteManagerUpdatable managerUpdatable = null;
5451

5552
private boolean managed = false;
53+
private boolean animated = false;
5654
private String name;
5755

5856
public EmoteImpl(long id, GuildImpl guild)
5957
{
6058
this.id = id;
6159
this.guild = guild;
6260
this.api = guild.getJDA();
63-
this.roles = new HashSet<>();
61+
this.roles = Collections.synchronizedSet(new HashSet<>());
6462
}
6563

6664
public EmoteImpl(long id, JDAImpl api)
@@ -147,6 +145,12 @@ public EmoteManagerUpdatable getManagerUpdatable()
147145
return m;
148146
}
149147

148+
@Override
149+
public boolean isAnimated()
150+
{
151+
return animated;
152+
}
153+
150154
@Override
151155
public AuditableRestAction<Void> delete()
152156
{
@@ -179,6 +183,12 @@ public EmoteImpl setName(String name)
179183
return this;
180184
}
181185

186+
public EmoteImpl setAnimated(boolean animated)
187+
{
188+
this.animated = animated;
189+
return this;
190+
}
191+
182192
public EmoteImpl setManaged(boolean val)
183193
{
184194
this.managed = val;
@@ -187,7 +197,7 @@ public EmoteImpl setManaged(boolean val)
187197

188198
// -- Set Getter --
189199

190-
public HashSet<Role> getRoleSet()
200+
public Set<Role> getRoleSet()
191201
{
192202
return this.roles;
193203
}
@@ -221,7 +231,7 @@ public String toString()
221231
public EmoteImpl clone()
222232
{
223233
if (isFake()) return null;
224-
EmoteImpl copy = new EmoteImpl(id, guild).setManaged(managed).setName(name);
234+
EmoteImpl copy = new EmoteImpl(id, guild).setManaged(managed).setAnimated(animated).setName(name);
225235
copy.roles.addAll(roles);
226236
return copy;
227237

src/main/java/net/dv8tion/jda/core/entities/impl/ReceivedMessage.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,10 +651,12 @@ public synchronized List<Emote> getEmotes()
651651
{
652652
final long emoteId = MiscUtil.parseSnowflake(matcher.group(2));
653653
final String emoteName = matcher.group(1);
654+
// Check animated by verifying whether or not it starts with <a: or <:
655+
final boolean animated = matcher.group(0).startsWith("<a:");
654656

655657
Emote emote = getJDA().getEmoteById(emoteId);
656658
if (emote == null)
657-
emote = new EmoteImpl(emoteId, api).setName(emoteName);
659+
emote = new EmoteImpl(emoteId, api).setAnimated(animated).setName(emoteName);
658660
emoteMentions.add(emote);
659661
}
660662
catch (NumberFormatException ignored) {}

src/main/java/net/dv8tion/jda/core/handle/GuildEmojisUpdateHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ protected Long handleInternally(JSONObject content)
7878
}
7979

8080
emote.setName(current.getString("name"))
81+
.setAnimated(current.optBoolean("animated"))
8182
.setManaged(current.getBoolean("managed"));
8283
//update roles
8384
JSONArray roles = current.getJSONArray("roles");

src/main/java/net/dv8tion/jda/core/handle/MessageReactionHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ protected Long handleInternally(JSONObject content)
5858

5959
final Long emojiId = emoji.isNull("id") ? null : emoji.getLong("id");
6060
String emojiName = emoji.optString("name", null);
61+
final boolean emojiAnimated = emoji.optBoolean("animated");
6162

6263
if (emojiId == null && emojiName == null)
6364
{
@@ -104,7 +105,7 @@ protected Long handleInternally(JSONObject content)
104105
{
105106
if (emojiName != null)
106107
{
107-
emote = new EmoteImpl(emojiId, api).setName(emojiName);
108+
emote = new EmoteImpl(emojiId, api).setAnimated(emojiAnimated).setName(emojiName);
108109
}
109110
else
110111
{

src/main/java/net/dv8tion/jda/core/managers/GuildController.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,11 @@ public RoleAction createCopyOfRole(Role role)
20822082
* <br>Passing the roles field will be ignored unless the application is whitelisted as an emoji provider.
20832083
* For more information and to request whitelisting please contact {@code [email protected]}
20842084
*
2085+
* <p>Note that a guild is limited to 50 normal and 50 animated emotes by default.
2086+
* Some guilds are able to add additional emotes beyond this limitation due to the
2087+
* {@code MORE_EMOJI} feature (see {@link net.dv8tion.jda.core.entities.Guild#getFeatures() Guild.getFeatures()}).
2088+
* <br>Due to simplicity we do not check for these limits.
2089+
*
20852090
* <p>Possible {@link net.dv8tion.jda.core.requests.ErrorResponse ErrorResponses} caused by
20862091
* the returned {@link net.dv8tion.jda.core.requests.RestAction RestAction} include the following:
20872092
* <ul>
@@ -2106,7 +2111,6 @@ public RoleAction createCopyOfRole(Role role)
21062111
* If the guild is temporarily not {@link net.dv8tion.jda.core.entities.Guild#isAvailable() available}
21072112
*
21082113
* @return {@link net.dv8tion.jda.core.requests.restaction.AuditableRestAction AuditableRestAction} - Type: {@link net.dv8tion.jda.core.entities.Emote Emote}
2109-
* <br>The newly created Emote
21102114
*/
21112115
@CheckReturnValue
21122116
public AuditableRestAction<Emote> createEmote(String name, Icon icon, Role... roles)
@@ -2117,9 +2121,6 @@ public AuditableRestAction<Emote> createEmote(String name, Icon icon, Role... ro
21172121
Checks.notNull(icon, "Emote icon");
21182122
Checks.notNull(roles, "Roles");
21192123

2120-
// if (getJDA().getAccountType() != AccountType.CLIENT)
2121-
// throw new AccountTypeException(AccountType.CLIENT);
2122-
21232124
JSONObject body = new JSONObject();
21242125
body.put("name", name);
21252126
body.put("image", icon.getEncoding());
@@ -2139,14 +2140,18 @@ protected void handleResponse(Response response, Request<Emote> request)
21392140
}
21402141
JSONObject obj = response.getObject();
21412142
final long id = obj.getLong("id");
2142-
final String name = obj.getString("name");
2143+
final String name = obj.optString("name", null);
21432144
final boolean managed = Helpers.optBoolean(obj, "managed");
2144-
EmoteImpl emote = new EmoteImpl(id, guild).setName(name).setManaged(managed);
2145+
final boolean animated = obj.optBoolean("animated");
2146+
EmoteImpl emote = new EmoteImpl(id, guild).setName(name).setAnimated(animated).setManaged(managed);
21452147

2146-
JSONArray rolesArr = obj.getJSONArray("roles");
2147-
Set<Role> roleSet = emote.getRoleSet();
2148-
for (int i = 0; i < rolesArr.length(); i++)
2149-
roleSet.add(guild.getRoleById(rolesArr.getString(i)));
2148+
JSONArray rolesArr = obj.optJSONArray("roles");
2149+
if (rolesArr != null)
2150+
{
2151+
Set<Role> roleSet = emote.getRoleSet();
2152+
for (int i = 0; i < rolesArr.length(); i++)
2153+
roleSet.add(guild.getRoleById(rolesArr.getString(i)));
2154+
}
21502155
request.onSuccess(emote);
21512156
}
21522157
};

src/main/java/net/dv8tion/jda/core/utils/PermissionUtil.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ public static boolean canInteract(Role issuer, Role target)
116116
*
117117
* <p>If the specified Member is not in the emote's guild or the emote provided is fake this will return false.
118118
* Otherwise it will check if the emote is restricted to any roles and if that is the case if the Member has one of these.
119+
*
120+
* <p>In the case of an {@link net.dv8tion.jda.core.entities.Emote#isAnimated() animated} Emote, this will
121+
* check if the issuer is the currently logged in account, and then check if the account has
122+
* {@link net.dv8tion.jda.core.entities.SelfUser#isNitro() nitro}, and return false if it doesn't.
123+
* <br>For other accounts, this method will not take into account whether the emote is animated, as there is
124+
* no real way to check if the Member can interact with them.
125+
*
119126
* <br><b>Note</b>: This is not checking if the issuer owns the Guild or not.
120127
*
121128
* @param issuer
@@ -136,6 +143,23 @@ public static boolean canInteract(Member issuer, Emote emote)
136143

137144
if (!issuer.getGuild().equals(emote.getGuild()))
138145
throw new IllegalArgumentException("The issuer and target are not in the same Guild");
146+
147+
// We don't need to check based on the fact it is animated if it's a BOT account
148+
// because BOT accounts cannot have nitro, and have access to animated Emotes naturally.
149+
if (emote.isAnimated() && !issuer.getUser().isBot())
150+
{
151+
// This is a currently logged in client, meaning we can check if they have nitro or not.
152+
// If this isn't the currently logged in account, we just check it like a normal emote,
153+
// since there is no way to verify if they have nitro or not.
154+
if (issuer.getUser() instanceof SelfUser)
155+
{
156+
// If they don't have nitro, we immediately return
157+
// false, otherwise we proceed with the remaining checks.
158+
if (!((SelfUser)issuer.getUser()).isNitro())
159+
return false;
160+
}
161+
}
162+
139163
return (emote.getRoles().isEmpty() // Emote restricted to roles -> check if the issuer has them
140164
|| CollectionUtils.containsAny(issuer.getRoles(), emote.getRoles()));
141165
}
@@ -171,7 +195,8 @@ public static boolean canInteract(User issuer, Emote emote, MessageChannel chann
171195
if (!canInteract(member, emote))
172196
return false;
173197
// external means it is available outside of its own guild - works for bots or if its managed
174-
final boolean external = emote.isManaged() || (issuer.isBot() && botOverride);
198+
// currently we cannot check whether other users have nitro, we assume no here
199+
final boolean external = emote.isManaged() || (issuer.isBot() && botOverride) || isNitro(issuer);
175200
switch (channel.getType())
176201
{
177202
case TEXT:
@@ -184,6 +209,11 @@ public static boolean canInteract(User issuer, Emote emote, MessageChannel chann
184209
}
185210
}
186211

212+
private static boolean isNitro(User issuer)
213+
{
214+
return issuer instanceof SelfUser && ((SelfUser) issuer).isNitro();
215+
}
216+
187217
/**
188218
* Checks whether the specified {@link net.dv8tion.jda.core.entities.Emote Emote} can be used by the provided
189219
* {@link net.dv8tion.jda.core.entities.User User} in the {@link net.dv8tion.jda.core.entities.MessageChannel MessageChannel}.

0 commit comments

Comments
 (0)