Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass on stage channels #1575

Merged
merged 47 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b64f0be
First pass on stage channels
MinnDevelopment Apr 2, 2021
92766bf
Update usage of channel type
MinnDevelopment Apr 5, 2021
f85d212
Apply suggestions from code review
MinnDevelopment Apr 6, 2021
cd10a2b
Add support for speaker moderation
MinnDevelopment Apr 12, 2021
e7eefa8
Actually fire the event
MinnDevelopment Apr 12, 2021
a695147
Fix Guild#requestToSpeak
MinnDevelopment Apr 12, 2021
38fb666
Add Guild#cancelRequestToSpeak
MinnDevelopment Apr 12, 2021
5492d1d
Improve some logic
MinnDevelopment Apr 12, 2021
a7a2bbb
Merge remote-tracking branch 'origin/development' into feature/stage-…
MinnDevelopment Jun 16, 2021
0b36c38
Implement some missing features
MinnDevelopment Jun 16, 2021
6541612
Add GuildVoiceState#inviteSpeaker
MinnDevelopment Jun 16, 2021
d62eff4
Add check for missing members in voice states
MinnDevelopment Jun 17, 2021
b20ffaa
Handle topics correctly
MinnDevelopment Jun 17, 2021
08e6b9b
Fix length checks
MinnDevelopment Jun 17, 2021
7d5d7d9
Support detached voice states
MinnDevelopment Jun 17, 2021
0e0480f
Remove topic from the stage channel interface
MinnDevelopment Jun 17, 2021
f788371
Fix docs
MinnDevelopment Jun 17, 2021
6efd23c
Remove setTopic in EntityBuilder
MinnDevelopment Jun 17, 2021
ac4939d
Add support for stage instances
MinnDevelopment Jun 18, 2021
ae65a7d
Add StageInstance#delete
MinnDevelopment Jun 19, 2021
aee47e8
Add StageInstanceManager
MinnDevelopment Jun 20, 2021
2bd3b4a
Add support for StageChannel#createStageInstance
MinnDevelopment Jun 20, 2021
2b824d3
Add permission checks
MinnDevelopment Jun 20, 2021
3f915ef
Add events
MinnDevelopment Jun 20, 2021
4da9700
Fix some bugs and add docs to StageChannel
MinnDevelopment Jun 20, 2021
956111c
Add docs to StageInstance
MinnDevelopment Jun 20, 2021
31e520c
Add docs to StageInstanceManager
MinnDevelopment Jun 20, 2021
18e0d09
Fix typo
MinnDevelopment Jun 20, 2021
5a7ced0
Add docs for StageInstanceAction
MinnDevelopment Jun 20, 2021
1e71416
Add docs for request to speak
MinnDevelopment Jun 20, 2021
129970b
Add check for missing member in voice state update
MinnDevelopment Jun 20, 2021
8475439
Add event requirements
MinnDevelopment Jun 20, 2021
557b1cf
Add docs for stage instance events
MinnDevelopment Jun 20, 2021
a320977
Add docs for stage channel cache
MinnDevelopment Jun 20, 2021
0bdaef2
Add docs for Guild#requestToSpeak
MinnDevelopment Jun 20, 2021
f9b0368
Remove duplicate check
MinnDevelopment Jun 20, 2021
56c5460
Change how request to speak is implemented
MinnDevelopment Jun 20, 2021
f8d9c38
Properly remove lurkers from cache
MinnDevelopment Jun 20, 2021
5fd77d7
Add audit log enum constants
MinnDevelopment Jun 20, 2021
0deade2
Make requestToSpeak use tasks
MinnDevelopment Jun 22, 2021
68ccdd3
Add missing annotations
MinnDevelopment Jun 22, 2021
b87d1b7
Also add annotations to implementation
MinnDevelopment Jun 22, 2021
01df722
Cleanup and missing docs
MinnDevelopment Jun 22, 2021
9475f8c
Add StageInstance#getSpeakers and StageInstance#getAudience
MinnDevelopment Jun 22, 2021
532c934
Add StageInstance#requestToSpeak
MinnDevelopment Jun 28, 2021
002a443
Implement review suggestions
MinnDevelopment Jun 29, 2021
33d1038
Remaining review suggestions
MinnDevelopment Jun 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/main/java/net/dv8tion/jda/api/JDA.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

