getVoiceChannels()
*
If there is no known {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel} with an id that matches the provided
* one, then this returns {@code null}.
*
+ * This may also contain {@link StageChannel StageChannels}!
+ *
* @param id
* The id of the {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel}.
* @throws java.lang.NumberFormatException
@@ -1730,6 +1814,8 @@ default VoiceChannel getVoiceChannelById(@Nonnull String id)
*
If there is no known {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel} with an id that matches the provided
* one, then this returns {@code null}.
*
+ *
This may also contain {@link StageChannel StageChannels}!
+ *
* @param id
* The id of the {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel}.
*
@@ -1745,6 +1831,8 @@ default VoiceChannel getVoiceChannelById(long id)
* An unmodifiable list of all {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels} that have the same name as the one provided.
*
If there are no {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels} with the provided name, then this returns an empty list.
*
+ *
This may also contain {@link StageChannel StageChannels}!
+ *
* @param name
* The name of the requested {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels}.
* @param ignoreCase
diff --git a/src/main/java/net/dv8tion/jda/api/Permission.java b/src/main/java/net/dv8tion/jda/api/Permission.java
index 3b7051f808..4cee18a9de 100644
--- a/src/main/java/net/dv8tion/jda/api/Permission.java
+++ b/src/main/java/net/dv8tion/jda/api/Permission.java
@@ -71,6 +71,8 @@ public enum Permission
MANAGE_WEBHOOKS( 29, true, true, "Manage Webhooks"),
MANAGE_EMOTES( 30, true, false, "Manage Emojis"),
+ REQUEST_TO_SPEAK( 32, true, true, "Request to Speak"),
+
UNKNOWN(-1, false, false, "Unknown");
/**
@@ -109,7 +111,7 @@ public enum Permission
*/
public static final long ALL_VOICE_PERMISSIONS
= Permission.getRaw(VOICE_STREAM, VOICE_CONNECT, VOICE_SPEAK, VOICE_MUTE_OTHERS,
- VOICE_DEAF_OTHERS, VOICE_MOVE_OTHERS, VOICE_USE_VAD, PRIORITY_SPEAKER);
+ VOICE_DEAF_OTHERS, VOICE_MOVE_OTHERS, VOICE_USE_VAD, PRIORITY_SPEAKER, REQUEST_TO_SPEAK);
private final int offset;
private final long raw;
diff --git a/src/main/java/net/dv8tion/jda/api/audit/ActionType.java b/src/main/java/net/dv8tion/jda/api/audit/ActionType.java
index d045eb9895..9b548f463c 100644
--- a/src/main/java/net/dv8tion/jda/api/audit/ActionType.java
+++ b/src/main/java/net/dv8tion/jda/api/audit/ActionType.java
@@ -16,6 +16,8 @@
package net.dv8tion.jda.api.audit;
+import net.dv8tion.jda.api.entities.Member;
+
/**
* ActionTypes for {@link net.dv8tion.jda.api.audit.AuditLogEntry AuditLogEntry} instances
*
Found via {@link net.dv8tion.jda.api.audit.AuditLogEntry#getType() AuditLogEntry.getType()}
@@ -444,6 +446,54 @@ public enum ActionType
*/
INTEGRATION_DELETE(82, TargetType.INTEGRATION),
+ /**
+ * A {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} was started by a {@link net.dv8tion.jda.api.entities.StageChannel#isModerator(Member) Stage Moderator}.
+ *
+ *
Possible Options
+ *
+ * - {@link net.dv8tion.jda.api.audit.AuditLogOption#CHANNEL CHANNEL}
+ *
+ *
+ * Possible Keys
+ *
+ * - {@link net.dv8tion.jda.api.audit.AuditLogKey#CHANNEL_TOPIC CHANNEL_TOPIC}
+ * - {@link net.dv8tion.jda.api.audit.AuditLogKey#PRIVACY_LEVEL STAGE_INSTANCE_PRIVACY_LEVEL}
+ *
+ */
+ STAGE_INSTANCE_CREATE(83, TargetType.STAGE_INSTANCE),
+
+ /**
+ * A {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} was updated by a {@link net.dv8tion.jda.api.entities.StageChannel#isModerator(Member) Stage Moderator}.
+ *
+ * Possible Options
+ *
+ * - {@link net.dv8tion.jda.api.audit.AuditLogOption#CHANNEL CHANNEL}
+ *
+ *
+ * Possible Keys
+ *
+ * - {@link net.dv8tion.jda.api.audit.AuditLogKey#CHANNEL_TOPIC CHANNEL_TOPIC}
+ * - {@link net.dv8tion.jda.api.audit.AuditLogKey#PRIVACY_LEVEL STAGE_INSTANCE_PRIVACY_LEVEL}
+ *
+ */
+ STAGE_INSTANCE_UPDATE(84, TargetType.STAGE_INSTANCE),
+
+ /**
+ * A {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} was deleted by a {@link net.dv8tion.jda.api.entities.StageChannel#isModerator(Member) Stage Moderator}.
+ *
+ * Possible Options
+ *
+ * - {@link net.dv8tion.jda.api.audit.AuditLogOption#CHANNEL CHANNEL}
+ *
+ *
+ * Possible Keys
+ *
+ * - {@link net.dv8tion.jda.api.audit.AuditLogKey#CHANNEL_TOPIC CHANNEL_TOPIC}
+ * - {@link net.dv8tion.jda.api.audit.AuditLogKey#PRIVACY_LEVEL STAGE_INSTANCE_PRIVACY_LEVEL}
+ *
+ */
+ STAGE_INSTANCE_DELETE(85, TargetType.STAGE_INSTANCE),
+
UNKNOWN(-1, TargetType.UNKNOWN);
private final int key;
diff --git a/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java b/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java
index 33bd84dc8a..114f264a74 100644
--- a/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java
+++ b/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java
@@ -262,6 +262,16 @@ public enum AuditLogKey
*/
CHANNEL_OVERRIDES("permission_overwrites"),
+ // STAGE_INSTANCE
+
+ /**
+ * Change of the {@link net.dv8tion.jda.api.entities.StageInstance#getPrivacyLevel() StageInstance.getPrivacyLevel()} value
+ *
Use with {@link net.dv8tion.jda.api.entities.StageInstance.PrivacyLevel#fromKey(int) StageInstance.PrivacyLevel.fromKey(int)}
+ *
+ * Expected type: Integer
+ */
+ PRIVACY_LEVEL("privacy_level"),
+
// MEMBER
/**
diff --git a/src/main/java/net/dv8tion/jda/api/audit/TargetType.java b/src/main/java/net/dv8tion/jda/api/audit/TargetType.java
index f0694ece72..c6f3cc3c31 100644
--- a/src/main/java/net/dv8tion/jda/api/audit/TargetType.java
+++ b/src/main/java/net/dv8tion/jda/api/audit/TargetType.java
@@ -39,5 +39,6 @@ public enum TargetType
WEBHOOK,
EMOTE,
INTEGRATION,
+ STAGE_INSTANCE,
UNKNOWN
}
diff --git a/src/main/java/net/dv8tion/jda/api/entities/Category.java b/src/main/java/net/dv8tion/jda/api/entities/Category.java
index 8e95072e3d..384ac1c99f 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/Category.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/Category.java
@@ -88,7 +88,7 @@ public interface Category extends GuildChannel
/**
* Creates a new {@link net.dv8tion.jda.api.entities.TextChannel TextChannel} with this Category as parent.
* For this to be successful, the logged in account has to have the
- * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission in the {@link net.dv8tion.jda.api.entities.Guild Guild}.
+ * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission in this Category.
*
*
This will copy all {@link net.dv8tion.jda.api.entities.PermissionOverride PermissionOverrides} of this Category!
* Unless the bot is unable to sync it with this category due to permission escalation.
@@ -125,7 +125,7 @@ public interface Category extends GuildChannel
/**
* Creates a new {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel} with this Category as parent.
* For this to be successful, the logged in account has to have the
- * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission in the {@link net.dv8tion.jda.api.entities.Guild Guild}.
+ * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission in this Category.
*
*
This will copy all {@link net.dv8tion.jda.api.entities.PermissionOverride PermissionOverrides} of this Category!
* Unless the bot is unable to sync it with this category due to permission escalation.
@@ -159,6 +159,43 @@ public interface Category extends GuildChannel
@CheckReturnValue
ChannelAction createVoiceChannel(@Nonnull String name);
+ /**
+ * Creates a new {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} with this Category as parent.
+ * For this to be successful, the logged in account has to have the
+ * {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission in this Category.
+ *
+ * This will copy all {@link net.dv8tion.jda.api.entities.PermissionOverride PermissionOverrides} of this Category!
+ * Unless the bot is unable to sync it with this category due to permission escalation.
+ * See {@link IPermissionHolder#canSync(GuildChannel, GuildChannel)} for details.
+ *
+ *
Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
+ * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
+ *
The channel could not be created due to a permission discrepancy
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_ACCESS MISSING_ACCESS}
+ *
The {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} permission was removed
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS}
+ *
The maximum number of channels were exceeded
+ *
+ *
+ * @param name
+ * The name of the StageChannel to create
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission
+ * @throws IllegalArgumentException
+ * If the provided name is {@code null} or empty or greater than 100 characters in length
+ *
+ * @return A specific {@link ChannelAction ChannelAction}
+ *
This action allows to set fields for the new StageChannel before creating it
+ */
+ @Nonnull
+ @CheckReturnValue
+ ChannelAction createStageChannel(@Nonnull String name);
+
/**
* Modifies the positional order of this Category's nested {@link #getTextChannels() TextChannels} and {@link #getStoreChannels() StoreChannels}.
*
This uses an extension of {@link ChannelOrderAction ChannelOrderAction}
diff --git a/src/main/java/net/dv8tion/jda/api/entities/ChannelType.java b/src/main/java/net/dv8tion/jda/api/entities/ChannelType.java
index 8c097dafe5..465111d734 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/ChannelType.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/ChannelType.java
@@ -47,6 +47,10 @@ public enum ChannelType
* A {@link net.dv8tion.jda.api.entities.StoreChannel StoreChannel}, Guild-Only.
*/
STORE(6, 0, true),
+ /**
+ * A {@link StageChannel StageChannel}, Guild-Only.
+ */
+ STAGE(13, 1, true),
/**
* Unknown Discord channel type. Should never happen and would only possibly happen if Discord implemented a new
* channel type and JDA had yet to implement support for it.
@@ -99,6 +103,42 @@ public boolean isGuild()
return isGuild;
}
+ /**
+ * Whether channels of this type support audio connections.
+ *
+ * @return True, if channels of this type support audio
+ */
+ public boolean isAudio()
+ {
+ switch (this)
+ {
+ case VOICE:
+ case STAGE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Whether channels of this type support message sending.
+ *
+ * @return True, if channels of this type support messages
+ */
+ public boolean isMessage()
+ {
+ switch (this)
+ {
+ //case NEWS: TODO
+ case TEXT:
+ case PRIVATE:
+ case GROUP:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/**
* Static accessor for retrieving a channel type based on its Discord id key.
*
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 796b7ec518..973387a7c9 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java
@@ -57,6 +57,7 @@
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* Represents a Discord {@link net.dv8tion.jda.api.entities.Guild Guild}.
@@ -1525,6 +1526,8 @@ default GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id)
return getTextChannelById(id);
case VOICE:
return getVoiceChannelById(id);
+ case STAGE:
+ return getStageChannelById(id);
case STORE:
return getStoreChannelById(id);
case CATEGORY:
@@ -1533,6 +1536,87 @@ default GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id)
return null;
}
+ /**
+ * Gets a list of all {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} in this Guild that have the same
+ * name as the one provided.
+ *
If there are no {@link net.dv8tion.jda.api.entities.StageChannel StageChannels} with the provided name, then this returns an empty list.
+ *
+ * @param name
+ * The name used to filter the returned {@link net.dv8tion.jda.api.entities.StageChannel StageChannels}.
+ * @param ignoreCase
+ * Determines if the comparison ignores case when comparing. True - case insensitive.
+ *
+ * @return Possibly-empty immutable list of all StageChannel names that match the provided name.
+ */
+ @Nonnull
+ default List getStageChannelsByName(@Nonnull String name, boolean ignoreCase)
+ {
+ return getVoiceChannelsByName(name, ignoreCase)
+ .stream()
+ .filter(StageChannel.class::isInstance)
+ .map(StageChannel.class::cast)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Gets a {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} from this guild that has the same id as the
+ * one provided. This method is similar to {@link net.dv8tion.jda.api.JDA#getStageChannelById(String)}, but it only
+ * checks this specific Guild for a StageChannel.
+ *
If there is no {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} with an id that matches the provided
+ * one, then this returns {@code null}.
+ *
+ * @param id
+ * The id of the {@link net.dv8tion.jda.api.entities.StageChannel StageChannel}.
+ *
+ * @throws java.lang.NumberFormatException
+ * If the provided {@code id} cannot be parsed by {@link Long#parseLong(String)}
+ *
+ * @return Possibly-null {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} with matching id.
+ */
+ @Nullable
+ default StageChannel getStageChannelById(@Nonnull String id)
+ {
+ return getStageChannelById(MiscUtil.parseSnowflake(id));
+ }
+
+ /**
+ * Gets a {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} from this guild that has the same id as the
+ * one provided. This method is similar to {@link net.dv8tion.jda.api.JDA#getStageChannelById(long)}, but it only
+ * checks this specific Guild for a StageChannel.
+ *
If there is no {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} with an id that matches the provided
+ * one, then this returns {@code null}.
+ *
+ * @param id
+ * The id of the {@link net.dv8tion.jda.api.entities.StageChannel StageChannel}.
+ *
+ * @return Possibly-null {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} with matching id.
+ */
+ @Nullable
+ default StageChannel getStageChannelById(long id)
+ {
+ VoiceChannel channel = getVoiceChannelById(id);
+ return channel instanceof StageChannel ? (StageChannel) channel : null;
+ }
+
+ /**
+ * Gets all {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} in this {@link net.dv8tion.jda.api.entities.Guild Guild}.
+ *
The channels returned will be sorted according to their position.
+ *
+ * This copies the backing store into a list. This means every call
+ * creates a new list with O(n) complexity.
+ *
+ * @return An immutable List of {@link net.dv8tion.jda.api.entities.StageChannel StageChannels}.
+ */
+ @Nonnull
+ default List getStageChannels()
+ {
+ return getVoiceChannels()
+ .stream()
+ .filter(StageChannel.class::isInstance)
+ .map(StageChannel.class::cast)
+ .collect(Collectors.toList());
+ }
+
/**
* Gets the {@link net.dv8tion.jda.api.entities.Category Category} from this guild that matches the provided id.
* This method is similar to {@link net.dv8tion.jda.api.JDA#getCategoryById(String)}, but it only checks in this
@@ -1802,6 +1886,8 @@ default List getTextChannelsByName(@Nonnull String name, boolean ig
*
If there is no {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel} with an id that matches the provided
* one, then this returns {@code null}.
*
+ * This may also contain {@link StageChannel StageChannels}!
+ *
* @param id
* The id of the {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel}.
*
@@ -1823,6 +1909,8 @@ default VoiceChannel getVoiceChannelById(@Nonnull String id)
*
If there is no {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel} with an id that matches the provided
* one, then this returns {@code null}.
*
+ *
This may also contain {@link StageChannel StageChannels}!
+ *
* @param id
* The id of the {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel}.
*
@@ -1843,6 +1931,8 @@ default VoiceChannel getVoiceChannelById(long id)
* a local variable or use {@link #getVoiceChannelCache()} and use its more efficient
* versions of handling these values.
*
+ *
This may also contain {@link StageChannel StageChannels}!
+ *
* @return An immutable List of {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels}.
*/
@Nonnull
@@ -1856,6 +1946,8 @@ default List getVoiceChannels()
* name as the one provided.
*
If there are no {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels} with the provided name, then this returns an empty list.
*
+ * This may also contain {@link StageChannel StageChannels}!
+ *
* @param name
* The name used to filter the returned {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels}.
* @param ignoreCase
@@ -1874,6 +1966,8 @@ default List getVoiceChannelsByName(@Nonnull String name, boolean
* all cached {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels} of this Guild.
*
VoiceChannels are sorted according to their position.
*
+ * This may also contain {@link StageChannel StageChannels}!
+ *
* @return {@link net.dv8tion.jda.api.utils.cache.SortedSnowflakeCacheView SortedSnowflakeCacheView}
*/
@Nonnull
@@ -2631,6 +2725,44 @@ default RestAction retrieveBan(@Nonnull User bannedUser)
@Nonnull
AudioManager getAudioManager();
+ /**
+ * Once the currently logged in account is connected to a {@link StageChannel} with an active {@link StageInstance},
+ * this will trigger a {@link GuildVoiceState#getRequestToSpeakTimestamp() Request-to-Speak} (aka raise your hand).
+ *
+ * This will set an internal flag to automatically request to speak once the bot joins a stage channel.
+ *
You can use {@link #cancelRequestToSpeak()} to move back to the audience or cancel your pending request.
+ *
+ *
If the self member has {@link Permission#VOICE_MUTE_OTHERS} this will immediately promote them to speaker.
+ *
+ *
Example:
+ *
{@code
+ * stageChannel.createStageInstance("Talent Show").queue()
+ * guild.requestToSpeak(); // Set request to speak flag
+ * guild.getAudioManager().openAudioConnection(stageChannel); // join the channel
+ * }
+ *
+ * @return {@link Task} representing the request to speak.
+ * Calling {@link Task#get()} can result in deadlocks and should be avoided at all times.
+ *
+ * @see #cancelRequestToSpeak()
+ */
+ @Nonnull
+ Task requestToSpeak();
+
+ /**
+ * Cancels the {@link #requestToSpeak() Request-to-Speak}.
+ *
This can also be used to move back to the audience if you are currently a speaker.
+ *
+ * If there is no request to speak or the member is not currently connected to an active {@link StageInstance}, this does nothing.
+ *
+ * @return {@link Task} representing the request to speak cancellation.
+ * Calling {@link Task#get()} can result in deadlocks and should be avoided at all times.
+ *
+ * @see #requestToSpeak()
+ */
+ @Nonnull
+ Task cancelRequestToSpeak();
+
/**
* Returns the {@link net.dv8tion.jda.api.JDA JDA} instance of this Guild
*
@@ -5038,6 +5170,70 @@ default ChannelAction createVoiceChannel(@Nonnull String name)
@CheckReturnValue
ChannelAction createVoiceChannel(@Nonnull String name, @Nullable Category parent);
+ /**
+ * Creates a new {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} in this Guild.
+ * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission.
+ *
+ * Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
+ * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
+ *
The channel could not be created due to a permission discrepancy
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS}
+ *
The maximum number of channels were exceeded
+ *
+ *
+ * @param name
+ * The name of the StageChannel to create
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission
+ * @throws IllegalArgumentException
+ * If the provided name is {@code null} or empty or greater than 100 characters in length
+ *
+ * @return A specific {@link ChannelAction ChannelAction}
+ *
This action allows to set fields for the new StageChannel before creating it
+ */
+ @Nonnull
+ @CheckReturnValue
+ default ChannelAction createStageChannel(@Nonnull String name)
+ {
+ return createStageChannel(name, null);
+ }
+
+ /**
+ * Creates a new {@link net.dv8tion.jda.api.entities.StageChannel StageChannel} in this Guild.
+ * For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission.
+ *
+ * Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
+ * the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
+ *
The channel could not be created due to a permission discrepancy
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS}
+ *
The maximum number of channels were exceeded
+ *
+ *
+ * @param name
+ * The name of the StageChannel to create
+ * @param parent
+ * The optional parent category for this channel, or null
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the logged in account does not have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL} permission
+ * @throws IllegalArgumentException
+ * If the provided name is {@code null} or empty or greater than 100 characters in length;
+ * or the provided parent is not in the same guild.
+ *
+ * @return A specific {@link ChannelAction ChannelAction}
+ *
This action allows to set fields for the new StageChannel before creating it
+ */
+ @Nonnull
+ @CheckReturnValue
+ ChannelAction createStageChannel(@Nonnull String name, @Nullable Category parent);
+
/**
* Creates a new {@link net.dv8tion.jda.api.entities.Category Category} in this Guild.
* For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission.
diff --git a/src/main/java/net/dv8tion/jda/api/entities/GuildVoiceState.java b/src/main/java/net/dv8tion/jda/api/entities/GuildVoiceState.java
index 069e37f2d4..8004c3ff54 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/GuildVoiceState.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/GuildVoiceState.java
@@ -17,9 +17,12 @@
package net.dv8tion.jda.api.entities;
import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.requests.RestAction;
+import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.time.OffsetDateTime;
/**
* Represents the voice state of a {@link net.dv8tion.jda.api.entities.Member Member} in a
@@ -27,7 +30,7 @@
*
* @see Member#getVoiceState()
*/
-public interface GuildVoiceState
+public interface GuildVoiceState extends ISnowflake
{
/**
* Returns the {@link net.dv8tion.jda.api.JDA JDA} instance of this VoiceState
@@ -83,12 +86,15 @@ public interface GuildVoiceState
/**
* Returns true if this {@link net.dv8tion.jda.api.entities.Member Member} is unable to speak because the
- * channel is actively suppressing audio communication. This occurs only in
+ * channel is actively suppressing audio communication. This occurs in
* {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannels} where the Member either doesn't have
* {@link net.dv8tion.jda.api.Permission#VOICE_SPEAK Permission#VOICE_SPEAK} or if the channel is the
* designated AFK channel.
+ *
This is also used by {@link StageChannel StageChannels} for listeners without speaker approval.
*
* @return True, if this {@link net.dv8tion.jda.api.entities.Member Member's} audio is being suppressed.
+ *
+ * @see #getRequestToSpeakTimestamp()
*/
boolean isSuppressed();
@@ -145,4 +151,62 @@ public interface GuildVoiceState
*/
@Nullable
String getSessionId();
+
+ /**
+ * The time at which the user requested to speak.
+ *
This is used for {@link StageChannel StageChannels} and can only be approved by members with {@link net.dv8tion.jda.api.Permission#VOICE_MUTE_OTHERS Permission.VOICE_MUTE_OTHERS} on the channel.
+ *
+ * @return The request to speak timestamp, or null if this user didn't request to speak
+ */
+ @Nullable
+ OffsetDateTime getRequestToSpeakTimestamp();
+
+ /**
+ * Promote the member to speaker.
+ * This requires a non-null {@link #getRequestToSpeakTimestamp()}.
+ * You can use {@link #inviteSpeaker()} to invite the member to become a speaker if they haven't requested to speak.
+ *
+ *
This does nothing if the member is not connected to a {@link StageChannel}.
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the currently logged in account does not have {@link net.dv8tion.jda.api.Permission#VOICE_MUTE_OTHERS Permission.VOICE_MUTE_OTHERS}
+ * in the associated {@link StageChannel}
+ *
+ * @return {@link RestAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ RestAction approveSpeaker();
+
+ /**
+ * Reject this members {@link #getRequestToSpeakTimestamp() request to speak}.
+ * This requires a non-null {@link #getRequestToSpeakTimestamp()}.
+ * The member will have to request to speak again.
+ *
+ *
This does nothing if the member is not connected to a {@link StageChannel}.
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the currently logged in account does not have {@link net.dv8tion.jda.api.Permission#VOICE_MUTE_OTHERS Permission.VOICE_MUTE_OTHERS}
+ * in the associated {@link StageChannel}
+ *
+ * @return {@link RestAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ RestAction declineSpeaker();
+
+ /**
+ * Invite this member to become a speaker.
+ *
+ * This does nothing if the member is not connected to a {@link StageChannel}.
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the currently logged in account does not have {@link net.dv8tion.jda.api.Permission#VOICE_MUTE_OTHERS Permission.VOICE_MUTE_OTHERS}
+ * in the associated {@link StageChannel}
+ *
+ * @return {@link RestAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ RestAction inviteSpeaker();
}
diff --git a/src/main/java/net/dv8tion/jda/api/entities/IPermissionHolder.java b/src/main/java/net/dv8tion/jda/api/entities/IPermissionHolder.java
index 3a52ffa2f4..1dcba93e77 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/IPermissionHolder.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/IPermissionHolder.java
@@ -173,7 +173,7 @@ public interface IPermissionHolder extends ISnowflake
default boolean hasAccess(@Nonnull GuildChannel channel)
{
Checks.notNull(channel, "Channel");
- return channel.getType() == ChannelType.VOICE
+ return channel.getType() == ChannelType.VOICE || channel.getType() == ChannelType.STAGE
? hasPermission(channel, Permission.VOICE_CONNECT, Permission.VIEW_CHANNEL)
: hasPermission(channel, Permission.VIEW_CHANNEL);
}
diff --git a/src/main/java/net/dv8tion/jda/api/entities/StageChannel.java b/src/main/java/net/dv8tion/jda/api/entities/StageChannel.java
new file mode 100644
index 0000000000..0364e88aa1
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/entities/StageChannel.java
@@ -0,0 +1,96 @@
+/*
+ * 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.entities;
+
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.requests.restaction.StageInstanceAction;
+import net.dv8tion.jda.internal.utils.Checks;
+
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Represents a Stage Channel.
+ *
+ * This is a more advanced version of a {@link VoiceChannel}
+ * that can be used to host events with speakers and listeners.
+ */
+public interface StageChannel extends VoiceChannel
+{
+ /**
+ * {@link StageInstance} attached to this stage channel.
+ *
+ *
This indicates whether a stage channel is currently "live".
+ *
+ * @return The {@link StageInstance} or {@code null} if this stage is not live
+ */
+ @Nullable
+ StageInstance getStageInstance();
+
+ /**
+ * Create a new {@link StageInstance} for this stage channel.
+ *
+ *
Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#STAGE_ALREADY_OPEN STAGE_ALREADY_OPEN}
+ *
If there already is an active {@link StageInstance} for this channel
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL}
+ *
If the channel was deleted
+ *
+ *
+ * @param topic
+ * The topic of this stage instance, must be 1-120 characters long
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the self member is not a stage moderator. (See {@link #isModerator(Member)})
+ * @throws IllegalArgumentException
+ * If the topic is null, empty, or longer than 120 characters
+ *
+ * @return {@link StageInstanceAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ StageInstanceAction createStageInstance(@Nonnull String topic);
+
+ /**
+ * Whether this member is considered a moderator for this stage channel.
+ *
Moderators can modify the {@link #getStageInstance() Stage Instance} and promote speakers.
+ * To promote a speaker you can use {@link GuildVoiceState#inviteSpeaker()} or {@link GuildVoiceState#approveSpeaker()} if they have already raised their hand (indicated by {@link GuildVoiceState#getRequestToSpeakTimestamp()}).
+ * A stage moderator can move between speaker and audience without raising their hand. This can be done with {@link Guild#requestToSpeak()} and {@link Guild#cancelRequestToSpeak()} respectively.
+ *
+ * A member is considered a stage moderator if they have these permissions in the stage channel:
+ *
+ * - {@link Permission#MANAGE_CHANNEL}
+ * - {@link Permission#VOICE_MUTE_OTHERS}
+ * - {@link Permission#VOICE_MOVE_OTHERS}
+ *
+ *
+ * @param member
+ * The member to check
+ *
+ * @throws IllegalArgumentException
+ * If the provided member is null or not from this guild
+ *
+ * @return True, if the provided member is a stage moderator
+ */
+ default boolean isModerator(@Nonnull Member member)
+ {
+ Checks.notNull(member, "Member");
+ return member.hasPermission(this, Permission.MANAGE_CHANNEL, Permission.VOICE_MUTE_OTHERS, Permission.VOICE_MOVE_OTHERS);
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/entities/StageInstance.java b/src/main/java/net/dv8tion/jda/api/entities/StageInstance.java
new file mode 100644
index 0000000000..f1797aca11
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/entities/StageInstance.java
@@ -0,0 +1,234 @@
+/*
+ * 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.entities;
+
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.managers.StageInstanceManager;
+import net.dv8tion.jda.api.requests.RestAction;
+
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * A Stage Instance holds information about a live stage.
+ *
+ * This instance indicates an active stage channel with speakers, usually to host events such as presentations or meetings.
+ */
+public interface StageInstance extends ISnowflake
+{
+ /**
+ * The {@link Guild} this stage instance is in
+ *
+ * @return The {@link Guild}
+ */
+ @Nonnull
+ Guild getGuild();
+
+ /**
+ * The {@link StageChannel} for this stage instance
+ *
+ * @return The {@link StageChannel}
+ */
+ @Nonnull
+ StageChannel getChannel();
+
+ /**
+ * The topic of this stage instance
+ *
+ * @return The topic
+ */
+ @Nonnull
+ String getTopic();
+
+ /**
+ * The {@link PrivacyLevel} of this stage instance
+ *
+ * @return The {@link PrivacyLevel}
+ */
+ @Nonnull
+ PrivacyLevel getPrivacyLevel();
+
+ /**
+ * Whether this stage instance can be found in stage discovery.
+ *
+ * @return True if this is a public stage that can be found in stage discovery
+ */
+ boolean isDiscoverable();
+
+ /**
+ * All current speakers of this stage instance.
+ *
+ *
A member is considered a speaker when they are currently connected to the stage channel
+ * and their voice state is not {@link GuildVoiceState#isSuppressed() suppressed}.
+ * When a member is not a speaker, they are part of the {@link #getAudience() audience}.
+ *
+ *
Only {@link StageChannel#isModerator(Member) stage moderators} can promote or invite speakers.
+ * A stage moderator can move between speaker and audience at any time.
+ *
+ * @return {@link List} of {@link Member Members} which can speak in this stage instance
+ */
+ @Nonnull
+ default List getSpeakers()
+ {
+ return Collections.unmodifiableList(getChannel().getMembers()
+ .stream()
+ .filter(member -> !member.getVoiceState().isSuppressed()) // voice states should not be null since getMembers() checks only for connected members in the channel
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * All current audience members of this stage instance.
+ *
+ * A member is considered part of the audience when they are currently connected to the stage channel
+ * and their voice state is {@link GuildVoiceState#isSuppressed() suppressed}.
+ * When a member is not part of the audience, they are considered a {@link #getSpeakers() speaker}.
+ *
+ *
Only {@link StageChannel#isModerator(Member) stage moderators} can promote or invite speakers.
+ * A stage moderator can move between speaker and audience at any time.
+ *
+ * @return {@link List} of {@link Member Members} which cannot speak in this stage instance
+ */
+ @Nonnull
+ default List getAudience()
+ {
+ return Collections.unmodifiableList(getChannel().getMembers()
+ .stream()
+ .filter(member -> member.getVoiceState().isSuppressed()) // voice states should not be null since getMembers() checks only for connected members in the channel
+ .collect(Collectors.toList()));
+ }
+
+ /**
+ * Deletes this stage instance
+ *
+ * Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} include:
+ *
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_STAGE_INSTANCE UNKNOWN_STAGE_INSTANCE}
+ *
If this stage instance is already deleted
+ * - {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_CHANNEL UNKNOWN_CHANNEL}
+ *
If the channel was deleted
+ *
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the self member is not a {@link StageChannel#isModerator(Member) stage moderator}
+ *
+ * @return {@link RestAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ RestAction delete();
+
+ /**
+ * Sends a {@link GuildVoiceState#getRequestToSpeakTimestamp() request-to-speak} indicator to the stage instance moderators.
+ * If the self member has {@link Permission#VOICE_MUTE_OTHERS} this will immediately promote them to speaker.
+ *
+ * @throws IllegalStateException
+ * If the self member is not currently connected to the channel of this stage instance
+ *
+ * @return {@link RestAction}
+ *
+ * @see #cancelRequestToSpeak()
+ */
+ @Nonnull
+ @CheckReturnValue
+ RestAction requestToSpeak();
+
+ /**
+ * Cancels the {@link #requestToSpeak() Request-to-Speak}.
+ *
This can also be used to move back to the audience if you are currently a speaker.
+ *
+ * If there is no request to speak or the member is not currently connected to an active {@link StageInstance}, this does nothing.
+ *
+ * @throws IllegalStateException
+ * If the self member is not currently connected to the channel of this stage instance
+ *
+ * @return {@link RestAction}
+ *
+ * @see #requestToSpeak()
+ */
+ @Nonnull
+ @CheckReturnValue
+ RestAction cancelRequestToSpeak();
+
+ /**
+ * The {@link StageInstanceManager} used to update this stage instance.
+ * This can be used to update multiple fields such as topic and privacy level in one request
+ *
+ *
If this stage instance is already deleted, this will fail with {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_STAGE_INSTANCE ErrorResponse.UNKNOWN_STAGE_INSTANCE}.
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the self member is not a {@link StageChannel#isModerator(Member) stage moderator}
+ *
+ * @return The {@link StageInstanceManager}
+ */
+ @Nonnull
+ @CheckReturnValue
+ StageInstanceManager getManager();
+
+ /**
+ * The privacy level for a stage instance.
+ *
+ *
This indicates from where people can join the stage instance.
+ */
+ enum PrivacyLevel
+ {
+ /** Placeholder for future privacy levels, indicates that this version of JDA does not support this privacy level yet */
+ UNKNOWN(-1),
+ /** This stage instance can be accessed by lurkers, meaning users that are not active members of the guild */
+ PUBLIC(1),
+ /** This stage instance can only be accessed by guild members */
+ GUILD_ONLY(2);
+
+ private final int key;
+
+ PrivacyLevel(int key)
+ {
+ this.key = key;
+ }
+
+ /**
+ * The raw API key for this privacy level
+ *
+ * @return The raw API value or {@code -1} if this is {@link #UNKNOWN}
+ */
+ public int getKey()
+ {
+ return key;
+ }
+
+ /**
+ * Converts the raw API key into the respective enum value
+ *
+ * @param key
+ * The API key
+ *
+ * @return The enum value or {@link #UNKNOWN}
+ */
+ @Nonnull
+ public static PrivacyLevel fromKey(int key)
+ {
+ for (PrivacyLevel level : values())
+ {
+ if (level.key == key)
+ return level;
+ }
+ return UNKNOWN;
+ }
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/entities/VoiceChannel.java b/src/main/java/net/dv8tion/jda/api/entities/VoiceChannel.java
index 9d5fb9be4b..b997e833fc 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/VoiceChannel.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/VoiceChannel.java
@@ -47,6 +47,8 @@ public interface VoiceChannel extends GuildChannel
* {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel} at once.
*
0 - No limit
*
+ *
This is meaningless for {@link StageChannel StageChannels}.
+ *
* @return The maximum amount of members allowed in this channel at once.
*/
int getUserLimit();
diff --git a/src/main/java/net/dv8tion/jda/api/events/guild/invite/GenericGuildInviteEvent.java b/src/main/java/net/dv8tion/jda/api/events/guild/invite/GenericGuildInviteEvent.java
index 4db3af934e..df42bb9c0e 100644
--- a/src/main/java/net/dv8tion/jda/api/events/guild/invite/GenericGuildInviteEvent.java
+++ b/src/main/java/net/dv8tion/jda/api/events/guild/invite/GenericGuildInviteEvent.java
@@ -114,7 +114,7 @@ public TextChannel getTextChannel()
* The {@link VoiceChannel} this invite points to.
*
* @throws IllegalStateException
- * If this did not happen in a channel of type {@link ChannelType#VOICE ChannelType.VOICE}
+ * If this did not happen in a voice channel or stage channel
*
* @return {@link VoiceChannel}
*
@@ -124,11 +124,30 @@ public TextChannel getTextChannel()
@Nonnull
public VoiceChannel getVoiceChannel()
{
- if (getChannelType() != ChannelType.VOICE)
- throw new IllegalStateException("The channel is not of type VOICE");
+ if (!(channel instanceof VoiceChannel))
+ throw new IllegalStateException("The channel is not of type VOICE or STAGE");
return (VoiceChannel) getChannel();
}
+ /**
+ * The {@link StageChannel} this invite points to.
+ *
+ * @throws IllegalStateException
+ * If this did not happen in a channel of type {@link ChannelType#STAGE ChannelType.STAGE}
+ *
+ * @return {@link StageChannel}
+ *
+ * @see #getChannel()
+ * @see #getChannelType()
+ */
+ @Nonnull
+ public StageChannel getStageChannel()
+ {
+ if (getChannelType() != ChannelType.STAGE)
+ throw new IllegalStateException("The channel is not of type STAGE");
+ return (StageChannel) getChannel();
+ }
+
/**
* The {@link StoreChannel} this invite points to.
*
diff --git a/src/main/java/net/dv8tion/jda/api/events/guild/voice/GuildVoiceRequestToSpeakEvent.java b/src/main/java/net/dv8tion/jda/api/events/guild/voice/GuildVoiceRequestToSpeakEvent.java
new file mode 100644
index 0000000000..5263939464
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/guild/voice/GuildVoiceRequestToSpeakEvent.java
@@ -0,0 +1,121 @@
+/*
+ * 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.voice;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.GuildVoiceState;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.StageChannel;
+import net.dv8tion.jda.api.requests.RestAction;
+
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.time.OffsetDateTime;
+
+/**
+ * Indicates that a guild member has updated their {@link GuildVoiceState#getRequestToSpeakTimestamp() Request-to-Speak}.
+ *
+ *
If {@link #getNewTime()} is non-null, this means the member has raised their hand and wants to speak.
+ * You can use {@link #approveSpeaker()} or {@link #declineSpeaker()} to handle this request if you have {@link net.dv8tion.jda.api.Permission#VOICE_MUTE_OTHERS Permission.VOICE_MUTE_OTHERS}.
+ *
+ *
Requirements
+ *
+ * These events require the {@link net.dv8tion.jda.api.utils.cache.CacheFlag#VOICE_STATE VOICE_STATE} CacheFlag to be enabled, which requires
+ * the {@link net.dv8tion.jda.api.requests.GatewayIntent#GUILD_VOICE_STATES GUILD_VOICE_STATES} intent.
+ *
+ *
{@link net.dv8tion.jda.api.JDABuilder#createLight(String) createLight(String)} disables that CacheFlag by default!
+ *
+ *
Additionally, these events require 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 specific events like these we
+ * need to have the old member cached to compare against.
+ */
+public class GuildVoiceRequestToSpeakEvent extends GenericGuildVoiceEvent
+{
+ private final OffsetDateTime oldTime, newTime;
+
+ public GuildVoiceRequestToSpeakEvent(@Nonnull JDA api, long responseNumber, @Nonnull Member member,
+ @Nullable OffsetDateTime oldTime, @Nullable OffsetDateTime newTime)
+ {
+ super(api, responseNumber, member);
+ this.oldTime = oldTime;
+ this.newTime = newTime;
+ }
+
+ /**
+ * The old {@link GuildVoiceState#getRequestToSpeakTimestamp()}
+ *
+ * @return The old timestamp, or null if this member did not request to speak before
+ */
+ @Nullable
+ public OffsetDateTime getOldTime()
+ {
+ return oldTime;
+ }
+
+ /**
+ * The new {@link GuildVoiceState#getRequestToSpeakTimestamp()}
+ *
+ * @return The new timestamp, or null if the request to speak was declined or cancelled
+ */
+ @Nullable
+ public OffsetDateTime getNewTime()
+ {
+ return newTime;
+ }
+
+ /**
+ * Promote the member to speaker.
+ *
This requires a non-null {@link #getNewTime()}.
+ * You can use {@link GuildVoiceState#inviteSpeaker()} to invite the member to become a speaker if they haven't requested to speak.
+ *
+ *
This does nothing if the member is not connected to a {@link StageChannel}.
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the currently logged in account does not have {@link net.dv8tion.jda.api.Permission#VOICE_MUTE_OTHERS Permission.VOICE_MUTE_OTHERS}
+ * in the associated {@link StageChannel}
+ *
+ * @return {@link RestAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ public RestAction approveSpeaker()
+ {
+ return getVoiceState().approveSpeaker();
+ }
+
+ /**
+ * Reject this members {@link GuildVoiceState#getRequestToSpeakTimestamp() request to speak}.
+ * This requires a non-null {@link #getNewTime()}.
+ * The member will have to request to speak again.
+ *
+ *
This does nothing if the member is not connected to a {@link StageChannel}.
+ *
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ * If the currently logged in account does not have {@link net.dv8tion.jda.api.Permission#VOICE_MUTE_OTHERS Permission.VOICE_MUTE_OTHERS}
+ * in the associated {@link StageChannel}
+ *
+ * @return {@link RestAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ public RestAction declineSpeaker()
+ {
+ return getVoiceState().declineSpeaker();
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/events/guild/voice/GuildVoiceUpdateEvent.java b/src/main/java/net/dv8tion/jda/api/events/guild/voice/GuildVoiceUpdateEvent.java
index 90945a60cf..13cac6b0dc 100644
--- a/src/main/java/net/dv8tion/jda/api/events/guild/voice/GuildVoiceUpdateEvent.java
+++ b/src/main/java/net/dv8tion/jda/api/events/guild/voice/GuildVoiceUpdateEvent.java
@@ -16,10 +16,12 @@
package net.dv8tion.jda.api.events.guild.voice;
+import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.events.UpdateEvent;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -49,6 +51,22 @@ public interface GuildVoiceUpdateEvent extends UpdateEvent
{
String IDENTIFIER = "voice-channel";
+ /**
+ * The affected {@link net.dv8tion.jda.api.entities.Member Member}
+ *
+ * @return The affected Member
+ */
+ @Nonnull
+ Member getMember();
+
+ /**
+ * The {@link net.dv8tion.jda.api.entities.Guild Guild}
+ *
+ * @return The Guild
+ */
+ @Nonnull
+ Guild getGuild();
+
/**
* The {@link net.dv8tion.jda.api.entities.VoiceChannel VoiceChannel} that the {@link net.dv8tion.jda.api.entities.Member Member} is moved from
*
diff --git a/src/main/java/net/dv8tion/jda/api/events/stage/GenericStageInstanceEvent.java b/src/main/java/net/dv8tion/jda/api/events/stage/GenericStageInstanceEvent.java
new file mode 100644
index 0000000000..bad85e368b
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/stage/GenericStageInstanceEvent.java
@@ -0,0 +1,63 @@
+/*
+ * 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.stage;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.StageChannel;
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.events.guild.GenericGuildEvent;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Indicates that a {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} was created/deleted/changed.
+ *
Every StageInstanceEvent is derived from this event and can be casted.
+ *
+ * Can be used to detect any StageInstanceEvent.
+ */
+public abstract class GenericStageInstanceEvent extends GenericGuildEvent
+{
+ protected final StageInstance instance;
+
+ public GenericStageInstanceEvent(@Nonnull JDA api, long responseNumber, @Nonnull StageInstance stageInstance)
+ {
+ super(api, responseNumber, stageInstance.getGuild());
+ this.instance = stageInstance;
+ }
+
+ /**
+ * The affected {@link StageInstance}
+ *
+ * @return The {@link StageInstance}
+ */
+ @Nonnull
+ public StageInstance getInstance()
+ {
+ return instance;
+ }
+
+ /**
+ * The {@link StageChannel} this instance belongs to
+ *
+ * @return The StageChannel
+ */
+ @Nonnull
+ public StageChannel getChannel()
+ {
+ return instance.getChannel();
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/events/stage/StageInstanceCreateEvent.java b/src/main/java/net/dv8tion/jda/api/events/stage/StageInstanceCreateEvent.java
new file mode 100644
index 0000000000..f4192f8e2b
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/stage/StageInstanceCreateEvent.java
@@ -0,0 +1,35 @@
+/*
+ * 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.stage;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.StageInstance;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Indicates that a {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} was created.
+ *
+ *
Can be used to retrieve the created StageInstance and its Guild.
+ */
+public class StageInstanceCreateEvent extends GenericStageInstanceEvent
+{
+ public StageInstanceCreateEvent(@Nonnull JDA api, long responseNumber, @Nonnull StageInstance stageInstance)
+ {
+ super(api, responseNumber, stageInstance);
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/events/stage/StageInstanceDeleteEvent.java b/src/main/java/net/dv8tion/jda/api/events/stage/StageInstanceDeleteEvent.java
new file mode 100644
index 0000000000..ca80135f37
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/stage/StageInstanceDeleteEvent.java
@@ -0,0 +1,35 @@
+/*
+ * 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.stage;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.StageInstance;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Indicates that a {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} was deleted.
+ *
+ *
Can be used to retrieve the deleted StageInstance and its Guild.
+ */
+public class StageInstanceDeleteEvent extends GenericStageInstanceEvent
+{
+ public StageInstanceDeleteEvent(@Nonnull JDA api, long responseNumber, @Nonnull StageInstance stageInstance)
+ {
+ super(api, responseNumber, stageInstance);
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/events/stage/update/GenericStageInstanceUpdateEvent.java b/src/main/java/net/dv8tion/jda/api/events/stage/update/GenericStageInstanceUpdateEvent.java
new file mode 100644
index 0000000000..62be4e64e2
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/stage/update/GenericStageInstanceUpdateEvent.java
@@ -0,0 +1,74 @@
+/*
+ * 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.stage.update;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.events.UpdateEvent;
+import net.dv8tion.jda.api.events.stage.GenericStageInstanceEvent;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Indicates that a {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} was updated.
+ *
Every StageInstanceUpdateEvent is derived from this event and can be casted.
+ *
+ *
Can be used to detect any StageInstanceUpdateEvent.
+ */
+public abstract class GenericStageInstanceUpdateEvent extends GenericStageInstanceEvent implements UpdateEvent
+{
+ protected final T previous;
+ protected final T next;
+ protected final String identifier;
+
+ public GenericStageInstanceUpdateEvent(@Nonnull JDA api, long responseNumber, @Nonnull StageInstance stageInstance, T previous, T next, String identifier)
+ {
+ super(api, responseNumber, stageInstance);
+ this.previous = previous;
+ this.next = next;
+ this.identifier = identifier;
+ }
+
+ @Nonnull
+ @Override
+ public String getPropertyIdentifier()
+ {
+ return identifier;
+ }
+
+ @Nonnull
+ @Override
+ public StageInstance getEntity()
+ {
+ return getInstance();
+ }
+
+ @Nullable
+ @Override
+ public T getOldValue()
+ {
+ return previous;
+ }
+
+ @Nullable
+ @Override
+ public T getNewValue()
+ {
+ return next;
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/events/stage/update/StageInstanceUpdatePrivacyLevelEvent.java b/src/main/java/net/dv8tion/jda/api/events/stage/update/StageInstanceUpdatePrivacyLevelEvent.java
new file mode 100644
index 0000000000..2a00e860ef
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/stage/update/StageInstanceUpdatePrivacyLevelEvent.java
@@ -0,0 +1,54 @@
+/*
+ * 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.stage.update;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.StageInstance;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Indicates that a {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} updated its {@link net.dv8tion.jda.api.entities.StageInstance.PrivacyLevel PrivacyLevel}.
+ *
+ * Can be used to retrieve the privacy level.
+ *
+ *
Identifier: {@code privacy_level}
+ */
+@SuppressWarnings("ConstantConditions")
+public class StageInstanceUpdatePrivacyLevelEvent extends GenericStageInstanceUpdateEvent
+{
+ public static final String IDENTIFIER = "privacy_level";
+
+ public StageInstanceUpdatePrivacyLevelEvent(@Nonnull JDA api, long responseNumber, @Nonnull StageInstance stageInstance, @Nonnull StageInstance.PrivacyLevel previous)
+ {
+ super(api, responseNumber, stageInstance, previous, stageInstance.getPrivacyLevel(), IDENTIFIER);
+ }
+
+ @Nonnull
+ @Override
+ public StageInstance.PrivacyLevel getOldValue()
+ {
+ return super.getOldValue();
+ }
+
+ @Nonnull
+ @Override
+ public StageInstance.PrivacyLevel getNewValue()
+ {
+ return super.getNewValue();
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/events/stage/update/StageInstanceUpdateTopicEvent.java b/src/main/java/net/dv8tion/jda/api/events/stage/update/StageInstanceUpdateTopicEvent.java
new file mode 100644
index 0000000000..0be4d8d130
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/stage/update/StageInstanceUpdateTopicEvent.java
@@ -0,0 +1,54 @@
+/*
+ * 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.stage.update;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.StageInstance;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Indicates that a {@link net.dv8tion.jda.api.entities.StageInstance StageInstance} updated its {@code topic}.
+ *
+ * Can be used to retrieve the topic.
+ *
+ *
Identifier: {@code topic}
+ */
+@SuppressWarnings("ConstantConditions")
+public class StageInstanceUpdateTopicEvent extends GenericStageInstanceUpdateEvent
+{
+ public static final String IDENTIFIER = "topic";
+
+ public StageInstanceUpdateTopicEvent(@Nonnull JDA api, long responseNumber, @Nonnull StageInstance stageInstance, String previous)
+ {
+ super(api, responseNumber, stageInstance, previous, stageInstance.getTopic(), IDENTIFIER);
+ }
+
+ @Nonnull
+ @Override
+ public String getOldValue()
+ {
+ return super.getOldValue();
+ }
+
+ @Nonnull
+ @Override
+ public String getNewValue()
+ {
+ return super.getNewValue();
+ }
+}
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 3e59676691..b9bed943ac 100644
--- a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java
+++ b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java
@@ -85,6 +85,12 @@
import net.dv8tion.jda.api.events.role.RoleDeleteEvent;
import net.dv8tion.jda.api.events.role.update.*;
import net.dv8tion.jda.api.events.self.*;
+import net.dv8tion.jda.api.events.stage.GenericStageInstanceEvent;
+import net.dv8tion.jda.api.events.stage.StageInstanceCreateEvent;
+import net.dv8tion.jda.api.events.stage.StageInstanceDeleteEvent;
+import net.dv8tion.jda.api.events.stage.update.GenericStageInstanceUpdateEvent;
+import net.dv8tion.jda.api.events.stage.update.StageInstanceUpdatePrivacyLevelEvent;
+import net.dv8tion.jda.api.events.stage.update.StageInstanceUpdateTopicEvent;
import net.dv8tion.jda.api.events.user.GenericUserEvent;
import net.dv8tion.jda.api.events.user.UserActivityEndEvent;
import net.dv8tion.jda.api.events.user.UserActivityStartEvent;
@@ -295,6 +301,12 @@ public void onPrivateChannelCreate(@Nonnull PrivateChannelCreateEvent event) {}
@DeprecatedSince("4.3.0")
public void onPrivateChannelDelete(@Nonnull PrivateChannelDeleteEvent event) {}
+ //StageInstance Event
+ public void onStageInstanceDelete(@Nonnull StageInstanceDeleteEvent event) {}
+ public void onStageInstanceUpdateTopic(@Nonnull StageInstanceUpdateTopicEvent event) {}
+ public void onStageInstanceUpdatePrivacyLevel(@Nonnull StageInstanceUpdatePrivacyLevelEvent event) {}
+ public void onStageInstanceCreate(@Nonnull StageInstanceCreateEvent event) {}
+
//Guild Events
public void onGuildReady(@Nonnull GuildReadyEvent event) {}
public void onGuildTimeout(@Nonnull GuildTimeoutEvent event) {}
@@ -367,6 +379,7 @@ public void onGuildVoiceSelfMute(@Nonnull GuildVoiceSelfMuteEvent event) {}
public void onGuildVoiceSelfDeafen(@Nonnull GuildVoiceSelfDeafenEvent event) {}
public void onGuildVoiceSuppress(@Nonnull GuildVoiceSuppressEvent event) {}
public void onGuildVoiceStream(@Nonnull GuildVoiceStreamEvent event) {}
+ public void onGuildVoiceRequestToSpeak(@Nonnull GuildVoiceRequestToSpeakEvent event) {}
//Role events
public void onRoleCreate(@Nonnull RoleCreateEvent event) {}
@@ -411,6 +424,8 @@ public void onGenericVoiceChannel(@Nonnull GenericVoiceChannelEvent event) {}
public void onGenericVoiceChannelUpdate(@Nonnull GenericVoiceChannelUpdateEvent event) {}
public void onGenericCategory(@Nonnull GenericCategoryEvent event) {}
public void onGenericCategoryUpdate(@Nonnull GenericCategoryUpdateEvent event) {}
+ public void onGenericStageInstance(@Nonnull GenericStageInstanceEvent event) {}
+ public void onGenericStageInstanceUpdate(@Nonnull GenericStageInstanceUpdateEvent event) {}
public void onGenericGuild(@Nonnull GenericGuildEvent event) {}
public void onGenericGuildUpdate(@Nonnull GenericGuildUpdateEvent event) {}
public void onGenericGuildInvite(@Nonnull GenericGuildInviteEvent event) {}
diff --git a/src/main/java/net/dv8tion/jda/api/managers/ChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/ChannelManager.java
index 30a9c4a7af..3c5cd25a73 100644
--- a/src/main/java/net/dv8tion/jda/api/managers/ChannelManager.java
+++ b/src/main/java/net/dv8tion/jda/api/managers/ChannelManager.java
@@ -370,13 +370,14 @@ default ChannelManager sync()
ChannelManager setPosition(int position);
/**
- * Sets the topic of the selected {@link net.dv8tion.jda.api.entities.TextChannel TextChannel}.
+ * Sets the topic of the selected
+ * {@link net.dv8tion.jda.api.entities.TextChannel TextChannel} or {@link StageChannel StageChannel}.
*
* A channel topic must not be more than {@code 1024} characters long!
*
This is only available to {@link net.dv8tion.jda.api.entities.TextChannel TextChannels}
*
* @param topic
- * The new topic for the selected {@link net.dv8tion.jda.api.entities.TextChannel TextChannel},
+ * The new topic for the selected channel,
* {@code null} or empty String to reset
*
* @throws UnsupportedOperationException
diff --git a/src/main/java/net/dv8tion/jda/api/managers/StageInstanceManager.java b/src/main/java/net/dv8tion/jda/api/managers/StageInstanceManager.java
new file mode 100644
index 0000000000..99be2e1a30
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/managers/StageInstanceManager.java
@@ -0,0 +1,126 @@
+/*
+ * 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.managers;
+
+import net.dv8tion.jda.api.entities.StageInstance;
+
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Manager providing functionality to update one or more fields for a {@link net.dv8tion.jda.api.entities.StageInstance StageInstance}.
+ *
+ *
Example
+ *
{@code
+ * manager.setTopic("LMAO JOIN FOR FREE NITRO")
+ * .setPrivacyLevel(PrivacyLevel.PUBLIC)
+ * .queue();
+ * manager.reset(ChannelManager.TOPIC | ChannelManager.PRIVACY_LEVEL)
+ * .setTopic("Talent Show | WINNER GETS FREE NITRO")
+ * .setPrivacyLevel(PrivacyLevel.GUILD_ONLY)
+ * .queue();
+ * }
+ *
+ * @see net.dv8tion.jda.api.entities.StageInstance#getManager()
+ */
+public interface StageInstanceManager extends Manager
+{
+ /** Used to reset the topic field */
+ long TOPIC = 1 << 0;
+ /** Used to reset the privacy level field */
+ long PRIVACY_LEVEL = 1 << 1;
+
+ /**
+ * Resets the fields specified by the provided bit-flag pattern.
+ * You can specify a combination by using a bitwise OR concat of the flag constants.
+ *
Example: {@code manager.reset(ChannelManager.TOPIC | ChannelManager.PRIVACY_LEVEL);}
+ *
+ * Flag Constants:
+ *
+ * - {@link #TOPIC}
+ * - {@link #PRIVACY_LEVEL}
+ *
+ *
+ * @param fields
+ * Integer value containing the flags to reset.
+ *
+ * @return StageInstanceManager for chaining convenience
+ */
+ @Nonnull
+ @Override
+ StageInstanceManager reset(long fields);
+
+ /**
+ * Resets the fields specified by the provided bit-flag patterns.
+ *
Example: {@code manager.reset(ChannelManager.TOPIC, ChannelManager.PRIVACY_LEVEL);}
+ *
+ * Flag Constants:
+ *
+ * - {@link #TOPIC}
+ * - {@link #PRIVACY_LEVEL}
+ *
+ *
+ * @param fields
+ * Integer values containing the flags to reset.
+ *
+ * @return StageInstanceManager for chaining convenience
+ */
+ @Nonnull
+ @Override
+ StageInstanceManager reset(long... fields);
+
+ /**
+ * The associated {@link StageInstance}
+ *
+ * @return The {@link StageInstance}
+ */
+ @Nonnull
+ StageInstance getStageInstance();
+
+ /**
+ * Sets the topic for this stage instance.
+ *
This shows up in stage discovery and in the stage view.
+ *
+ * @param topic
+ * The topic or null to reset, must be 1-120 characters long
+ *
+ * @throws IllegalArgumentException
+ * If the topic is longer than 120 characters
+ *
+ * @return StageInstanceManager for chaining convenience
+ */
+ @Nonnull
+ @CheckReturnValue
+ StageInstanceManager setTopic(@Nullable String topic);
+
+ /**
+ * Sets the {@link net.dv8tion.jda.api.entities.StageInstance.PrivacyLevel PrivacyLevel} for this stage instance.
+ *
This indicates whether guild lurkers are allowed to join the stage instance or only guild members.
+ *
+ * @param level
+ * The privacy level
+ *
+ * @throws IllegalArgumentException
+ * If the privacy level is null or {@link net.dv8tion.jda.api.entities.StageInstance.PrivacyLevel#UNKNOWN UNKNOWN}
+ *
+ * @return StageInstanceManager for chaining convenience
+ */
+ @Nonnull
+ @CheckReturnValue
+ StageInstanceManager setPrivacyLevel(@Nonnull StageInstance.PrivacyLevel level);
+}
diff --git a/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java b/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java
index c772fc86a8..d7ae49f29b 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/ErrorResponse.java
@@ -64,6 +64,7 @@ public enum ErrorResponse
UNKNOWN_INTERACTION( 10062, "Unknown Interaction"),
UNKNOWN_COMMAND( 10063, "Unknown application command"),
UNKNOWN_COMMAND_PERMISSIONS( 10066, "Unknown application command permissions"),
+ UNKNOWN_STAGE_INSTANCE( 10067, "Unknown Stage Instance"),
BOTS_NOT_ALLOWED( 20001, "Bots cannot use this endpoint"),
ONLY_BOTS_ALLOWED( 20002, "Only bots can use this endpoint"),
MAX_GUILDS( 30001, "Maximum number of Guilds reached (100)"),
@@ -110,6 +111,7 @@ public enum ErrorResponse
MFA_NOT_ENABLED( 60003, "MFA auth required but not enabled"),
REACTION_BLOCKED( 90001, "Reaction Blocked"),
RESOURCES_OVERLOADED( 130000, "Resource overloaded"),
+ STAGE_ALREADY_OPEN( 150006, "The Stage is already open"),
SERVER_ERROR( 0, "Discord encountered an internal server error! Not good!");
diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/GuildAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/GuildAction.java
index a8b4d4cc1f..4b1f14880b 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/restaction/GuildAction.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/GuildAction.java
@@ -546,9 +546,12 @@ class ChannelData implements SerializableData
public ChannelData(ChannelType type, String name)
{
Checks.notBlank(name, "Name");
- Checks.check(type == ChannelType.TEXT || type == ChannelType.VOICE, "Can only create channels of type TEXT or VOICE in GuildAction!");
- Checks.check(name.length() >= 2 && name.length() <= 100, "Channel name has to be between 2-100 characters long!");
- Checks.check(type == ChannelType.VOICE || name.matches("[a-zA-Z0-9-_]+"), "Channels of type TEXT must have a name in alphanumeric with underscores!");
+ Checks.check(type == ChannelType.TEXT || type == ChannelType.VOICE || type == ChannelType.STAGE,
+ "Can only create channels of type TEXT, STAGE, or VOICE in GuildAction!");
+ Checks.check(name.length() >= 2 && name.length() <= 100,
+ "Channel name has to be between 2-100 characters long!");
+ Checks.check(type == ChannelType.VOICE || type == ChannelType.STAGE || name.matches("[a-zA-Z0-9-_]+"),
+ "Channels of type TEXT must have a name in alphanumeric with underscores!");
this.type = type;
this.name = name;
diff --git a/src/main/java/net/dv8tion/jda/api/requests/restaction/StageInstanceAction.java b/src/main/java/net/dv8tion/jda/api/requests/restaction/StageInstanceAction.java
new file mode 100644
index 0000000000..6564149a33
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/requests/restaction/StageInstanceAction.java
@@ -0,0 +1,78 @@
+/*
+ * 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.requests.restaction;
+
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.requests.RestAction;
+
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+
+/**
+ * Specialized {@link RestAction} used to create a {@link StageInstance}
+ *
+ * @see net.dv8tion.jda.api.entities.StageChannel#createStageInstance(String)
+ */
+public interface StageInstanceAction extends RestAction
+{
+ @Nonnull
+ @Override
+ StageInstanceAction setCheck(@Nullable BooleanSupplier checks);
+
+ @Nonnull
+ @Override
+ StageInstanceAction timeout(long timeout, @Nonnull TimeUnit unit);
+
+ @Nonnull
+ @Override
+ StageInstanceAction deadline(long timestamp);
+
+ /**
+ * Sets the topic for the stage instance.
+ *
This shows up in stage discovery and in the stage view.
+ *
+ * @param topic
+ * The topic, must be 1-120 characters long
+ *
+ * @throws IllegalArgumentException
+ * If the topic is null, empty, or longer than 120 characters
+ *
+ * @return The StageInstanceAction for chaining
+ */
+ @Nonnull
+ @CheckReturnValue
+ StageInstanceAction setTopic(@Nonnull String topic);
+
+ /**
+ * Sets the {@link net.dv8tion.jda.api.entities.StageInstance.PrivacyLevel PrivacyLevel} for the stage instance.
+ *
This indicates whether guild lurkers are allowed to join the stage instance or only guild members.
+ *
+ * @param level
+ * The {@link net.dv8tion.jda.api.entities.StageInstance.PrivacyLevel}
+ *
+ * @throws IllegalArgumentException
+ * If the provided level is null or {@link net.dv8tion.jda.api.entities.StageInstance.PrivacyLevel#UNKNOWN UNKNOWN}
+ *
+ * @return The StageInstanceAction for chaining
+ */
+ @Nonnull
+ @CheckReturnValue
+ StageInstanceAction setPrivacyLevel(@Nonnull StageInstance.PrivacyLevel level);
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/CategoryImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/CategoryImpl.java
index 5a1a2d0655..7589beca3e 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/CategoryImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/CategoryImpl.java
@@ -171,6 +171,14 @@ public ChannelAction createVoiceChannel(@Nonnull String name)
return trySync(action);
}
+ @Nonnull
+ @Override
+ public ChannelAction createStageChannel(@Nonnull String name)
+ {
+ ChannelAction action = getGuild().createStageChannel(name, this);
+ return trySync(action);
+ }
+
private ChannelAction trySync(ChannelAction action)
{
Member selfMember = getGuild().getSelfMember();
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 24d2766adc..1d2f0cbb69 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
@@ -279,6 +279,9 @@ public GuildImpl createGuild(long guildId, DataObject guildJson, TLongObjectMap<
createGuildEmotePass(guildObj, emotesArray);
+ guildJson.optArray("stage_instances")
+ .map(arr -> arr.stream(DataArray::getObject))
+ .ifPresent(list -> list.forEach(it -> createStageInstance(guildObj, it)));
guildObj.setAfkChannel(guildObj.getVoiceChannelById(afkChannelId))
.setSystemChannel(guildObj.getTextChannelById(systemChannelId))
@@ -296,6 +299,7 @@ private void createGuildChannel(GuildImpl guildObj, DataObject channelData)
case TEXT:
createTextChannel(guildObj, channelData, guildObj.getIdLong());
break;
+ case STAGE:
case VOICE:
createVoiceChannel(guildObj, channelData, guildObj.getIdLong());
break;
@@ -536,6 +540,11 @@ private void createVoiceState(GuildImpl guild, DataObject voiceStateJson, User u
LOG.error("Received a GuildVoiceState with a channel ID for a non-existent channel! ChannelId: {} GuildId: {} UserId: {}",
channelId, guild.getId(), user.getId());
+ String requestToSpeak = voiceStateJson.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"))
@@ -544,6 +553,7 @@ private void createVoiceState(GuildImpl guild, DataObject voiceStateJson, User u
.setSuppressed(voiceStateJson.getBoolean("suppress"))
.setSessionId(voiceStateJson.getString("session_id"))
.setStream(voiceStateJson.getBoolean("self_stream"))
+ .setRequestToSpeak(timestamp)
.setConnectedChannel(voiceChannel);
}
@@ -974,7 +984,10 @@ public VoiceChannel createVoiceChannel(GuildImpl guild, DataObject json, long gu
UnlockHook vlock = guildVoiceView.writeLock();
UnlockHook jlock = voiceView.writeLock())
{
- channel = new VoiceChannelImpl(id, guild);
+ if (json.getInt("type") == ChannelType.STAGE.getId())
+ channel = new StageChannelImpl(id, guild);
+ else
+ channel = new VoiceChannelImpl(id, guild);
guildVoiceView.getMap().put(id, channel);
playbackCache = voiceView.getMap().put(id, channel) == null;
}
@@ -1034,6 +1047,33 @@ public PrivateChannel createPrivateChannel(DataObject json, UserImpl user)
return priv;
}
+ @Nullable
+ public StageInstance createStageInstance(GuildImpl guild, DataObject json)
+ {
+ long channelId = json.getUnsignedLong("channel_id");
+ StageChannelImpl channel = (StageChannelImpl) guild.getStageChannelById(channelId);
+ if (channel == null)
+ return null;
+
+ long id = json.getUnsignedLong("id");
+ String topic = json.getString("topic");
+ boolean discoverable = !json.getBoolean("discoverable_disabled");
+ StageInstance.PrivacyLevel level = StageInstance.PrivacyLevel.fromKey(json.getInt("privacy_level", -1));
+
+
+ StageInstanceImpl instance = (StageInstanceImpl) channel.getStageInstance();
+ if (instance == null)
+ {
+ instance = new StageInstanceImpl(id, channel);
+ channel.setStageInstance(instance);
+ }
+
+ return instance
+ .setPrivacyLevel(level)
+ .setDiscoverable(discoverable)
+ .setTopic(topic);
+ }
+
public void createOverridesPass(AbstractChannelImpl,?> channel, DataArray overrides)
{
for (int i = 0; i < overrides.length(); i++)
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 7680db110b..adf3217268 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
@@ -84,6 +84,7 @@ public class GuildImpl implements Guild
private final CacheView.SimpleCacheView memberPresences;
private GuildManager manager;
+ private CompletableFuture pendingRequestToSpeak;
private Member owner;
private String name;
@@ -886,6 +887,36 @@ public AudioManager getAudioManager()
return mng;
}
+ @Nonnull
+ @Override
+ public synchronized Task requestToSpeak()
+ {
+ if (!isRequestToSpeakPending())
+ pendingRequestToSpeak = new CompletableFuture<>();
+
+ Task task = new GatewayTask<>(pendingRequestToSpeak, this::cancelRequestToSpeak);
+ updateRequestToSpeak();
+ return task;
+ }
+
+ @Nonnull
+ @Override
+ public synchronized Task cancelRequestToSpeak()
+ {
+ if (isRequestToSpeakPending())
+ {
+ pendingRequestToSpeak.cancel(false);
+ pendingRequestToSpeak = null;
+ }
+
+ VoiceChannel channel = getSelfMember().getVoiceState().getChannel();
+ StageInstance instance = channel instanceof StageChannel ? ((StageChannel) channel).getStageInstance() : null;
+ if (instance == null)
+ return new GatewayTask<>(CompletableFuture.completedFuture(null), () -> {});
+ CompletableFuture future = instance.cancelRequestToSpeak().submit();
+ return new GatewayTask<>(future, () -> future.cancel(false));
+ }
+
@Nonnull
@Override
public JDAImpl getJDA()
@@ -1551,7 +1582,6 @@ public ChannelAction createTextChannel(@Nonnull String name, Catego
Checks.notBlank(name, "Name");
name = name.trim();
- Checks.notEmpty(name, "Name");
Checks.notLonger(name, 100, "Name");
return new ChannelActionImpl<>(TextChannel.class, name, this, ChannelType.TEXT).setParent(parent);
}
@@ -1573,11 +1603,31 @@ public ChannelAction createVoiceChannel(@Nonnull String name, Cate
Checks.notBlank(name, "Name");
name = name.trim();
- Checks.notEmpty(name, "Name");
Checks.notLonger(name, 100, "Name");
return new ChannelActionImpl<>(VoiceChannel.class, name, this, ChannelType.VOICE).setParent(parent);
}
+ @Nonnull
+ @Override
+ public ChannelAction createStageChannel(@Nonnull String name, Category parent)
+ {
+ if (parent != null)
+ {
+ Checks.check(parent.getGuild().equals(this), "Category is not from the same guild!");
+ if (!getSelfMember().hasPermission(parent, Permission.MANAGE_CHANNEL))
+ throw new InsufficientPermissionException(parent, Permission.MANAGE_CHANNEL);
+ }
+ else
+ {
+ checkPermission(Permission.MANAGE_CHANNEL);
+ }
+
+ Checks.notBlank(name, "Name");
+ name = name.trim();
+ Checks.notLonger(name, 100, "Name");
+ return new ChannelActionImpl<>(StageChannel.class, name, this, ChannelType.STAGE).setParent(parent);
+ }
+
@Nonnull
@Override
public ChannelAction createCategory(@Nonnull String name)
@@ -1703,6 +1753,29 @@ private void checkRoles(Collection roles, String type, String preposition)
});
}
+ private synchronized boolean isRequestToSpeakPending()
+ {
+ return pendingRequestToSpeak != null && !pendingRequestToSpeak.isDone();
+ }
+
+ public synchronized void updateRequestToSpeak()
+ {
+ if (!isRequestToSpeakPending())
+ return;
+ VoiceChannel connectedChannel = getSelfMember().getVoiceState().getChannel();
+ if (!(connectedChannel instanceof StageChannel))
+ return;
+ StageChannel stage = (StageChannel) connectedChannel;
+ StageInstance instance = stage.getStageInstance();
+ if (instance == null)
+ return;
+
+ CompletableFuture future = pendingRequestToSpeak;
+ pendingRequestToSpeak = null;
+
+ instance.requestToSpeak().queue((v) -> future.complete(null), future::completeExceptionally);
+ }
+
// ---- Setters -----
public GuildImpl setAvailable(boolean available)
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java
index 1883c894bb..c0936b5299 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildVoiceStateImpl.java
@@ -17,12 +17,18 @@
package net.dv8tion.jda.internal.entities;
import net.dv8tion.jda.api.JDA;
-import net.dv8tion.jda.api.entities.Guild;
-import net.dv8tion.jda.api.entities.GuildVoiceState;
-import net.dv8tion.jda.api.entities.Member;
-import net.dv8tion.jda.api.entities.VoiceChannel;
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.*;
+import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.requests.CompletedRestAction;
+import net.dv8tion.jda.internal.requests.RestActionImpl;
+import net.dv8tion.jda.internal.requests.Route;
+import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.Nonnull;
+import java.time.OffsetDateTime;
public class GuildVoiceStateImpl implements GuildVoiceState
{
@@ -32,6 +38,7 @@ public class GuildVoiceStateImpl implements GuildVoiceState
private VoiceChannel connectedChannel;
private String sessionId;
+ private long requestToSpeak;
private boolean selfMuted = false;
private boolean selfDeafened = false;
private boolean guildMuted = false;
@@ -42,8 +49,7 @@ public class GuildVoiceStateImpl implements GuildVoiceState
public GuildVoiceStateImpl(Member member)
{
this.api = member.getJDA();
- this.guild = member.getGuild();
- this.member = member;
+ setMember(member);
}
@Override
@@ -71,6 +77,64 @@ public String getSessionId()
return sessionId;
}
+ public long getRequestToSpeak()
+ {
+ return requestToSpeak;
+ }
+
+ @Override
+ public OffsetDateTime getRequestToSpeakTimestamp()
+ {
+ return requestToSpeak == 0 ? null : Helpers.toOffset(requestToSpeak);
+ }
+
+ @Nonnull
+ @Override
+ public RestAction approveSpeaker()
+ {
+ return update(false);
+ }
+
+ @Nonnull
+ @Override
+ public RestAction declineSpeaker()
+ {
+ return update(true);
+ }
+
+ private RestAction update(boolean suppress)
+ {
+ if (requestToSpeak == 0L || !(connectedChannel instanceof StageChannel))
+ return new CompletedRestAction<>(api, null);
+ Member selfMember = getGuild().getSelfMember();
+ boolean isSelf = selfMember.equals(member);
+ if (!isSelf && !selfMember.hasPermission(connectedChannel, Permission.VOICE_MUTE_OTHERS))
+ throw new InsufficientPermissionException(connectedChannel, Permission.VOICE_MUTE_OTHERS);
+
+ Route.CompiledRoute route = Route.Guilds.UPDATE_VOICE_STATE.compile(guild.getId(), isSelf ? "@me" : getId());
+ DataObject body = DataObject.empty()
+ .put("channel_id", connectedChannel.getId())
+ .put("suppress", suppress);
+ return new RestActionImpl<>(getJDA(), route, body);
+ }
+
+ @Nonnull
+ @Override
+ public RestAction inviteSpeaker()
+ {
+ if (!(connectedChannel instanceof StageChannel))
+ return new CompletedRestAction<>(api, null);
+ if (!getGuild().getSelfMember().hasPermission(connectedChannel, Permission.VOICE_MUTE_OTHERS))
+ throw new InsufficientPermissionException(connectedChannel, Permission.VOICE_MUTE_OTHERS);
+
+ Route.CompiledRoute route = Route.Guilds.UPDATE_VOICE_STATE.compile(guild.getId(), getId());
+ DataObject body = DataObject.empty()
+ .put("channel_id", connectedChannel.getId())
+ .put("suppress", false)
+ .put("request_to_speak_timestamp", OffsetDateTime.now().toString());
+ return new RestActionImpl<>(getJDA(), route, body);
+ }
+
@Override
public boolean isMuted()
{
@@ -139,10 +203,16 @@ public boolean inVoiceChannel()
return getChannel() != null;
}
+ @Override
+ public long getIdLong()
+ {
+ return member.getIdLong();
+ }
+
@Override
public int hashCode()
{
- return getMember().hashCode();
+ return member.hashCode();
}
@Override
@@ -153,17 +223,23 @@ public boolean equals(Object obj)
if (!(obj instanceof GuildVoiceState))
return false;
GuildVoiceState oStatus = (GuildVoiceState) obj;
- return this.getMember().equals(oStatus.getMember());
+ return member.equals(oStatus.getMember());
}
@Override
public String toString()
{
- return "VS:" + getGuild().getName() + ':' + getMember().getEffectiveName();
+ return "VS:" + getGuild().getName() + '(' + getId() + ')';
}
// -- Setters --
+ public GuildVoiceStateImpl setMember(Member member)
+ {
+ this.member = member;
+ return this;
+ }
+
public GuildVoiceStateImpl setConnectedChannel(VoiceChannel connectedChannel)
{
this.connectedChannel = connectedChannel;
@@ -211,4 +287,10 @@ public GuildVoiceStateImpl setStream(boolean stream)
this.stream = stream;
return this;
}
+
+ public GuildVoiceStateImpl setRequestToSpeak(OffsetDateTime timestamp)
+ {
+ this.requestToSpeak = timestamp == null ? 0L : timestamp.toInstant().toEpochMilli();
+ return this;
+ }
}
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 b0068a730b..660b4cc265 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/MemberImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/MemberImpl.java
@@ -31,16 +31,13 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.awt.*;
-import java.time.Instant;
import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
import java.util.List;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MemberImpl implements Member
{
- private static final ZoneOffset OFFSET = ZoneOffset.of("+00:00");
private final JDAImpl api;
private final Set roles = ConcurrentHashMap.newKeySet();
private final GuildVoiceState voiceState;
@@ -100,7 +97,7 @@ public JDA getJDA()
public OffsetDateTime getTimeJoined()
{
if (hasTimeJoined())
- return OffsetDateTime.ofInstant(Instant.ofEpochMilli(joinDate), OFFSET);
+ return Helpers.toOffset(joinDate);
return getGuild().getTimeCreated();
}
@@ -114,7 +111,7 @@ public boolean hasTimeJoined()
@Override
public OffsetDateTime getTimeBoosted()
{
- return boostDate != 0 ? OffsetDateTime.ofInstant(Instant.ofEpochMilli(boostDate), OFFSET) : null;
+ return boostDate != 0 ? Helpers.toOffset(boostDate) : null;
}
@Override
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/StageChannelImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/StageChannelImpl.java
new file mode 100644
index 0000000000..fa155a5569
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/entities/StageChannelImpl.java
@@ -0,0 +1,74 @@
+/*
+ * 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.internal.entities;
+
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.ChannelType;
+import net.dv8tion.jda.api.entities.StageChannel;
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.requests.restaction.StageInstanceAction;
+import net.dv8tion.jda.internal.requests.restaction.StageInstanceActionImpl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.EnumSet;
+
+public class StageChannelImpl extends VoiceChannelImpl implements StageChannel
+{
+ private StageInstance instance;
+
+ public StageChannelImpl(long id, GuildImpl guild)
+ {
+ super(id, guild);
+ }
+
+ @Nonnull
+ @Override
+ public ChannelType getType()
+ {
+ return ChannelType.STAGE;
+ }
+
+ @Nullable
+ @Override
+ public StageInstance getStageInstance()
+ {
+ return instance;
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceAction createStageInstance(@Nonnull String topic)
+ {
+ EnumSet permissions = getGuild().getSelfMember().getPermissions(this);
+ EnumSet required = EnumSet.of(Permission.MANAGE_CHANNEL, Permission.VOICE_MUTE_OTHERS, Permission.VOICE_MOVE_OTHERS);
+ for (Permission perm : required)
+ {
+ if (!permissions.contains(perm))
+ throw new InsufficientPermissionException(this, perm, "You must be a stage moderator to create a stage instance! Missing Permission: " + perm);
+ }
+
+ return new StageInstanceActionImpl(this).setTopic(topic);
+ }
+
+ public StageChannelImpl setStageInstance(StageInstance instance)
+ {
+ this.instance = instance;
+ return this;
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/StageInstanceImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/StageInstanceImpl.java
new file mode 100644
index 0000000000..dcaceb36a0
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/entities/StageInstanceImpl.java
@@ -0,0 +1,175 @@
+/*
+ * 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.internal.entities;
+
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.StageChannel;
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
+import net.dv8tion.jda.api.managers.StageInstanceManager;
+import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.managers.StageInstanceManagerImpl;
+import net.dv8tion.jda.internal.requests.RestActionImpl;
+import net.dv8tion.jda.internal.requests.Route;
+
+import javax.annotation.Nonnull;
+import java.time.OffsetDateTime;
+import java.util.EnumSet;
+
+public class StageInstanceImpl implements StageInstance
+{
+ private final long id;
+ private StageChannel channel;
+ private StageInstanceManager manager;
+
+ private String topic;
+ private PrivacyLevel privacyLevel;
+ private boolean discoverable;
+
+ public StageInstanceImpl(long id, StageChannel channel)
+ {
+ this.id = id;
+ this.channel = channel;
+ }
+
+ @Override
+ public long getIdLong()
+ {
+ return id;
+ }
+
+ @Nonnull
+ @Override
+ public Guild getGuild()
+ {
+ return getChannel().getGuild();
+ }
+
+ @Nonnull
+ @Override
+ public StageChannel getChannel()
+ {
+ StageChannel real = channel.getJDA().getStageChannelById(channel.getIdLong());
+ if (real != null)
+ channel = real;
+ return channel;
+ }
+
+ @Nonnull
+ @Override
+ public String getTopic()
+ {
+ return topic;
+ }
+
+ @Nonnull
+ @Override
+ public PrivacyLevel getPrivacyLevel()
+ {
+ return privacyLevel;
+ }
+
+ @Override
+ public boolean isDiscoverable()
+ {
+ return discoverable;
+ }
+
+ @Nonnull
+ @Override
+ public RestAction delete()
+ {
+ checkPermissions();
+ Route.CompiledRoute route = Route.StageInstances.DELETE_INSTANCE.compile(channel.getId());
+ return new RestActionImpl<>(channel.getJDA(), route);
+ }
+
+ @Nonnull
+ @Override
+ public RestAction requestToSpeak()
+ {
+ Guild guild = getGuild();
+ Route.CompiledRoute route = Route.Guilds.UPDATE_VOICE_STATE.compile(guild.getId(), "@me");
+ DataObject body = DataObject.empty().put("channel_id", channel.getId());
+ // Stage moderators can bypass the request queue by just unsuppressing
+ if (guild.getSelfMember().hasPermission(getChannel(), Permission.VOICE_MUTE_OTHERS))
+ body.putNull("request_to_speak_timestamp").put("suppress", false);
+ else
+ body.put("request_to_speak_timestamp", OffsetDateTime.now().toString());
+
+ if (!channel.equals(guild.getSelfMember().getVoiceState().getChannel()))
+ throw new IllegalStateException("Cannot request to speak without being connected to the stage channel!");
+ return new RestActionImpl<>(channel.getJDA(), route, body);
+ }
+
+ @Nonnull
+ @Override
+ public RestAction cancelRequestToSpeak()
+ {
+ Guild guild = getGuild();
+ Route.CompiledRoute route = Route.Guilds.UPDATE_VOICE_STATE.compile(guild.getId(), "@me");
+ DataObject body = DataObject.empty()
+ .putNull("request_to_speak_timestamp")
+ .put("suppress", true)
+ .put("channel_id", channel.getId());
+
+ if (!channel.equals(guild.getSelfMember().getVoiceState().getChannel()))
+ throw new IllegalStateException("Cannot cancel request to speak without being connected to the stage channel!");
+ return new RestActionImpl<>(channel.getJDA(), route, body);
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceManager getManager()
+ {
+ checkPermissions();
+ if (manager == null)
+ manager = new StageInstanceManagerImpl(this);
+ return manager;
+ }
+
+ public StageInstanceImpl setTopic(String topic)
+ {
+ this.topic = topic;
+ return this;
+ }
+
+ public StageInstanceImpl setPrivacyLevel(PrivacyLevel privacyLevel)
+ {
+ this.privacyLevel = privacyLevel;
+ return this;
+ }
+
+ public StageInstanceImpl setDiscoverable(boolean discoverable)
+ {
+ this.discoverable = discoverable;
+ return this;
+ }
+
+ private void checkPermissions()
+ {
+ EnumSet permissions = getGuild().getSelfMember().getPermissions(getChannel());
+ EnumSet required = EnumSet.of(Permission.MANAGE_CHANNEL, Permission.VOICE_MUTE_OTHERS, Permission.VOICE_MOVE_OTHERS);
+ for (Permission perm : required)
+ {
+ if (!permissions.contains(perm))
+ throw new InsufficientPermissionException(getChannel(), perm, "You must be a stage moderator to manage a stage instance! Missing Permission: " + perm);
+ }
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java
index 1a8cb2a00f..76d19abe65 100644
--- a/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java
+++ b/src/main/java/net/dv8tion/jda/internal/handle/ChannelCreateHandler.java
@@ -67,6 +67,7 @@ protected Long handleInternally(DataObject content)
builder.createTextChannel(content, guildId)));
break;
}
+ case STAGE:
case VOICE:
{
jda.handleEvent(
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java
index ddfa50052b..c361c51af6 100644
--- a/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java
+++ b/src/main/java/net/dv8tion/jda/internal/handle/ChannelDeleteHandler.java
@@ -84,6 +84,7 @@ protected Long handleInternally(DataObject content)
channel));
break;
}
+ case STAGE:
case VOICE:
{
VoiceChannel channel = getJDA().getVoiceChannelsView().remove(channelId);
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java
index aca2646285..f195669177 100644
--- a/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java
+++ b/src/main/java/net/dv8tion/jda/internal/handle/ChannelUpdateHandler.java
@@ -183,6 +183,7 @@ protected Long handleInternally(DataObject content)
applyPermissions(textChannel, permOverwrites);
break; //Finish the TextChannelUpdate case
}
+ case STAGE:
case VOICE:
{
VoiceChannelImpl voiceChannel = (VoiceChannelImpl) getJDA().getVoiceChannelsView().get(channelId);
@@ -333,6 +334,7 @@ private void applyPermissions(AbstractChannelImpl,?> channel, DataArray permOv
api, responseNumber,
(StoreChannel) channel, changed));
break;
+ case STAGE:
case VOICE:
api.handleEvent(
new VoiceChannelUpdatePermissionsEvent(
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceCreateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceCreateHandler.java
new file mode 100644
index 0000000000..4424cd6bf3
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceCreateHandler.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.handle;
+
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.events.stage.StageInstanceCreateEvent;
+import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.JDAImpl;
+import net.dv8tion.jda.internal.entities.GuildImpl;
+
+public class StageInstanceCreateHandler extends SocketHandler
+{
+ public StageInstanceCreateHandler(JDAImpl api)
+ {
+ super(api);
+ }
+
+ @Override
+ protected Long handleInternally(DataObject content)
+ {
+ long guildId = content.getUnsignedLong("guild_id", 0L);
+ if (getJDA().getGuildSetupController().isLocked(guildId))
+ return guildId;
+
+ GuildImpl guild = (GuildImpl) getJDA().getGuildById(guildId);
+ if (guild == null)
+ {
+ EventCache.LOG.debug("Caching STAGE_INSTANCE_CREATE for uncached guild with id {}", guildId);
+ getJDA().getEventCache().cache(EventCache.Type.GUILD, guildId, responseNumber, allContent, this::handle);
+ return null;
+ }
+
+ StageInstance instance = getJDA().getEntityBuilder().createStageInstance(guild, content);
+ if (instance != null)
+ {
+ getJDA().handleEvent(new StageInstanceCreateEvent(getJDA(), responseNumber, instance));
+ guild.updateRequestToSpeak();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceDeleteHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceDeleteHandler.java
new file mode 100644
index 0000000000..3fbbe98f0c
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceDeleteHandler.java
@@ -0,0 +1,58 @@
+/*
+ * 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.internal.handle;
+
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.events.stage.StageInstanceDeleteEvent;
+import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.JDAImpl;
+import net.dv8tion.jda.internal.entities.GuildImpl;
+import net.dv8tion.jda.internal.entities.StageChannelImpl;
+
+public class StageInstanceDeleteHandler extends SocketHandler
+{
+ public StageInstanceDeleteHandler(JDAImpl api)
+ {
+ super(api);
+ }
+
+ @Override
+ protected Long handleInternally(DataObject content)
+ {
+ long guildId = content.getUnsignedLong("guild_id", 0L);
+ if (getJDA().getGuildSetupController().isLocked(guildId))
+ return guildId;
+
+ GuildImpl guild = (GuildImpl) getJDA().getGuildById(guildId);
+ if (guild == null)
+ {
+ EventCache.LOG.debug("Caching STAGE_INSTANCE_DELETE for uncached guild with id {}", guildId);
+ getJDA().getEventCache().cache(EventCache.Type.GUILD, guildId, responseNumber, allContent, this::handle);
+ return null;
+ }
+
+ long channelId = content.getUnsignedLong("channel_id", 0L);
+ StageChannelImpl channel = (StageChannelImpl) guild.getStageChannelById(channelId);
+ if (channel == null)
+ return null;
+ StageInstance instance = channel.getStageInstance();
+ channel.setStageInstance(null);
+ if (instance != null)
+ getJDA().handleEvent(new StageInstanceDeleteEvent(getJDA(), responseNumber, instance));
+ return null;
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceUpdateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceUpdateHandler.java
new file mode 100644
index 0000000000..5113467b6e
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/handle/StageInstanceUpdateHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.internal.handle;
+
+import net.dv8tion.jda.api.entities.StageChannel;
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.events.stage.update.StageInstanceUpdatePrivacyLevelEvent;
+import net.dv8tion.jda.api.events.stage.update.StageInstanceUpdateTopicEvent;
+import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.JDAImpl;
+import net.dv8tion.jda.internal.entities.GuildImpl;
+
+import java.util.Objects;
+
+public class StageInstanceUpdateHandler extends SocketHandler
+{
+ public StageInstanceUpdateHandler(JDAImpl api)
+ {
+ super(api);
+ }
+
+ @Override
+ protected Long handleInternally(DataObject content)
+ {
+ long guildId = content.getUnsignedLong("guild_id", 0L);
+ if (getJDA().getGuildSetupController().isLocked(guildId))
+ return guildId;
+
+ GuildImpl guild = (GuildImpl) getJDA().getGuildById(guildId);
+ if (guild == null)
+ {
+ EventCache.LOG.debug("Caching STAGE_INSTANCE_UPDATE for uncached guild with id {}", guildId);
+ getJDA().getEventCache().cache(EventCache.Type.GUILD, guildId, responseNumber, allContent, this::handle);
+ return null;
+ }
+
+ StageChannel channel = getJDA().getStageChannelById(content.getUnsignedLong("channel_id"));
+ if (channel == null)
+ return null;
+ StageInstance oldInstance = channel.getStageInstance();
+ if (oldInstance == null)
+ return null;
+
+ String oldTopic = oldInstance.getTopic();
+ StageInstance.PrivacyLevel oldLevel = oldInstance.getPrivacyLevel();
+ StageInstance newInstance = getJDA().getEntityBuilder().createStageInstance(guild, content);
+ if (newInstance == null)
+ return null;
+
+ if (!Objects.equals(oldTopic, newInstance.getTopic()))
+ getJDA().handleEvent(new StageInstanceUpdateTopicEvent(getJDA(), responseNumber, newInstance, oldTopic));
+ if (oldLevel != newInstance.getPrivacyLevel())
+ getJDA().handleEvent(new StageInstanceUpdatePrivacyLevelEvent(getJDA(), responseNumber, newInstance, oldLevel));
+ return null;
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/handle/VoiceStateUpdateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/VoiceStateUpdateHandler.java
index 3c1a65e908..b0b12b157f 100644
--- a/src/main/java/net/dv8tion/jda/internal/handle/VoiceStateUpdateHandler.java
+++ b/src/main/java/net/dv8tion/jda/internal/handle/VoiceStateUpdateHandler.java
@@ -26,7 +26,9 @@
import net.dv8tion.jda.internal.entities.MemberImpl;
import net.dv8tion.jda.internal.entities.VoiceChannelImpl;
import net.dv8tion.jda.internal.managers.AudioManagerImpl;
+import net.dv8tion.jda.internal.requests.WebSocketClient;
+import java.time.OffsetDateTime;
import java.util.Objects;
public class VoiceStateUpdateHandler extends SocketHandler
@@ -44,6 +46,14 @@ protected Long handleInternally(DataObject content)
return null; //unhandled for calls
if (getJDA().getGuildSetupController().isLocked(guildId))
return guildId;
+
+ // TODO: Handle these voice states properly
+ if (content.isNull("member"))
+ {
+ WebSocketClient.LOG.debug("Discarding VOICE_STATE_UPDATE with missing member. JSON: {}", content);
+ return null;
+ }
+
handleGuildVoiceState(content);
return null;
}
@@ -60,6 +70,14 @@ private void handleGuildVoiceState(DataObject content)
boolean guildDeafened = content.getBoolean("deaf");
boolean suppressed = content.getBoolean("suppress");
boolean stream = content.getBoolean("self_stream");
+ String requestToSpeak = content.getString("request_to_speak_timestamp", null);
+ OffsetDateTime requestToSpeakTime = null;
+ long requestToSpeakTimestamp = 0L;
+ if (requestToSpeak != null)
+ {
+ requestToSpeakTime = OffsetDateTime.parse(requestToSpeak);
+ requestToSpeakTimestamp = requestToSpeakTime.toInstant().toEpochMilli();
+ }
Guild guild = getJDA().getGuildById(guildId);
if (guild == null)
@@ -132,6 +150,12 @@ private void handleGuildVoiceState(DataObject content)
getJDA().handleEvent(new GuildVoiceMuteEvent(getJDA(), responseNumber, member));
if (wasDeaf != vState.isDeafened())
getJDA().handleEvent(new GuildVoiceDeafenEvent(getJDA(), responseNumber, member));
+ if (requestToSpeakTimestamp != vState.getRequestToSpeak())
+ {
+ OffsetDateTime oldRequestToSpeak = vState.getRequestToSpeakTimestamp();
+ vState.setRequestToSpeak(requestToSpeakTime);
+ getJDA().handleEvent(new GuildVoiceRequestToSpeakEvent(getJDA(), responseNumber, member, oldRequestToSpeak, requestToSpeakTime));
+ }
if (!Objects.equals(channel, vState.getChannel()))
{
@@ -152,7 +176,7 @@ else if (channel == null)
oldChannel.getConnectedMembersMap().remove(userId);
if (isSelf)
getJDA().getDirectAudioController().update(guild, null);
- getJDA().getEntityBuilder().updateMemberCache(member);
+ getJDA().getEntityBuilder().updateMemberCache(member, memberJson.isNull("joined_at"));
getJDA().handleEvent(
new GuildVoiceLeaveEvent(
getJDA(), responseNumber,
@@ -193,5 +217,7 @@ else if (channel == null)
if (voiceInterceptor.onVoiceStateUpdate(new VoiceDispatchInterceptor.VoiceStateUpdate(channel, vState, allContent)))
getJDA().getDirectAudioController().update(guild, channel);
}
+
+ ((GuildImpl) guild).updateRequestToSpeak();
}
}
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/ChannelManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/ChannelManagerImpl.java
index 18c2622432..620e5468f4 100644
--- a/src/main/java/net/dv8tion/jda/internal/managers/ChannelManagerImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/managers/ChannelManagerImpl.java
@@ -297,7 +297,7 @@ public ChannelManagerImpl setName(@Nonnull String name)
public ChannelManagerImpl setRegion(@Nonnull Region region)
{
Checks.notNull(region, "Region");
- if (getType() != ChannelType.VOICE)
+ if (!getType().isAudio())
throw new IllegalStateException("Can only change region on voice channels!");
Checks.check(Region.VOICE_CHANNEL_REGIONS.contains(region), "Region is not usable for VoiceChannel region overrides!");
this.region = region == Region.AUTOMATIC ? null : region.getKey();
@@ -389,7 +389,7 @@ public ChannelManagerImpl setUserLimit(int userLimit)
@CheckReturnValue
public ChannelManagerImpl setBitrate(int bitrate)
{
- if (getType() != ChannelType.VOICE)
+ if (!getType().isAudio())
throw new IllegalStateException("Can only set bitrate on voice channels");
final int maxBitrate = getGuild().getMaxBitrate();
Checks.check(bitrate >= 8000, "Bitrate must be greater or equal to 8000");
diff --git a/src/main/java/net/dv8tion/jda/internal/managers/StageInstanceManagerImpl.java b/src/main/java/net/dv8tion/jda/internal/managers/StageInstanceManagerImpl.java
new file mode 100644
index 0000000000..f04dd60564
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/managers/StageInstanceManagerImpl.java
@@ -0,0 +1,86 @@
+/*
+ * 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.internal.managers;
+
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.managers.StageInstanceManager;
+import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.requests.Route;
+import net.dv8tion.jda.internal.utils.Checks;
+import okhttp3.RequestBody;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class StageInstanceManagerImpl extends ManagerBase implements StageInstanceManager
+{
+ private final StageInstance instance;
+
+ private String topic;
+ private StageInstance.PrivacyLevel privacyLevel;
+
+ public StageInstanceManagerImpl(StageInstance instance)
+ {
+ super(instance.getChannel().getJDA(), Route.StageInstances.UPDATE_INSTANCE.compile(instance.getChannel().getId()));
+ this.instance = instance;
+ }
+
+ @Nonnull
+ @Override
+ public StageInstance getStageInstance()
+ {
+ return instance;
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceManager setTopic(@Nullable String topic)
+ {
+ if (topic != null)
+ {
+ topic = topic.trim();
+ Checks.notLonger(topic, 120, "Topic");
+ if (topic.isEmpty())
+ topic = null;
+ }
+ this.topic = topic;
+ set |= TOPIC;
+ return this;
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceManager setPrivacyLevel(@Nonnull StageInstance.PrivacyLevel level)
+ {
+ Checks.notNull(level, "PrivacyLevel");
+ Checks.check(level != StageInstance.PrivacyLevel.UNKNOWN, "PrivacyLevel must not be UNKNOWN!");
+ this.privacyLevel = level;
+ set |= PRIVACY_LEVEL;
+ return this;
+ }
+
+ @Override
+ protected RequestBody finalizeData()
+ {
+ DataObject body = DataObject.empty();
+ if (shouldUpdate(TOPIC) && topic != null)
+ body.put("topic", topic);
+ if (shouldUpdate(PRIVACY_LEVEL))
+ body.put("privacy_level", privacyLevel.getKey());
+ return getRequestBody(body);
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/Route.java b/src/main/java/net/dv8tion/jda/internal/requests/Route.java
index 2448e8a9aa..6680ff9a2e 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/Route.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/Route.java
@@ -145,6 +145,7 @@ public static class Guilds
public static final Route GET_GUILD_EMOTES = 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 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");
public static final Route CREATE_INTEGRATION = new Route(POST, "guilds/{guild_id}/integrations");
@@ -225,6 +226,14 @@ public static class Channels
public static final Route STOP_CALL = new Route(POST, "channels/{channel_id}/call/stop_ringing"); // aka deny or end call
}
+ public static class StageInstances
+ {
+ public static final Route GET_INSTANCE = new Route(GET, "stage-instances/{channel_id}");
+ public static final Route DELETE_INSTANCE = new Route(DELETE, "stage-instances/{channel_id}");
+ public static final Route UPDATE_INSTANCE = new Route(PATCH, "stage-instances/{channel_id}");
+ public static final Route CREATE_INSTANCE = new Route(POST, "stage-instances");
+ }
+
public static class Messages
{
public static final Route EDIT_MESSAGE = new Route(PATCH, "channels/{channel_id}/messages/{message_id}"); // requires special handling, same bucket but different endpoints
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java b/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java
index 94db588cd1..da68ce1f09 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java
@@ -1348,6 +1348,9 @@ protected void setupHandlers()
handlers.put("MESSAGE_REACTION_REMOVE_EMOTE", new MessageReactionClearEmoteHandler(api));
handlers.put("MESSAGE_UPDATE", new MessageUpdateHandler(api));
handlers.put("READY", new ReadyHandler(api));
+ handlers.put("STAGE_INSTANCE_CREATE", new StageInstanceCreateHandler(api));
+ handlers.put("STAGE_INSTANCE_DELETE", new StageInstanceDeleteHandler(api));
+ handlers.put("STAGE_INSTANCE_UPDATE", new StageInstanceUpdateHandler(api));
handlers.put("USER_UPDATE", new UserUpdateHandler(api));
handlers.put("VOICE_SERVER_UPDATE", new VoiceServerUpdateHandler(api));
handlers.put("VOICE_STATE_UPDATE", new VoiceStateUpdateHandler(api));
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java
index faa264435a..f954db5f99 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/ChannelActionImpl.java
@@ -282,8 +282,8 @@ private ChannelActionImpl addOverride(long targetId, int type, long allow, lo
@CheckReturnValue
public ChannelActionImpl setBitrate(Integer bitrate)
{
- if (type != ChannelType.VOICE)
- throw new UnsupportedOperationException("Can only set the bitrate for a VoiceChannel!");
+ if (!type.isAudio())
+ throw new UnsupportedOperationException("Can only set the bitrate for an Audio Channel!");
if (bitrate != null)
{
int maxBitrate = getGuild().getMaxBitrate();
@@ -328,14 +328,17 @@ protected RequestBody finalizeData()
object.put("user_limit", userlimit);
break;
case TEXT:
- if (topic != null && !topic.isEmpty())
- object.put("topic", topic);
if (nsfw != null)
object.put("nsfw", nsfw);
if (slowmode != null)
object.put("rate_limit_per_user", slowmode);
if (news != null)
object.put("type", news ? 5 : 0);
+ break;
+ case STAGE:
+ if (bitrate != null)
+ object.put("bitrate", bitrate);
+ break;
}
if (type != ChannelType.CATEGORY && parent != null)
object.put("parent_id", parent.getId());
@@ -350,6 +353,7 @@ protected void handleSuccess(Response response, Request request)
GuildChannel channel;
switch (type)
{
+ case STAGE:
case VOICE:
channel = builder.createVoiceChannel(response.getObject(), guild.getIdLong());
break;
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/restaction/StageInstanceActionImpl.java b/src/main/java/net/dv8tion/jda/internal/requests/restaction/StageInstanceActionImpl.java
new file mode 100644
index 0000000000..f74453e7fb
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/internal/requests/restaction/StageInstanceActionImpl.java
@@ -0,0 +1,104 @@
+/*
+ * 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.internal.requests.restaction;
+
+import net.dv8tion.jda.api.entities.StageChannel;
+import net.dv8tion.jda.api.entities.StageInstance;
+import net.dv8tion.jda.api.requests.Request;
+import net.dv8tion.jda.api.requests.Response;
+import net.dv8tion.jda.api.requests.restaction.StageInstanceAction;
+import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.entities.GuildImpl;
+import net.dv8tion.jda.internal.requests.RestActionImpl;
+import net.dv8tion.jda.internal.requests.Route;
+import net.dv8tion.jda.internal.utils.Checks;
+import okhttp3.RequestBody;
+
+import javax.annotation.Nonnull;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+
+public class StageInstanceActionImpl extends RestActionImpl implements StageInstanceAction
+{
+ private final StageChannel channel;
+ private String topic;
+ private StageInstance.PrivacyLevel level = StageInstance.PrivacyLevel.GUILD_ONLY;
+
+ public StageInstanceActionImpl(StageChannel channel)
+ {
+ super(channel.getJDA(), Route.StageInstances.CREATE_INSTANCE.compile());
+ this.channel = channel;
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceAction setCheck(BooleanSupplier checks)
+ {
+ return (StageInstanceAction) super.setCheck(checks);
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceAction timeout(long timeout, @Nonnull TimeUnit unit)
+ {
+ return (StageInstanceAction) super.timeout(timeout, unit);
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceAction deadline(long timestamp)
+ {
+ return (StageInstanceAction) super.deadline(timestamp);
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceAction setTopic(@Nonnull String topic)
+ {
+ Checks.notEmpty(topic, "Topic");
+ Checks.notLonger(topic, 120, "Topic");
+ this.topic = topic;
+ return this;
+ }
+
+ @Nonnull
+ @Override
+ public StageInstanceAction setPrivacyLevel(@Nonnull StageInstance.PrivacyLevel level)
+ {
+ Checks.notNull(level, "PrivacyLevel");
+ Checks.check(level != StageInstance.PrivacyLevel.UNKNOWN, "The PrivacyLevel must not be UNKNOWN!");
+ this.level = level;
+ return this;
+ }
+
+ @Override
+ protected RequestBody finalizeData()
+ {
+ DataObject body = DataObject.empty();
+ body.put("channel_id", channel.getId());
+ body.put("topic", topic);
+ body.put("privacy_level", level.getKey());
+ return getRequestBody(body);
+ }
+
+ @Override
+ protected void handleSuccess(Response response, Request request)
+ {
+ StageInstance instance = api.getEntityBuilder().createStageInstance((GuildImpl) channel.getGuild(), response.getObject());
+ request.onSuccess(instance);
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java b/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java
index bc6dd3b766..727b1af520 100644
--- a/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java
+++ b/src/main/java/net/dv8tion/jda/internal/utils/Helpers.java
@@ -16,6 +16,9 @@
package net.dv8tion.jda.internal.utils;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
import java.util.*;
import java.util.function.Consumer;
@@ -26,6 +29,7 @@
*/
public final class Helpers
{
+ private static final ZoneOffset OFFSET = ZoneOffset.of("+00:00");
@SuppressWarnings("rawtypes")
private static final Consumer EMPTY_CONSUMER = (v) -> {};
@@ -35,6 +39,11 @@ public static Consumer emptyConsumer()
return (Consumer) EMPTY_CONSUMER;
}
+ public static OffsetDateTime toOffset(long instant)
+ {
+ return OffsetDateTime.ofInstant(Instant.ofEpochMilli(instant), OFFSET);
+ }
+
// locale-safe String#format
public static String format(String format, Object... args)
diff --git a/src/main/java/net/dv8tion/jda/internal/utils/PermissionUtil.java b/src/main/java/net/dv8tion/jda/internal/utils/PermissionUtil.java
index 4591060b2a..85ac63949f 100644
--- a/src/main/java/net/dv8tion/jda/internal/utils/PermissionUtil.java
+++ b/src/main/java/net/dv8tion/jda/internal/utils/PermissionUtil.java
@@ -368,7 +368,7 @@ public static long getEffectivePermission(GuildChannel channel, Member member)
//When the permission to view the channel or to connect to the channel is not applied it is not granted
// This means that we have no access to this channel at all
- final boolean hasConnect = channel.getType() != ChannelType.VOICE || isApplied(permission, connectChannel);
+ final boolean hasConnect = (channel.getType() != ChannelType.VOICE && channel.getType() != ChannelType.STAGE) || isApplied(permission, connectChannel);
final boolean hasView = isApplied(permission, viewChannel);
return hasView && hasConnect ? permission : 0;
}