Skip to content

Commit cc205fa

Browse files
Whizyyyclaude
andauthored
Add support for guild member banners (#3126)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 69ed08b commit cc205fa

11 files changed

Lines changed: 365 additions & 0 deletions

File tree

src/main/java/net/dv8tion/jda/api/entities/Member.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,81 @@ default ImageProxy getEffectiveAvatar(@Nonnull ImageFormat preferredFormat) {
420420
return avatar == null ? getUser().getEffectiveAvatar(preferredFormat) : avatar;
421421
}
422422

423+
/**
424+
* The Discord Id for this member's per guild banner image.
425+
* If the member has not set a per guild banner, this will return null.
426+
*
427+
* @return Possibly-null String containing the {@link net.dv8tion.jda.api.entities.Member} per guild banner id.
428+
*/
429+
@Nullable
430+
String getBannerId();
431+
432+
/**
433+
* The URL for the member's per guild banner image.
434+
* If the member has not set a per guild banner, this will return null.
435+
*
436+
* @return Possibly-null String containing the {@link net.dv8tion.jda.api.entities.Member} per guild banner url.
437+
*/
438+
@Nullable
439+
default String getBannerUrl() {
440+
String bannerId = getBannerId();
441+
return bannerId == null
442+
? null
443+
: getBannerUrl(bannerId.startsWith("a_") ? ImageFormat.ANIMATED_WEBP : ImageFormat.PNG);
444+
}
445+
446+
/**
447+
* The URL for the member's per guild banner image.
448+
* If the member has not set a per guild banner, this will return null.
449+
*
450+
* @param format
451+
* The format in which the image should be
452+
*
453+
* @throws IllegalArgumentException
454+
* If the format is {@code null}
455+
*
456+
* @return Possibly-null String containing the {@link net.dv8tion.jda.api.entities.Member} per guild banner url.
457+
*
458+
* @see DiscordAssets#memberBanner(ImageFormat, String, String, String)
459+
*/
460+
@Nullable
461+
default String getBannerUrl(@Nonnull ImageFormat format) {
462+
ImageProxy proxy = getBanner(format);
463+
return proxy == null ? null : proxy.getUrl();
464+
}
465+
466+
/**
467+
* Returns an {@link ImageProxy} for this member's banner.
468+
*
469+
* @return Possibly-null {@link ImageProxy} of this member's banner
470+
*
471+
* @see #getBannerUrl()
472+
*/
473+
@Nullable
474+
default ImageProxy getBanner() {
475+
String bannerUrl = getBannerUrl();
476+
return bannerUrl == null ? null : new ImageProxy(bannerUrl);
477+
}
478+
479+
/**
480+
* Returns an {@link ImageProxy} for this member's banner.
481+
*
482+
* @param format
483+
* The format in which the image should be
484+
*
485+
* @throws IllegalArgumentException
486+
* If the format is {@code null}
487+
*
488+
* @return Possibly-null {@link ImageProxy} of this member's banner
489+
*
490+
* @see #getBannerUrl(ImageFormat)
491+
* @see DiscordAssets#memberBanner(ImageFormat, String, String, String)
492+
*/
493+
@Nullable
494+
default ImageProxy getBanner(@Nonnull ImageFormat format) {
495+
return DiscordAssets.memberBanner(format, getGuild().getId(), getId(), getBannerId());
496+
}
497+
423498
/**
424499
* The roles applied to this Member.
425500
* <br>The roles are ordered based on their position. The highest role being at index 0
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.dv8tion.jda.api.events.guild.member.update;
18+
19+
import net.dv8tion.jda.api.JDA;
20+
import net.dv8tion.jda.api.entities.Member;
21+
import net.dv8tion.jda.api.utils.DiscordAssets;
22+
import net.dv8tion.jda.api.utils.ImageFormat;
23+
import net.dv8tion.jda.api.utils.ImageProxy;
24+
25+
import javax.annotation.Nonnull;
26+
import javax.annotation.Nullable;
27+
28+
/**
29+
* Indicates that a {@link net.dv8tion.jda.api.entities.Member Member} updated their {@link net.dv8tion.jda.api.entities.Guild Guild} banner.
30+
*
31+
* <p>Can be used to retrieve members who change their per guild banner, the triggering guild, the old banner id and the new banner id.
32+
*
33+
* <p>Identifier: {@code banner}
34+
*
35+
* <p><b>Requirements</b><br>
36+
*
37+
* <p>This event requires the {@link net.dv8tion.jda.api.requests.GatewayIntent#GUILD_MEMBERS GUILD_MEMBERS} intent to be enabled.
38+
* <br>{@link net.dv8tion.jda.api.JDABuilder#createDefault(String) createDefault(String)} and
39+
* {@link net.dv8tion.jda.api.JDABuilder#createLight(String) createLight(String)} disable this by default!
40+
*
41+
* <p>Additionally, this event requires the {@link net.dv8tion.jda.api.utils.MemberCachePolicy MemberCachePolicy}
42+
* to cache the updated members. Discord does not specifically tell us about the updates, but merely tells us the
43+
* member was updated and gives us the updated member object. In order to fire a specific event like this we
44+
* need to have the old member cached to compare against.
45+
*/
46+
public class GuildMemberUpdateBannerEvent extends GenericGuildMemberUpdateEvent<String> {
47+
public static final String IDENTIFIER = "banner";
48+
49+
public GuildMemberUpdateBannerEvent(
50+
@Nonnull JDA api, long responseNumber, @Nonnull Member member, @Nullable String oldBannerId) {
51+
super(api, responseNumber, member, oldBannerId, member.getBannerId(), IDENTIFIER);
52+
}
53+
54+
/**
55+
* The old banner id
56+
*
57+
* @return The old banner id
58+
*/
59+
@Nullable
60+
public String getOldBannerId() {
61+
return getOldValue();
62+
}
63+
64+
/**
65+
* The previous banner url
66+
*
67+
* @return The previous banner url
68+
*/
69+
@Nullable
70+
public String getOldBannerUrl() {
71+
return previous == null
72+
? null
73+
: getOldBannerUrl(previous.startsWith("a_") ? ImageFormat.ANIMATED_WEBP : ImageFormat.PNG);
74+
}
75+
76+
/**
77+
* The previous banner url
78+
*
79+
* @param format
80+
* The format in which the image should be
81+
*
82+
* @throws IllegalArgumentException
83+
* If the format is {@code null}
84+
*
85+
* @return The previous banner url
86+
*
87+
* @see DiscordAssets#memberBanner(ImageFormat, String, String, String)
88+
*/
89+
@Nullable
90+
public String getOldBannerUrl(@Nonnull ImageFormat format) {
91+
ImageProxy proxy = getOldBanner(format);
92+
return proxy == null ? null : proxy.getUrl();
93+
}
94+
95+
/**
96+
* Returns an {@link ImageProxy} for this member's old banner.
97+
* <p>
98+
* <b>Note:</b> the old banner may not always be downloadable as it might have been removed from Discord.
99+
*
100+
* @return Possibly-null {@link ImageProxy} of this member's old banner
101+
*
102+
* @see #getOldBannerUrl()
103+
*/
104+
@Nullable
105+
public ImageProxy getOldBanner() {
106+
String oldBannerUrl = getOldBannerUrl();
107+
return oldBannerUrl == null ? null : new ImageProxy(oldBannerUrl);
108+
}
109+
110+
/**
111+
* Returns an {@link ImageProxy} for this member's old banner.
112+
* <p>
113+
* <b>Note:</b> the old banner may not always be downloadable as it might have been removed from Discord.
114+
*
115+
* @param format
116+
* The format in which the image should be
117+
*
118+
* @throws IllegalArgumentException
119+
* If the format is {@code null}
120+
*
121+
* @return Possibly-null {@link ImageProxy} of this member's old banner
122+
*
123+
* @see #getOldBannerUrl(ImageFormat)
124+
* @see DiscordAssets#memberBanner(ImageFormat, String, String, String)
125+
*/
126+
@Nullable
127+
public ImageProxy getOldBanner(@Nonnull ImageFormat format) {
128+
return DiscordAssets.memberBanner(format, getGuild().getId(), getUser().getId(), previous);
129+
}
130+
131+
/**
132+
* The new banner id
133+
*
134+
* @return The new banner id
135+
*/
136+
@Nullable
137+
public String getNewBannerId() {
138+
return getNewValue();
139+
}
140+
141+
/**
142+
* The url of the new banner
143+
*
144+
* @return The url of the new banner
145+
*/
146+
@Nullable
147+
public String getNewBannerUrl() {
148+
return next == null
149+
? null
150+
: getNewBannerUrl(next.startsWith("a_") ? ImageFormat.ANIMATED_WEBP : ImageFormat.PNG);
151+
}
152+
153+
/**
154+
* The url of the new banner
155+
*
156+
* @param format
157+
* The format in which the image should be
158+
*
159+
* @throws IllegalArgumentException
160+
* If the format is {@code null}
161+
*
162+
* @return The url of the new banner
163+
*
164+
* @see DiscordAssets#memberBanner(ImageFormat, String, String, String)
165+
*/
166+
@Nullable
167+
public String getNewBannerUrl(@Nonnull ImageFormat format) {
168+
ImageProxy proxy = getNewBanner(format);
169+
return proxy == null ? null : proxy.getUrl();
170+
}
171+
172+
/**
173+
* Returns an {@link ImageProxy} for this member's new banner.
174+
*
175+
* @return Possibly-null {@link ImageProxy} of this member's new banner
176+
*
177+
* @see #getNewBannerUrl()
178+
*/
179+
@Nullable
180+
public ImageProxy getNewBanner() {
181+
String newBannerUrl = getNewBannerUrl();
182+
return newBannerUrl == null ? null : new ImageProxy(newBannerUrl);
183+
}
184+
185+
/**
186+
* Returns an {@link ImageProxy} for this member's new banner.
187+
*
188+
* @param format
189+
* The format in which the image should be
190+
*
191+
* @throws IllegalArgumentException
192+
* If the format is {@code null}
193+
*
194+
* @return Possibly-null {@link ImageProxy} of this member's new banner
195+
*
196+
* @see #getNewBannerUrl(ImageFormat)
197+
* @see DiscordAssets#memberBanner(ImageFormat, String, String, String)
198+
*/
199+
@Nullable
200+
public ImageProxy getNewBanner(@Nonnull ImageFormat format) {
201+
return DiscordAssets.memberBanner(format, getGuild().getId(), getUser().getId(), next);
202+
}
203+
}

src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ public void onGuildMemberUpdateNickname(@Nonnull GuildMemberUpdateNicknameEvent
458458

459459
public void onGuildMemberUpdateAvatar(@Nonnull GuildMemberUpdateAvatarEvent event) {}
460460

461+
public void onGuildMemberUpdateBanner(@Nonnull GuildMemberUpdateBannerEvent event) {}
462+
461463
public void onGuildMemberUpdateBoostTime(@Nonnull GuildMemberUpdateBoostTimeEvent event) {}
462464

463465
public void onGuildMemberUpdatePending(@Nonnull GuildMemberUpdatePendingEvent event) {}

src/main/java/net/dv8tion/jda/api/utils/DiscordAssets.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,51 @@ public static ImageProxy memberAvatar(
362362
return format.finishProxy(builder, avatarId);
363363
}
364364

365+
/**
366+
* Returns an {@link ImageProxy} of a member's banner.
367+
* <br>This returns {@code null} if the banner ID is {@code null}.
368+
*
369+
* <p>At the time of writing, the supported formats are:
370+
* <ul>
371+
* <li>{@link ImageFormat#PNG PNG}</li>
372+
* <li>{@link ImageFormat#JPG JPG}</li>
373+
* <li>{@link ImageFormat#STATIC_WEBP STATIC_WEBP}</li>
374+
* <li>{@link ImageFormat#ANIMATED_WEBP ANIMATED_WEBP}</li>
375+
* <li>{@link ImageFormat#GIF GIF}</li>
376+
* </ul>
377+
*
378+
* @param format
379+
* The image format to request the image as
380+
* @param guildId
381+
* The guild ID
382+
* @param userId
383+
* The user ID
384+
* @param bannerId
385+
* The member's banner ID
386+
*
387+
* @throws IllegalArgumentException
388+
* If an argument is {@code null}, except for the banner ID
389+
*
390+
* @return An {@link ImageProxy} of the member's banner, or {@code null}
391+
*/
392+
@Contract("_, _, _, null -> null; _, _, _, !null -> !null")
393+
public static ImageProxy memberBanner(
394+
@Nonnull ImageFormat format, @Nonnull String guildId, @Nonnull String userId, @Nullable String bannerId) {
395+
Checks.notNull(format, "Format");
396+
Checks.isSnowflake(guildId, "Guild ID");
397+
Checks.isSnowflake(userId, "User ID");
398+
if (bannerId == null) {
399+
return null;
400+
}
401+
402+
HttpUrl.Builder builder = newUrl().addEncodedPathSegment("guilds")
403+
.addPathSegment(guildId)
404+
.addEncodedPathSegment("users")
405+
.addPathSegment(userId)
406+
.addEncodedPathSegment("banners");
407+
return format.finishProxy(builder, bannerId);
408+
}
409+
365410
/**
366411
* Returns an {@link ImageProxy} of a role's icon.
367412
* <br>This returns {@code null} if the icon ID is {@code null}.

src/main/java/net/dv8tion/jda/internal/entities/AbstractEntityBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ protected void configureGroupChannel(DataObject json, GroupChannelMixin<?> chann
195195
protected void configureMember(DataObject memberJson, MemberMixin<?> member) {
196196
member.setNickname(memberJson.getString("nick", null));
197197
member.setAvatarId(memberJson.getString("avatar", null));
198+
member.setBannerId(memberJson.getString("banner", null));
198199
if (!memberJson.isNull("flags")) {
199200
member.setFlags(memberJson.getInt("flags"));
200201
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,14 @@ public void updateMember(GuildImpl guild, MemberImpl member, DataObject content,
787787
getJDA().handleEvent(new GuildMemberUpdateAvatarEvent(getJDA(), responseNumber, member, oldAvatarId));
788788
}
789789
}
790+
if (content.hasKey("banner")) {
791+
String oldBannerId = member.getBannerId();
792+
String newBannerId = content.getString("banner", null);
793+
if (!Objects.equals(oldBannerId, newBannerId)) {
794+
member.setBannerId(newBannerId);
795+
getJDA().handleEvent(new GuildMemberUpdateBannerEvent(getJDA(), responseNumber, member, oldBannerId));
796+
}
797+
}
790798
if (content.hasKey("premium_since")) {
791799
long epoch = 0;
792800
if (!content.isNull("premium_since")) {

0 commit comments

Comments
 (0)