diff --git a/src/main/java/net/dv8tion/jda/api/entities/Member.java b/src/main/java/net/dv8tion/jda/api/entities/Member.java index efa09c9e06..248d58396b 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Member.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Member.java @@ -22,10 +22,17 @@ import net.dv8tion.jda.annotations.ReplaceWith; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.OnlineStatus; +import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.channel.unions.DefaultGuildChannelUnion; import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; +import net.dv8tion.jda.api.exceptions.InsufficientPermissionException; +import net.dv8tion.jda.api.requests.Route; import net.dv8tion.jda.api.requests.restaction.AuditableRestAction; import net.dv8tion.jda.api.utils.ImageProxy; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.internal.requests.restaction.AuditableRestActionImpl; +import net.dv8tion.jda.internal.utils.Checks; +import net.dv8tion.jda.internal.utils.Helpers; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; @@ -144,8 +151,8 @@ public interface Member extends IMentionable, IPermissionHolder, UserSnowflake /** * Whether this Member is in time out. - *
While a Member is in time out, all permissions except {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} and - * {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. + *
While a Member is in time out, all permissions except {@link Permission#VIEW_CHANNEL VIEW_CHANNEL} and + * {@link Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. * * @return True, if this Member is in time out */ @@ -356,6 +363,25 @@ default ImageProxy getEffectiveAvatar() */ int getColorRaw(); + /** + * The raw {@link MemberFlag flags} bitset for this member. + * + * @return The raw flag bitset + */ + int getFlagsRaw(); + + /** + * The {@link MemberFlag flags} for this member as an {@link EnumSet}. + *
Modifying this set will not update the member, it is a copy of existing flags. + * + * @return The flags + */ + @Nonnull + default EnumSet getFlags() + { + return MemberFlag.fromRaw(getFlagsRaw()); + } + /** * Whether this Member can interact with the provided Member * (kick/ban/etc.) @@ -431,7 +457,7 @@ default ImageProxy getEffectiveAvatar() *
This is the channel that the Discord client will default to opening when a Guild is opened for the first time * after joining the guild. *
The default channel is the channel with the highest position in which the member has - * {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL} permissions. If this requirement doesn't apply for + * {@link Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL} permissions. If this requirement doesn't apply for * any channel in the guild, this method returns {@code null}. * * @return The {@link DefaultGuildChannelUnion channel} representing the default channel for this member @@ -467,7 +493,7 @@ default ImageProxy getEffectiveAvatar() * Timeframe unit as a {@link TimeUnit} (for example {@code member.ban(7, TimeUnit.DAYS)}). * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#BAN_MEMBERS} permission. + * If the logged in account does not have the {@link Permission#BAN_MEMBERS} permission. * @throws net.dv8tion.jda.api.exceptions.HierarchyException * If the logged in account cannot ban the other user due to permission hierarchy position. *
See {@link Member#canInteract(Member)} @@ -507,7 +533,7 @@ default AuditableRestAction ban(int deletionTimeframe, @Nonnull TimeUnit u * * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#KICK_MEMBERS} permission. + * If the logged in account does not have the {@link Permission#KICK_MEMBERS} permission. * @throws net.dv8tion.jda.api.exceptions.HierarchyException * If the logged in account cannot kick the other member due to permission hierarchy position. *
See {@link Member#canInteract(Member)} @@ -544,7 +570,7 @@ default AuditableRestAction kick() * The reason for this action or {@code null} if there is no specified reason * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#KICK_MEMBERS} permission. + * If the logged in account does not have the {@link Permission#KICK_MEMBERS} permission. * @throws net.dv8tion.jda.api.exceptions.HierarchyException * If the logged in account cannot kick the other member due to permission hierarchy position. *
See {@link Member#canInteract(Member)} @@ -570,8 +596,8 @@ default AuditableRestAction kick(@Nullable String reason) /** * Puts this Member in time out in this {@link net.dv8tion.jda.api.entities.Guild Guild} for a specific amount of time. - *
While a Member is in time out, all permissions except {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} and - * {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. + *
While a Member is in time out, all permissions except {@link Permission#VIEW_CHANNEL VIEW_CHANNEL} and + * {@link Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. * *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following: @@ -589,7 +615,7 @@ default AuditableRestAction kick(@Nullable String reason) * The {@link TimeUnit Unit} type of {@code amount} * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MODERATE_MEMBERS} permission. + * If the logged in account does not have the {@link Permission#MODERATE_MEMBERS} permission. * @throws IllegalArgumentException * If any of the following checks are true *

    @@ -609,8 +635,8 @@ default AuditableRestAction timeoutFor(long amount, @Nonnull TimeUnit unit /** * Puts this Member in time out in this {@link net.dv8tion.jda.api.entities.Guild Guild} for a specific amount of time. - *
    While a Member is in time out, all permissions except {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} and - * {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. + *
    While a Member is in time out, all permissions except {@link Permission#VIEW_CHANNEL VIEW_CHANNEL} and + * {@link Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. * *

    Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following: @@ -626,7 +652,7 @@ default AuditableRestAction timeoutFor(long amount, @Nonnull TimeUnit unit * The duration to put this Member in time out for * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MODERATE_MEMBERS} permission. + * If the logged in account does not have the {@link Permission#MODERATE_MEMBERS} permission. * @throws IllegalArgumentException * If any of the following checks are true *

      @@ -646,8 +672,8 @@ default AuditableRestAction timeoutFor(@Nonnull Duration duration) /** * Puts this Member in time out in this {@link net.dv8tion.jda.api.entities.Guild Guild} until the specified date. - *
      While a Member is in time out, all permissions except {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} and - * {@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. + *
      While a Member is in time out, all permissions except {@link Permission#VIEW_CHANNEL VIEW_CHANNEL} and + * {@link Permission#MESSAGE_HISTORY MESSAGE_HISTORY} are removed from them. * *

      Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following: @@ -663,7 +689,7 @@ default AuditableRestAction timeoutFor(@Nonnull Duration duration) * The time this Member will be released from time out * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MODERATE_MEMBERS} permission. + * If the logged in account does not have the {@link Permission#MODERATE_MEMBERS} permission. * @throws IllegalArgumentException * If any of the following checks are true *

        @@ -695,7 +721,7 @@ default AuditableRestAction timeoutUntil(@Nonnull TemporalAccessor tempora *
      * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MODERATE_MEMBERS} permission. + * If the logged in account does not have the {@link Permission#MODERATE_MEMBERS} permission. * * @return {@link net.dv8tion.jda.api.requests.restaction.AuditableRestAction AuditableRestAction} */ @@ -730,7 +756,7 @@ default AuditableRestAction removeTimeout() * Whether this {@link net.dv8tion.jda.api.entities.Member Member} should be muted or unmuted. * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#VOICE_DEAF_OTHERS} permission. + * If the logged in account does not have the {@link Permission#VOICE_DEAF_OTHERS} permission. * @throws java.lang.IllegalStateException * If the member is not currently connected to a voice channel. * @@ -768,7 +794,7 @@ default AuditableRestAction mute(boolean mute) * Whether this {@link net.dv8tion.jda.api.entities.Member Member} should be deafened or undeafened. * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException - * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#VOICE_DEAF_OTHERS} permission. + * If the logged in account does not have the {@link Permission#VOICE_DEAF_OTHERS} permission. * @throws java.lang.IllegalStateException * If the member is not currently connected to a voice channel. * @@ -788,9 +814,9 @@ default AuditableRestAction deafen(boolean deafen) * The nickname is visible to all members of this guild. * *

      To change the nickname for the currently logged in account - * only the Permission {@link net.dv8tion.jda.api.Permission#NICKNAME_CHANGE NICKNAME_CHANGE} is required. + * only the Permission {@link Permission#NICKNAME_CHANGE NICKNAME_CHANGE} is required. *
      To change the nickname of any {@link net.dv8tion.jda.api.entities.Member Member} for this {@link net.dv8tion.jda.api.entities.Guild Guild} - * the Permission {@link net.dv8tion.jda.api.Permission#NICKNAME_MANAGE NICKNAME_MANAGE} is required. + * the Permission {@link Permission#NICKNAME_MANAGE NICKNAME_MANAGE} is required. * *

      Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following: @@ -808,9 +834,9 @@ default AuditableRestAction deafen(boolean deafen) * * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException *

        - *
      • If attempting to set nickname for self and the logged in account has neither {@link net.dv8tion.jda.api.Permission#NICKNAME_CHANGE} - * or {@link net.dv8tion.jda.api.Permission#NICKNAME_MANAGE}
      • - *
      • If attempting to set nickname for another member and the logged in account does not have {@link net.dv8tion.jda.api.Permission#NICKNAME_MANAGE}
      • + *
      • If attempting to set nickname for self and the logged in account has neither {@link Permission#NICKNAME_CHANGE} + * or {@link Permission#NICKNAME_MANAGE}
      • + *
      • If attempting to set nickname for another member and the logged in account does not have {@link Permission#NICKNAME_MANAGE}
      • *
      * @throws net.dv8tion.jda.api.exceptions.HierarchyException * If attempting to set nickname for another member and the logged in account cannot manipulate the other user due to permission hierarchy position. @@ -826,4 +852,138 @@ default AuditableRestAction modifyNickname(@Nullable String nickname) { return getGuild().modifyNickname(this, nickname); } + + /** + * Updates the flags to the new flag set. + *
      If any of the flags is not {@link MemberFlag#isModifiable() modifiable}, it is not updated. + * + *

      Any flags not provided by the set will be disabled, all contained flags will be enabled. + * + * @param newFlags + * The new flags for the member. + * + * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException + * If the bot does not have {@link Permission#MODERATE_MEMBERS} in the guild + * @throws IllegalArgumentException + * If {@code null} is provided + * + * @return {@link AuditableRestAction} + */ + @Nonnull + @CheckReturnValue + default AuditableRestAction modifyFlags(@Nonnull Collection newFlags) + { + Checks.noneNull(newFlags, "Flags"); + if (!getGuild().getSelfMember().hasPermission(Permission.MODERATE_MEMBERS)) + throw new InsufficientPermissionException(getGuild(), Permission.MODERATE_MEMBERS); + int flags = getFlagsRaw(); + EnumSet updated = Helpers.copyEnumSet(MemberFlag.class, newFlags); + for (MemberFlag flag : MemberFlag.values()) + { + if (flag.modifiable) + { + if (updated.contains(flag)) + flags |= flag.raw; + else + flags &= ~flag.raw; + } + } + + DataObject body = DataObject.empty().put("flags", flags); + Route.CompiledRoute route = Route.Guilds.MODIFY_MEMBER.compile(getGuild().getId(), getId()); + return new AuditableRestActionImpl<>(getJDA(), route, body); + } + + /** + * Member flags indicating information about the membership state. + */ + enum MemberFlag + { + /** + * The Member has left and rejoined the guild + */ + DID_REJOIN(1, false), + /** + * The Member has completed the onboarding process + */ + COMPLETED_ONBOARDING(1 << 1, false), + /** + * The Member bypasses guild verification requirements + */ + BYPASSES_VERIFICATION(1 << 2, true), + /** + * The Member has started the onboarding process + */ + STARTED_ONBOARDING(1 << 3, false), + ; + + private final int raw; + private final boolean modifiable; + + + MemberFlag(int raw, boolean modifiable) + { + this.raw = raw; + this.modifiable = modifiable; + } + + /** + * The raw value used by Discord for this flag + * + * @return The raw value + */ + public int getRaw() + { + return raw; + } + + /** + * Whether this flag can be modified by the client + * + * @return True, if this flag can be modified + */ + public boolean isModifiable() + { + return modifiable; + } + + /** + * The {@link MemberFlag Flags} represented by the provided raw value. + *
      If the provided raw value is {@code 0} this will return an empty {@link java.util.EnumSet EnumSet}. + * + * @param raw + * The raw value + * + * @return EnumSet containing the flags represented by the provided raw value + */ + @Nonnull + public static EnumSet fromRaw(int raw) + { + EnumSet flags = EnumSet.noneOf(MemberFlag.class); + for (MemberFlag flag : values()) + { + if ((raw & flag.raw) == flag.raw) + flags.add(flag); + } + return flags; + } + + /** + * The raw value of the provided {@link MemberFlag Flags}. + *
      If the provided set is empty this will return {@code 0}. + * + * @param flags + * The flags + * + * @return The raw value of the provided flags + */ + public static int toRaw(@Nonnull Collection flags) + { + Checks.noneNull(flags, "Flags"); + int raw = 0; + for (MemberFlag flag : flags) + raw |= flag.raw; + return raw; + } + } } diff --git a/src/main/java/net/dv8tion/jda/api/events/guild/member/update/GuildMemberUpdateFlagsEvent.java b/src/main/java/net/dv8tion/jda/api/events/guild/member/update/GuildMemberUpdateFlagsEvent.java new file mode 100644 index 0000000000..3c4555efd6 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/guild/member/update/GuildMemberUpdateFlagsEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.api.events.guild.member.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Member; + +import javax.annotation.Nonnull; +import java.util.EnumSet; + +/** + * Indicates that the {@link Member#getFlags()} flags for a {@link Member} were updated. + * + *

      Identifier: {@code flags} + * + *

      Requirements
      + * + *

      This event requires the {@link net.dv8tion.jda.api.requests.GatewayIntent#GUILD_MEMBERS GUILD_MEMBERS} intent to be enabled. + *
      {@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! + * + *

      Additionally, this event also 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 GuildMemberUpdateFlagsEvent extends GenericGuildMemberUpdateEvent> +{ + public static final String IDENTIFIER = "flags"; + + public GuildMemberUpdateFlagsEvent(@Nonnull JDA api, long responseNumber, @Nonnull Member member, @Nonnull EnumSet previous) + { + super(api, responseNumber, member, previous, member.getFlags(), IDENTIFIER); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java index 0a9e5a3186..a0ea417616 100644 --- a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java +++ b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java @@ -299,6 +299,7 @@ public void onGuildMemberUpdateNickname(@Nonnull GuildMemberUpdateNicknameEvent public void onGuildMemberUpdateAvatar(@Nonnull GuildMemberUpdateAvatarEvent event) {} public void onGuildMemberUpdateBoostTime(@Nonnull GuildMemberUpdateBoostTimeEvent event) {} public void onGuildMemberUpdatePending(@Nonnull GuildMemberUpdatePendingEvent event) {} + public void onGuildMemberUpdateFlags(@Nonnull GuildMemberUpdateFlagsEvent event) {} public void onGuildMemberUpdateTimeOut(@Nonnull GuildMemberUpdateTimeOutEvent event) {} //Guild Voice Events diff --git a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java index dee8de93e5..a9609ed92f 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java @@ -600,6 +600,8 @@ public MemberImpl createMember(GuildImpl guild, DataObject memberJson, DataObjec member = new MemberImpl(guild, user); member.setNickname(memberJson.getString("nick", null)); member.setAvatarId(memberJson.getString("avatar", null)); + if (!memberJson.isNull("flags")) + member.setFlags(memberJson.getInt("flags")); long boostTimestamp = memberJson.isNull("premium_since") ? 0 @@ -768,6 +770,20 @@ public void updateMember(GuildImpl guild, MemberImpl member, DataObject content, } } + if (!content.isNull("flags")) + { + int flags = content.getInt("flags"); + int oldFlags = member.getFlagsRaw(); + if (flags != oldFlags) + { + member.setFlags(flags); + getJDA().handleEvent( + new GuildMemberUpdateFlagsEvent( + getJDA(), responseNumber, + member, Member.MemberFlag.fromRaw(oldFlags))); + } + } + updateUser((UserImpl) member.getUser(), content.getObject("user")); } diff --git a/src/main/java/net/dv8tion/jda/internal/entities/MemberImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/MemberImpl.java index 6f689c3256..192aebfea3 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/MemberImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/MemberImpl.java @@ -55,6 +55,7 @@ public class MemberImpl implements Member private String avatarId; private long joinDate, boostDate, timeOutEnd; private boolean pending = false; + private int flags; public MemberImpl(GuildImpl guild, User user) { @@ -225,6 +226,12 @@ public int getColorRaw() return Role.DEFAULT_COLOR_RAW; } + @Override + public int getFlagsRaw() + { + return flags; + } + @Nonnull @Override public EnumSet getPermissions() @@ -406,6 +413,12 @@ public MemberImpl setPending(boolean pending) return this; } + public MemberImpl setFlags(int flags) + { + this.flags = flags; + return this; + } + public Set getRoleSet() { return roles;