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()