Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for user's avatar decorations #2668

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ default ImageProxy getAvatar()
return avatarUrl == null ? null : new ImageProxy(avatarUrl);
}

@Nullable
User.AvatarDecoration getAvatarDecoration();

/**
* The URL for the member's effective avatar image.
* If they do not have a per guild avatar set, this will return the URL of
Expand Down
84 changes: 84 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import net.dv8tion.jda.internal.entities.UserSnowflakeImpl;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.Helpers;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
Expand Down Expand Up @@ -89,6 +90,8 @@ public interface User extends UserSnowflake
String DEFAULT_AVATAR_URL = "https://cdn.discordapp.com/embed/avatars/%s.png";
/** Template for {@link Profile#getBannerUrl()} */
String BANNER_URL = "https://cdn.discordapp.com/banners/%s/%s.%s";
/** Template for {@link AvatarDecoration#getDecorationAvatarUrl()} */
String DECORATION_AVATAR_URL = "https://cdn.discordapp.com/avatar-decoration-presets/%s.png";

/** Used to keep consistency between color values used in the API */
int DEFAULT_ACCENT_COLOR_RAW = 0x1FFFFFFF; // java.awt.Color fills the MSB with FF, we just use 1F to provide better consistency
Expand Down Expand Up @@ -361,6 +364,14 @@ default ImageProxy getEffectiveAvatar()
*/
int getFlagsRaw();

/**
* Returns the possibly-null {@link AvatarDecoration} of this user. If the user has not set a decoration avatar, this will return null.
*
* @return The possibly-null avatar decoration of this user
*/
@Nullable
AvatarDecoration getAvatarDecoration();

/**
* Represents the information contained in a {@link User User}'s profile.
*
Expand Down Expand Up @@ -455,6 +466,79 @@ public String toString()
}
}

/**
* Represents the avatar decoration of a {@link User User}.
*/
class AvatarDecoration
{

private final String decorationAvatarId;
private final String skuId;

public AvatarDecoration(String decorationAvatarId, String skuId)
{
this.decorationAvatarId = decorationAvatarId;
this.skuId = skuId;
}

/**
* The never-null SKU id of the {@link User User} decoration avatar.
*
* @return The never-null SKU id of the {@link User User} decoration avatar.
*/
@Nonnull
public String getSkuId()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this useful to a bot?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how for now, but maybe JDA will add support for SKU later on? Should I just remove that entirely?

{
return skuId;
}

/**
* The never-null avatar ID for this user's decoration avatar image.
*
* @return The never-null avatar ID for this user's decoration avatar image.
*/
@Nonnull
public String getDecorationAvatarId()
{
return decorationAvatarId;
}

/**
* The URL for the user's decoration avatar image.
*
* @return The never-null String containing the {@link User User} decoration avatar url.
*
* @see User#DECORATION_AVATAR_URL
*/
@Nonnull
public String getDecorationAvatarUrl()
{
return Helpers.format(DECORATION_AVATAR_URL, decorationAvatarId);
}

/**
* Returns an {@link ImageProxy} for this user's decoration avatar.
*
* @return Never-null {@link ImageProxy} of this user's decoration avatar
*
* @see #getDecorationAvatarUrl()
*/
@Nonnull
public ImageProxy getDecorationAvatar()
{
return new ImageProxy(getDecorationAvatarUrl());
}

@Override
public String toString()
{
return new EntityString(this)
.addMetadata("decorationAvatarId", decorationAvatarId)
.addMetadata("skuId", skuId)
.toString();
}
}