/**
* The core of JDA. Acts as a registry system of JDA. All parts of the the API can be accessed starting from this class.
Expand Down Expand Up @@ -1164,6 +1165,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:
Expand All @@ -1172,6 +1175,29 @@ default GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id)
return null;
}

@Nonnull
default List<StageChannel> getStageChannelsByName(@Nonnull String name, boolean ignoreCase)
{
return getVoiceChannelsByName(name, ignoreCase)
.stream()
.filter(StageChannel.class::isInstance)
.map(StageChannel.class::cast)
.collect(Collectors.toList());
}

@Nullable
default StageChannel getStageChannelById(@Nonnull String id)
{
return getStageChannelById(MiscUtil.parseSnowflake(id));
}

@Nullable
default StageChannel getStageChannelById(long id)
{
VoiceChannel channel = getVoiceChannelById(id);
return channel instanceof StageChannel ? (StageChannel) channel : null;
}

/**
* {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} of
* all cached {@link net.dv8tion.jda.api.entities.Category Categories} visible to this JDA session.
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Category.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,43 @@ public interface Category extends GuildChannel
@CheckReturnValue
ChannelAction<VoiceChannel> createVoiceChannel(@Nonnull String name);

/**
* Creates a new {@link net.dv8tion.jda.api.entities.StageChannel VoiceChannel} with this Category as parent.
MinnDevelopment marked this conversation as resolved.
Show resolved Hide resolved
* 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}.
MinnDevelopment marked this conversation as resolved.
Show resolved Hide resolved
*
* <p>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.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
* the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
* <br>The channel could not be created due to a permission discrepancy</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_ACCESS MISSING_ACCESS}
* <br>The {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL VIEW_CHANNEL} permission was removed</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS}
* <br>The maximum number of channels were exceeded</li>
* </ul>
*
* @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}
* <br>This action allows to set fields for the new StageChannel before creating it
*/
@Nonnull
@CheckReturnValue
ChannelAction<StageChannel> createStageChannel(@Nonnull String name);

/**
* Modifies the positional order of this Category's nested {@link #getTextChannels() TextChannels} and {@link #getStoreChannels() StoreChannels}.
* <br>This uses an extension of {@link ChannelOrderAction ChannelOrderAction}
Expand Down
90 changes: 90 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Guild.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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}.
Expand Down Expand Up @@ -1147,6 +1148,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:
Expand All @@ -1155,6 +1158,29 @@ default GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id)
return null;
}

@Nonnull
default List<StageChannel> getStageChannelsByName(@Nonnull String name, boolean ignoreCase)
{
return getVoiceChannelsByName(name, ignoreCase)
.stream()
.filter(StageChannel.class::isInstance)
.map(StageChannel.class::cast)
.collect(Collectors.toList());
}

@Nullable
default StageChannel getStageChannelById(@Nonnull String id)
{
return getStageChannelById(MiscUtil.parseSnowflake(id));
}

@Nullable
default StageChannel getStageChannelById(long id)
{
VoiceChannel channel = getVoiceChannelById(id);
return channel instanceof StageChannel ? (StageChannel) channel : null;
}

/**
* 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
Expand Down Expand Up @@ -4613,6 +4639,70 @@ default ChannelAction<VoiceChannel> createVoiceChannel(@Nonnull String name)
@CheckReturnValue
ChannelAction<VoiceChannel> createVoiceChannel(@Nonnull String name, @Nullable Category parent);

/**
* Creates a new {@link net.dv8tion.jda.api.entities.StageChannel VoiceChannel} in this Guild.
MinnDevelopment marked this conversation as resolved.
Show resolved Hide resolved
* For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
* the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
* <br>The channel could not be created due to a permission discrepancy</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS}
* <br>The maximum number of channels were exceeded</li>
* </ul>
*
* @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}
* <br>This action allows to set fields for the new StageChannel before creating it
*/
@Nonnull
@CheckReturnValue
default ChannelAction<StageChannel> createStageChannel(@Nonnull String name)
{
return createStageChannel(name, null);
}

