From 8aa241a56df3355efb2f2effb0bc9f0a3d582b36 Mon Sep 17 00:00:00 2001
From: pascal
Date: Thu, 12 Sep 2024 17:55:27 +0200
Subject: [PATCH] implement get voice state feature
---
.../net/dv8tion/jda/api/entities/Guild.java | 85 +++++++++++++++++++
.../net/dv8tion/jda/api/requests/Route.java | 1 +
.../jda/internal/entities/EntityBuilder.java | 46 +++++-----
.../jda/internal/entities/GuildImpl.java | 26 ++++++
4 files changed, 139 insertions(+), 19 deletions(-)
diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java
index 8c07ec5325..961af368dc 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java
@@ -2584,6 +2584,91 @@ default RestAction retrieveEmoji(@Nonnull CustomEmoji emoji)
@Nonnull
List getVoiceStates();
+ /**
+ * Load the member's voice state for the specified user.
+ *
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.
+ *
+ * Possible {@link net.dv8tion.jda.api.exceptions.ErrorResponseException ErrorResponseExceptions} include:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_VOICE_STATE}
+ *
The specified user does not exist, is not a member of this guild or is not connected to a voice channel
+ *
+ *
+ * @param id
+ * The user id to load the voice state from
+ *
+ * @return {@link RestAction} - Type: {@link GuildVoiceState}
+ */
+ @Nonnull
+ CacheRestAction retrieveMemberVoiceStateById(long id);
+
+ /**
+ * Load the member's voice state for the specified user.
+ *
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.
+ *
+ * Possible {@link net.dv8tion.jda.api.exceptions.ErrorResponseException ErrorResponseExceptions} include:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_VOICE_STATE}
+ *
The specified user does not exist, is not a member of this guild or is not connected to a voice channel
+ *
+ *
+ * @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 retrieveMemberVoiceStateById(@Nonnull String id)
+ {
+ return retrieveMemberVoiceStateById(MiscUtil.parseSnowflake(id));
+ }
+
+ /**
+ * Load the member's voice state for the specified {@link UserSnowflake}.
+ *
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.
+ *
+ * Possible {@link net.dv8tion.jda.api.exceptions.ErrorResponseException ErrorResponseExceptions} include:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_VOICE_STATE}
+ *
The specified user does not exist, is not a member of this guild or is not connected to a voice channel
+ *
+ *
+ * @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 retrieveMemberVoiceState(UserSnowflake user)
+ {
+ Checks.notNull(user, "User");
+ return retrieveMemberVoiceStateById(user.getId());
+ }
+
/**
* 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.
diff --git a/src/main/java/net/dv8tion/jda/api/requests/Route.java b/src/main/java/net/dv8tion/jda/api/requests/Route.java
index 0e165f9f85..ff9e95e800 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/Route.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/Route.java
@@ -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");
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 687b1df795..b63d60a6ca 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
@@ -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"));
member.setTimeOutEnd(timeOutTimestamp);
if (!memberJson.isNull("pending"))
@@ -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) {
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);
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 newRoles)
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
index 2ab78beceb..363d8d0039 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
@@ -1153,6 +1153,32 @@ public List getVoiceStates()
.collect(Helpers.toUnmodifiableList());
}
+ @Nonnull
+ @Override
+ public CacheRestAction retrieveMemberVoiceStateById(long id)
+ {
+ JDAImpl jda = getJDA();
+ return new DeferredRestAction<>(jda, GuildVoiceState.class,
+ () ->
+ {
+ MemberImpl member = (MemberImpl) getMemberById(id);
+ return member == null ? null : member.getVoiceState();
+ },
+ () ->
+ {
+ 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()