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

Add Guild#retrieveMemberVoiceState #2729

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
85 changes: 85 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Guild.java
Original file line number Diff line number Diff line change
Expand Up @@ -2584,6 +2584,91 @@ default RestAction<RichCustomEmoji> retrieveEmoji(@Nonnull CustomEmoji emoji)
@Nonnull
List<GuildVoiceState> getVoiceStates();

/**
* Load the member's voice state for the specified user.
* <br>If the member is already loaded it will be retrieved from {@link #getMemberById(long)} and
PascalNB marked this conversation as resolved.
Show resolved Hide resolved
* the voice state is immediately provided by {@link Member#getVoiceState()}.
* The cache consistency directly relies on the enabled {@link GatewayIntent GatewayIntents} as {@link GatewayIntent#GUILD_MEMBERS GatewayIntent.GUILD_MEMBERS}
* and {@link GatewayIntent#GUILD_VOICE_STATES GatewayIntent.GUILD_VOICE_STATES} are required to keep the cache updated with the latest information.
* You can use {@link CacheRestAction#useCache(boolean) useCache(false)} to always
* make a new request, which is the default behavior if the required intents are disabled.
*
* <p>Possible {@link net.dv8tion.jda.api.exceptions.ErrorResponseException ErrorResponseExceptions} include:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_VOICE_STATE}
* <br>The specified user does not exist, is not a member of this guild or is not connected to a voice channel</li>
* </ul>
*
* @param id
* The user id to load the voice state from
*
* @return {@link RestAction} - Type: {@link GuildVoiceState}
*/
@Nonnull
CacheRestAction<GuildVoiceState> retrieveMemberVoiceStateById(long id);
PascalNB marked this conversation as resolved.
Show resolved Hide resolved

/**
* Load the member's voice state for the specified user.
* <br>If the member is already loaded it will be retrieved from {@link #getMemberById(long)} and
* the voice state is immediately provided by {@link Member#getVoiceState()}.
* The cache consistency directly relies on the enabled {@link GatewayIntent GatewayIntents} as {@link GatewayIntent#GUILD_MEMBERS GatewayIntent.GUILD_MEMBERS}
* and {@link GatewayIntent#GUILD_VOICE_STATES GatewayIntent.GUILD_VOICE_STATES} are required to keep the cache updated with the latest information.
* You can use {@link CacheRestAction#useCache(boolean) useCache(false)} to always
* make a new request, which is the default behavior if the required intents are disabled.
*
* <p>Possible {@link net.dv8tion.jda.api.exceptions.ErrorResponseException ErrorResponseExceptions} include:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_VOICE_STATE}
* <br>The specified user does not exist, is not a member of this guild or is not connected to a voice channel</li>
* </ul>
*
* @param id
* The user id to load the voice state from
*
* @throws IllegalArgumentException
* If the provided id is empty or null
* @throws NumberFormatException
* If the provided id is not a snowflake
*
* @return {@link RestAction} - Type: {@link GuildVoiceState}
*/
@Nonnull
default CacheRestAction<GuildVoiceState> retrieveMemberVoiceStateById(@Nonnull String id)
{
return retrieveMemberVoiceStateById(MiscUtil.parseSnowflake(id));
}