/**
* Creates a new {@link net.dv8tion.jda.api.entities.StageChannel VoiceChannel} in this Guild.
MinnDevelopment marked this conversation as resolved.
Show resolved Hide resolved
* For this to be successful, the logged in account has to have the {@link net.dv8tion.jda.api.Permission#MANAGE_CHANNEL MANAGE_CHANNEL} Permission.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
* the returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} include the following:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MISSING_PERMISSIONS MISSING_PERMISSIONS}
* <br>The channel could not be created due to a permission discrepancy</li>
*
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#MAX_CHANNELS MAX_CHANNELS}
* <br>The maximum number of channels were exceeded</li>
* </ul>
*
* @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}
* <br>This action allows to set fields for the new StageChannel before creating it
*/
@Nonnull
@CheckReturnValue
ChannelAction<StageChannel> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
MinnDevelopment marked this conversation as resolved.
Show resolved Hide resolved
*
* @return {@link VoiceChannel}
*
Expand All @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -539,9 +539,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ public ChannelAction<VoiceChannel> createVoiceChannel(@Nonnull String name)
return trySync(action);
}

@Nonnull
@Override
public ChannelAction<StageChannel> createStageChannel(@Nonnull String name)
{
ChannelAction<StageChannel> action = getGuild().createStageChannel(name, this);
return trySync(action);
}

private <T extends GuildChannel> ChannelAction<T> trySync(ChannelAction<T> action)
{
Member selfMember = getGuild().getSelfMember();
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,28 @@ public ChannelAction<VoiceChannel> createVoiceChannel(@Nonnull String name, Cate
return new ChannelActionImpl<>(VoiceChannel.class, name, this, ChannelType.VOICE).setParent(parent);
}

@Nonnull
@Override
public ChannelAction<StageChannel> 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.check(name.length() > 0 && name.length() <= 100, "Provided name must be 1 - 100 characters in length");
return new ChannelActionImpl<>(StageChannel.class, name, this, ChannelType.STAGE).setParent(parent);
}

@Nonnull
@Override
public ChannelAction<Category> createCategory(@Nonnull String name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ protected Long handleInternally(DataObject content)
builder.createTextChannel(content, guildId)));
break;
}
case STAGE:
case VOICE:
{
jda.handleEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ protected Long handleInternally(DataObject content)
channel));
break;
}
case STAGE:
case VOICE:
{
VoiceChannel channel = getJDA().getVoiceChannelsView().remove(channelId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -323,6 +324,7 @@ private void applyPermissions(AbstractChannelImpl<?,?> channel, DataArray permOv
api, responseNumber,
(StoreChannel) channel, changed));
break;
case STAGE:
case VOICE:
api.handleEvent(
new VoiceChannelUpdatePermissionsEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ public ChannelActionImpl<T> setPosition(Integer position)
@CheckReturnValue
public ChannelActionImpl<T> setTopic(String topic)
{
if (type != ChannelType.TEXT)
throw new UnsupportedOperationException("Can only set the topic for a TextChannel!");
if (type != ChannelType.TEXT && type != ChannelType.STAGE)
throw new UnsupportedOperationException("Can only set the topic for a TextChannel or StageChannel!");
if (topic != null && topic.length() > 1024)
throw new IllegalArgumentException("Channel Topic must not be greater than 1024 in length!");
this.topic = topic;
Expand Down Expand Up @@ -330,14 +330,15 @@ 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);
case STAGE:
MinnDevelopment marked this conversation as resolved.
Show resolved Hide resolved
if (topic != null && !topic.isEmpty())
object.put("topic", topic);
}
if (type != ChannelType.CATEGORY && parent != null)
object.put("parent_id", parent.getId());
Expand All @@ -352,6 +353,7 @@ protected void handleSuccess(Response response, Request<T> request)
GuildChannel channel;
switch (type)
{
case STAGE:
case VOICE:
channel = builder.createVoiceChannel(response.getObject(), guild.getIdLong());
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down