/**
* Represents the bit offsets used by Discord for public flags
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package net.dv8tion.jda.api.events.guild.member.update;

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Indicates that a {@link net.dv8tion.jda.api.entities.Member Member} updated their {@link net.dv8tion.jda.api.entities.Guild Guild} {@link User.AvatarDecoration avatar decoration}.
*
* <p>Can be used to retrieve members who change their per guild avatar decoration, the triggering guild, the old avatar decoration and the new avatar decoration.
*
* <p>Identifier: {@code avatar_decoration}
*
* <p><b>Requirements</b><br>
*
* <p>This event requires the {@link net.dv8tion.jda.api.requests.GatewayIntent#GUILD_MEMBERS GUILD_MEMBERS} intent to be enabled.
* <br>{@link net.dv8tion.jda.api.JDABuilder#createDefault(String) createDefault(String)} and
* {@link net.dv8tion.jda.api.JDABuilder#createLight(String) createLight(String)} disable this by default!
*
* <p>Additionally, this event requires the {@link net.dv8tion.jda.api.utils.MemberCachePolicy MemberCachePolicy}
* to cache the updated members. Discord does not specifically tell us about the updates, but merely tells us the
* member was updated and gives us the updated member object. In order to fire a specific event like this we
* need to have the old member cached to compare against.
*/
public class GuildMemberUpdateAvatarDecorationEvent extends GenericGuildMemberUpdateEvent<User.AvatarDecoration> {

public static final String IDENTIFIER = "avatar_decoration";

public GuildMemberUpdateAvatarDecorationEvent(@NotNull JDA api, long responseNumber, @NotNull Member member, @Nullable User.AvatarDecoration next)
{
super(api, responseNumber, member, member.getAvatarDecoration(), next, IDENTIFIER);
}

/**
* The old avatar decoration
*
* @return The old avatar decoration
*/
@Nullable
public User.AvatarDecoration getOldAvatarDecoration()
{
return getOldValue();
}

/**
* The new avatar decoration
*
* @return The new avatar decoration
*/
@Nullable
public User.AvatarDecoration getNewAvatarDecoration()
{
return getNewValue();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package net.dv8tion.jda.api.events.user.update;

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.User;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Indicates that the {@link net.dv8tion.jda.api.entities.User.AvatarDecoration AvatarDecoration} of a {@link net.dv8tion.jda.api.entities.User User} changed.
*
* <p>Can be used to retrieve the User who changed their avatar and their previous Avatar Decoration object.
*
* <p>Identifier: {@code avatar_decoration}
*
* <p><b>Requirements</b><br>
*
* <p>This event requires the {@link net.dv8tion.jda.api.requests.GatewayIntent#GUILD_MEMBERS GUILD_MEMBERS} intent to be enabled.
* <br>{@link net.dv8tion.jda.api.JDABuilder#createDefault(String) createDefault(String)} and
* {@link net.dv8tion.jda.api.JDABuilder#createLight(String) createLight(String)} disable this by default!
*
* <p>Additionally, this event requires the {@link net.dv8tion.jda.api.utils.MemberCachePolicy MemberCachePolicy}
* to cache the updated members. Discord does not specifically tell us about the updates, but merely tells us the
* member was updated and gives us the updated member object. In order to fire a specific event like this we
* need to have the old member cached to compare against.
*/
public class UserUpdateAvatarDecorationEvent extends GenericUserUpdateEvent<User.AvatarDecoration>
{
public static final String IDENTIFIER = "avatar_decoration";

public UserUpdateAvatarDecorationEvent(@NotNull JDA api, long responseNumber, @NotNull User user, @Nullable User.AvatarDecoration oldAvatarDecoration)
{
super(api, responseNumber, user, oldAvatarDecoration, user.getAvatarDecoration(), IDENTIFIER);
}

/**
* The old avatar decoration
* @return The old avatar decoration
*/
@Nullable
public User.AvatarDecoration getOldAvatarDecoration()
{
return getOldValue();
}

/**
* The new avatar decoration
* @return The new avatar decoration
*/
@Nullable
public User.AvatarDecoration getNewAvatarDecoration()
{
return getNewValue();
}

}
2 changes: 2 additions & 0 deletions src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public void onUserUpdateGlobalName(@Nonnull UserUpdateGlobalNameEvent event) {}
@ForRemoval
public void onUserUpdateDiscriminator(@Nonnull UserUpdateDiscriminatorEvent event) {}
public void onUserUpdateAvatar(@Nonnull UserUpdateAvatarEvent event) {}
public void onUserUpdateAvatarDecoration(@Nonnull UserUpdateAvatarDecorationEvent event) {}
public void onUserUpdateOnlineStatus(@Nonnull UserUpdateOnlineStatusEvent event) {}
public void onUserUpdateActivityOrder(@Nonnull UserUpdateActivityOrderEvent event) {}
public void onUserUpdateFlags(@Nonnull UserUpdateFlagsEvent event) {}
Expand Down Expand Up @@ -312,6 +313,7 @@ public void onGuildMemberRoleRemove(@Nonnull GuildMemberRoleRemoveEvent event) {
public void onGuildMemberUpdate(@Nonnull GuildMemberUpdateEvent event) {}
public void onGuildMemberUpdateNickname(@Nonnull GuildMemberUpdateNicknameEvent event) {}
public void onGuildMemberUpdateAvatar(@Nonnull GuildMemberUpdateAvatarEvent event) {}
public void onGuildMemberUpdateAvatarDecoration(@Nonnull GuildMemberUpdateAvatarDecorationEvent event) {}
public void onGuildMemberUpdateBoostTime(@Nonnull GuildMemberUpdateBoostTimeEvent event) {}
public void onGuildMemberUpdatePending(@Nonnull GuildMemberUpdatePendingEvent event) {}
public void onGuildMemberUpdateFlags(@Nonnull GuildMemberUpdateFlagsEvent event) {}
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,18 @@ public UserImpl createUser(DataObject user)
? new User.Profile(id, user.getString("banner", null), user.getInt("accent_color", User.DEFAULT_ACCENT_COLOR_RAW))
: null;

User.AvatarDecoration avatarDecoration = user.optObject("avatar_decoration_data")
.map(o -> new User.AvatarDecoration(o.getString("asset"), o.getString("sku_id")))
.orElse(null);

if (newUser)
{
// Initial creation
userObj.setName(user.getString("username"))
.setGlobalName(user.getString("global_name", null))
.setDiscriminator(Short.parseShort(user.getString("discriminator", "0")))
.setAvatarId(user.getString("avatar", null))
.setAvatarDecoration(avatarDecoration)
ItsTheSky marked this conversation as resolved.
Show resolved Hide resolved
.setBot(user.getBoolean("bot"))
.setSystem(user.getBoolean("system"))
.setFlags(user.getInt("public_flags", 0))
Expand All @@ -481,6 +486,10 @@ public void updateUser(UserImpl userObj, DataObject user)
short newDiscriminator = Short.parseShort(user.getString("discriminator", "0"));
String oldAvatar = userObj.getAvatarId();
String newAvatar = user.getString("avatar", null);
User.AvatarDecoration oldAvatarDecoration = userObj.getAvatarDecoration();
User.AvatarDecoration newAvatarDecoration = user.optObject("avatar_decoration_data")
.map(o -> new User.AvatarDecoration(o.getString("asset"), o.getString("sku_id")))
.orElse(null);
int oldFlags = userObj.getFlagsRaw();
int newFlags = user.getInt("public_flags", 0);

Expand Down Expand Up @@ -523,6 +532,15 @@ public void updateUser(UserImpl userObj, DataObject user)
userObj, oldAvatar));
}

if (!Objects.equals(oldAvatarDecoration, newAvatarDecoration))
{
userObj.setAvatarDecoration(newAvatarDecoration);
jda.handleEvent(
new UserUpdateAvatarDecorationEvent(
jda, responseNumber,
userObj, oldAvatarDecoration));
}

if (oldFlags != newFlags)
{
userObj.setFlags(newFlags);
Expand Down Expand Up @@ -615,6 +633,11 @@ public MemberImpl createMember(GuildImpl guild, DataObject memberJson, DataObjec
if (!memberJson.isNull("flags"))
member.setFlags(memberJson.getInt("flags"));

User.AvatarDecoration avatarDecoration = memberJson.optObject("avatar_decoration_data")
.map(o -> new User.AvatarDecoration(o.getString("asset"), o.getString("sku_id")))
.orElse(null);
member.setAvatarDecoration(avatarDecoration);

long boostTimestamp = memberJson.isNull("premium_since")
? 0
: Helpers.toTimestamp(memberJson.getString("premium_since"));
Expand Down Expand Up @@ -743,6 +766,22 @@ public void updateMember(GuildImpl guild, MemberImpl member, DataObject content,
member, oldTime));
}
}
if (content.hasKey("avatar_decoration_data"))
{
DataObject avatarDecorationData = content.getObject("avatar_decoration_data");
User.AvatarDecoration oldAvatarDecoration = member.getAvatarDecoration();
User.AvatarDecoration newAvatarDecoration = new User.AvatarDecoration(
avatarDecorationData.getString("asset"),
avatarDecorationData.getString("sku_id"));
if (!Objects.equals(oldAvatarDecoration, newAvatarDecoration))
{
member.setAvatarDecoration(newAvatarDecoration);
getJDA().handleEvent(
new GuildMemberUpdateAvatarDecorationEvent(
getJDA(), responseNumber,
member, oldAvatarDecoration));
}
}

if (content.hasKey("communication_disabled_until"))
{
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/net/dv8tion/jda/internal/entities/MemberImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class MemberImpl implements Member
private User user;
private String nickname;
private String avatarId;
private User.AvatarDecoration avatarDecoration;
private long joinDate, boostDate, timeOutEnd;
private boolean pending = false;
private int flags;
Expand Down Expand Up @@ -190,6 +191,13 @@ public String getAvatarId()
return avatarId;
}

@Nullable
@Override
public User.AvatarDecoration getAvatarDecoration()
{
return avatarDecoration;
}

@Nonnull
@Override
public String getEffectiveName()
Expand Down Expand Up @@ -443,6 +451,12 @@ public MemberImpl setFlags(int flags)
return this;
}

public MemberImpl setAvatarDecoration(User.AvatarDecoration avatarDecoration)
{
this.avatarDecoration = avatarDecoration;
return this;
}

public Set<Role> getRoleSet()
{
return roles;
Expand Down