/**
* Load the member's voice state for the specified {@link UserSnowflake}.
* <br>If the member is already loaded it will be retrieved from {@link #getMemberById(long)} and
* the voice state is immediately provided by {@link Member#getVoiceState()}.
* The cache consistency directly relies on the enabled {@link GatewayIntent GatewayIntents} as {@link GatewayIntent#GUILD_MEMBERS GatewayIntent.GUILD_MEMBERS}
* and {@link GatewayIntent#GUILD_VOICE_STATES GatewayIntent.GUILD_VOICE_STATES} are required to keep the cache updated with the latest information.
* You can use {@link CacheRestAction#useCache(boolean) useCache(false)} to always
* make a new request, which is the default behavior if the required intents are disabled.
*
* <p>Possible {@link net.dv8tion.jda.api.exceptions.ErrorResponseException ErrorResponseExceptions} include:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_VOICE_STATE}
* <br>The specified user does not exist, is not a member of this guild or is not connected to a voice channel</li>
* </ul>
*
* @param user
* The {@link UserSnowflake} for the member's voice state to retrieve.
* This can be a member or user instance or {@link User#fromId(long)}.
*
* @throws IllegalArgumentException
* If provided with null
*
* @return {@link RestAction} - Type: {@link GuildVoiceState}
*/
@Nonnull
default CacheRestAction<GuildVoiceState> retrieveMemberVoiceState(UserSnowflake user)
PascalNB marked this conversation as resolved.
Show resolved Hide resolved
{
Checks.notNull(user, "User");
return retrieveMemberVoiceStateById(user.getId());
PascalNB marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Returns the verification-Level of this Guild. Verification level is one of the factors that determines if a Member
* can send messages in a Guild.
Expand Down
1 change: 1 addition & 0 deletions src/main/java/net/dv8tion/jda/api/requests/Route.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public static class Guilds
public static final Route GET_GUILD_EMOJIS = new Route(GET, "guilds/{guild_id}/emojis");
public static final Route GET_AUDIT_LOGS = new Route(GET, "guilds/{guild_id}/audit-logs");
public static final Route GET_VOICE_REGIONS = new Route(GET, "guilds/{guild_id}/regions");
public static final Route GET_VOICE_STATE = new Route(GET, "guilds/{guild_id}/voice-states/{user_id}");
public static final Route UPDATE_VOICE_STATE = new Route(PATCH, "guilds/{guild_id}/voice-states/{user_id}");

public static final Route GET_INTEGRATIONS = new Route(GET, "guilds/{guild_id}/integrations");
Expand Down
46 changes: 27 additions & 19 deletions src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -616,13 +616,13 @@ public MemberImpl createMember(GuildImpl guild, DataObject memberJson, DataObjec
member.setFlags(memberJson.getInt("flags"));

long boostTimestamp = memberJson.isNull("premium_since")
? 0
: Helpers.toTimestamp(memberJson.getString("premium_since"));
? 0
: Helpers.toTimestamp(memberJson.getString("premium_since"));
member.setBoostDate(boostTimestamp);

long timeOutTimestamp = memberJson.isNull("communication_disabled_until")
? 0
: Helpers.toTimestamp(memberJson.getString("communication_disabled_until"));
? 0
: Helpers.toTimestamp(memberJson.getString("communication_disabled_until"));
PascalNB marked this conversation as resolved.
Show resolved Hide resolved
member.setTimeOutEnd(timeOutTimestamp);

if (!memberJson.isNull("pending"))
Expand Down Expand Up @@ -658,39 +658,47 @@ public MemberImpl createMember(GuildImpl guild, DataObject memberJson, DataObjec

// Load voice state and presence if necessary
if (voiceStateJson != null && member.getVoiceState() != null)
createVoiceState(guild, voiceStateJson, user, member);
createGuildVoiceState(member, voiceStateJson);
if (presence != null)
createPresence(member, presence);
return member;
}

private void createVoiceState(GuildImpl guild, DataObject voiceStateJson, User user, MemberImpl member)
{
public GuildVoiceState createGuildVoiceState(MemberImpl member, DataObject voiceStateJson) {
PascalNB marked this conversation as resolved.
Show resolved Hide resolved
GuildVoiceStateImpl voiceState = (GuildVoiceStateImpl) member.getVoiceState();
if (voiceState == null)
voiceState = new GuildVoiceStateImpl(member);
updateGuildVoiceState(voiceState, voiceStateJson, member);
return voiceState;
}

private void updateGuildVoiceState(GuildVoiceStateImpl oldVoiceState, DataObject newVoiceStateJson, MemberImpl member)
{
Guild guild = member.getGuild();

final long channelId = voiceStateJson.getLong("channel_id");
final long channelId = newVoiceStateJson.getLong("channel_id");
AudioChannel audioChannel = (AudioChannel) guild.getGuildChannelById(channelId);
if (audioChannel != null)
((AudioChannelMixin<?>) audioChannel).getConnectedMembersMap().put(member.getIdLong(), member);
Copy link
Member

Choose a reason for hiding this comment

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

We should avoid updating the channel connected members cache unless we actually cache voice states, otherwise this cache is incredibly unreliable.

else
LOG.error("Received a GuildVoiceState with a channel ID for a non-existent channel! ChannelId: {} GuildId: {} UserId: {}",
channelId, guild.getId(), user.getId());
channelId, guild.getId(), member.getId());

String requestToSpeak = voiceStateJson.getString("request_to_speak_timestamp", null);
String requestToSpeak = newVoiceStateJson.getString("request_to_speak_timestamp", null);
OffsetDateTime timestamp = null;
if (requestToSpeak != null)
timestamp = OffsetDateTime.parse(requestToSpeak);

// VoiceState is considered volatile so we don't expect anything to actually exist
voiceState.setSelfMuted(voiceStateJson.getBoolean("self_mute"))
.setSelfDeafened(voiceStateJson.getBoolean("self_deaf"))
.setGuildMuted(voiceStateJson.getBoolean("mute"))
.setGuildDeafened(voiceStateJson.getBoolean("deaf"))
.setSuppressed(voiceStateJson.getBoolean("suppress"))
.setSessionId(voiceStateJson.getString("session_id"))
.setStream(voiceStateJson.getBoolean("self_stream"))
.setRequestToSpeak(timestamp)
.setConnectedChannel(audioChannel);
oldVoiceState.setSelfMuted(newVoiceStateJson.getBoolean("self_mute"))
.setSelfDeafened(newVoiceStateJson.getBoolean("self_deaf"))
.setGuildMuted(newVoiceStateJson.getBoolean("mute"))
.setGuildDeafened(newVoiceStateJson.getBoolean("deaf"))
.setSuppressed(newVoiceStateJson.getBoolean("suppress"))
.setSessionId(newVoiceStateJson.getString("session_id"))
.setStream(newVoiceStateJson.getBoolean("self_stream"))
.setRequestToSpeak(timestamp)
.setConnectedChannel(audioChannel);
}

public void updateMember(GuildImpl guild, MemberImpl member, DataObject content, List<Role> newRoles)
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,32 @@ public List<GuildVoiceState> getVoiceStates()
.collect(Helpers.toUnmodifiableList());
}

@Nonnull
@Override
public CacheRestAction<GuildVoiceState> retrieveMemberVoiceStateById(long id)
{
JDAImpl jda = getJDA();
return new DeferredRestAction<>(jda, GuildVoiceState.class,
() ->
{
MemberImpl member = (MemberImpl) getMemberById(id);
return member == null ? null : member.getVoiceState();
PascalNB marked this conversation as resolved.
Show resolved Hide resolved
},
() ->
{
Route.CompiledRoute route = Route.Guilds.GET_VOICE_STATE.compile(getId(), Long.toUnsignedString(id));
return new RestActionImpl<>(jda, route, (response, request) ->
{
EntityBuilder entityBuilder = jda.getEntityBuilder();
DataObject voiceStateData = response.getObject();
//Creates voice state if VOICE_STATE cache flag is set
MemberImpl member = entityBuilder.createMember(this, voiceStateData.getObject("member"), voiceStateData, null);
entityBuilder.updateMemberCache(member);
return entityBuilder.createGuildVoiceState(member, voiceStateData);
});
}).useCache(jda.isIntent(GatewayIntent.GUILD_MEMBERS) && jda.isIntent(GatewayIntent.GUILD_VOICE_STATES));
}

@Nonnull
@Override
public VerificationLevel getVerificationLevel()
Expand Down
Loading