automaticallyDisabled = EnumSet.noneOf(CacheFlag.class);
- protected ScheduledExecutorService rateLimitPool = null;
- protected boolean shutdownRateLimitPool = true;
+ protected ScheduledExecutorService rateLimitScheduler = null;
+ protected boolean shutdownRateLimitScheduler = true;
+ protected ExecutorService rateLimitElastic = null;
+ protected boolean shutdownRateLimitElastic = true;
protected ScheduledExecutorService mainWsPool = null;
protected boolean shutdownMainWsPool = true;
protected ExecutorService callbackPool = null;
@@ -913,8 +915,13 @@ public JDABuilder setWebsocketFactory(@Nullable WebSocketFactory factory)
* The thread-pool to use for rate-limit handling
*
* @return The JDABuilder instance. Useful for chaining.
+ *
+ * @deprecated This pool is now split into two pools.
+ * You should use {@link #setRateLimitScheduler(ScheduledExecutorService)} and {@link #setRateLimitElastic(ExecutorService)} instead.
*/
@Nonnull
+ @Deprecated
+ @ReplaceWith("setRateLimitScheduler(pool)")
public JDABuilder setRateLimitPool(@Nullable ScheduledExecutorService pool)
{
return setRateLimitPool(pool, pool == null);
@@ -937,12 +944,113 @@ public JDABuilder setRateLimitPool(@Nullable ScheduledExecutorService pool)
* Whether {@link JDA#shutdown()} should shutdown this pool
*
* @return The JDABuilder instance. Useful for chaining.
+ *
+ * @deprecated This pool is now split into two pools.
+ * You should use {@link #setRateLimitScheduler(ScheduledExecutorService, boolean)} and {@link #setRateLimitElastic(ExecutorService, boolean)} instead.
*/
@Nonnull
+ @Deprecated
+ @ReplaceWith("setRateLimitScheduler(pool, automaticShutdown)")
public JDABuilder setRateLimitPool(@Nullable ScheduledExecutorService pool, boolean automaticShutdown)
{
- this.rateLimitPool = pool;
- this.shutdownRateLimitPool = automaticShutdown;
+ this.rateLimitScheduler = pool;
+ this.shutdownRateLimitScheduler = automaticShutdown;
+ return this;
+ }
+
+ /**
+ * Sets the {@link ScheduledExecutorService ScheduledExecutorService} that should be used in
+ * the JDA rate-limit handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ * This automatically disables the automatic shutdown of the rate-limit pool, you can enable
+ * it using {@link #setRateLimitPool(ScheduledExecutorService, boolean) setRateLimitPool(executor, true)}
+ *
+ * This is used mostly by the Rate-Limiter to handle backoff delays by using scheduled executions.
+ * Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
+ * and similar methods. Requests are handed off to the {@link #setRateLimitElastic(ExecutorService) elastic pool} for blocking execution.
+ *
+ *
Default: {@link ScheduledThreadPoolExecutor} with 2 threads.
+ *
+ * @param pool
+ * The thread-pool to use for rate-limit handling
+ *
+ * @return The JDABuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public JDABuilder setRateLimitScheduler(@Nullable ScheduledExecutorService pool)
+ {
+ return setRateLimitScheduler(pool, pool == null);
+ }
+
+ /**
+ * Sets the {@link ScheduledExecutorService ScheduledExecutorService} that should be used in
+ * the JDA rate-limit handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ *
+ *
This is used mostly by the Rate-Limiter to handle backoff delays by using scheduled executions.
+ * Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
+ * and similar methods. Requests are handed off to the {@link #setRateLimitElastic(ExecutorService) elastic pool} for blocking execution.
+ *
+ *
Default: {@link ScheduledThreadPoolExecutor} with 2 threads.
+ *
+ * @param pool
+ * The thread-pool to use for rate-limit handling
+ * @param automaticShutdown
+ * Whether {@link JDA#shutdown()} should shutdown this pool
+ *
+ * @return The JDABuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public JDABuilder setRateLimitScheduler(@Nullable ScheduledExecutorService pool, boolean automaticShutdown)
+ {
+ this.rateLimitScheduler = pool;
+ this.shutdownRateLimitScheduler = automaticShutdown;
+ return this;
+ }
+
+ /**
+ * Sets the {@link ExecutorService ExecutorService} that should be used in
+ * the JDA request handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ * This automatically disables the automatic shutdown of the rate-limit elastic pool, you can enable
+ * it using {@link #setRateLimitElastic(ExecutorService, boolean) setRateLimitElastic(executor, true)}
+ *
+ *
This is used mostly by the Rate-Limiter to execute the blocking HTTP requests at runtime.
+ *
+ *
Default: {@link Executors#newCachedThreadPool()}.
+ *
+ * @param pool
+ * The thread-pool to use for executing http requests
+ *
+ * @return The JDABuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public JDABuilder setRateLimitElastic(@Nullable ExecutorService pool)
+ {
+ return setRateLimitElastic(pool, pool == null);
+ }
+
+ /**
+ * Sets the {@link ExecutorService ExecutorService} that should be used in
+ * the JDA request handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ *
+ *
This is used mostly by the Rate-Limiter to execute the blocking HTTP requests at runtime.
+ *
+ *
Default: {@link Executors#newCachedThreadPool()}.
+ *
+ * @param pool
+ * The thread-pool to use for executing http requests
+ * @param automaticShutdown
+ * Whether {@link JDA#shutdown()} should shutdown this pool
+ *
+ * @return The JDABuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public JDABuilder setRateLimitElastic(@Nullable ExecutorService pool, boolean automaticShutdown)
+ {
+ this.rateLimitElastic = pool;
+ this.shutdownRateLimitElastic = automaticShutdown;
return this;
}
@@ -1797,7 +1905,8 @@ public JDA build()
ThreadingConfig threadingConfig = new ThreadingConfig();
threadingConfig.setCallbackPool(callbackPool, shutdownCallbackPool);
threadingConfig.setGatewayPool(mainWsPool, shutdownMainWsPool);
- threadingConfig.setRateLimitPool(rateLimitPool, shutdownRateLimitPool);
+ threadingConfig.setRateLimitScheduler(rateLimitScheduler, shutdownRateLimitScheduler);
+ threadingConfig.setRateLimitElastic(rateLimitElastic, shutdownRateLimitElastic);
threadingConfig.setEventPool(eventPool, shutdownEventPool);
threadingConfig.setAudioPool(audioPool, shutdownAudioPool);
SessionConfig sessionConfig = new SessionConfig(controller, httpClient, wsFactory, voiceDispatchInterceptor, flags, maxReconnectDelay, largeThreshold);
diff --git a/src/main/java/net/dv8tion/jda/api/Permission.java b/src/main/java/net/dv8tion/jda/api/Permission.java
index 313a4fbd3da..22e99f903d7 100644
--- a/src/main/java/net/dv8tion/jda/api/Permission.java
+++ b/src/main/java/net/dv8tion/jda/api/Permission.java
@@ -87,6 +87,7 @@ public enum Permission
VOICE_START_ACTIVITIES( 39, true, true, "Use Activities"),
VOICE_USE_SOUNDBOARD( 42, true, true, "Use Soundboard"),
VOICE_USE_EXTERNAL_SOUNDS(45, true, true, "Use External Sounds"),
+ VOICE_SET_STATUS( 48, true, true, "Set Voice Channel Status"),
// Stage Channel Permissions
REQUEST_TO_SPEAK( 32, true, true, "Request to Speak"),
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 32efb8933ed..ba216cc69c8 100644
--- a/src/main/java/net/dv8tion/jda/api/audit/ActionType.java
+++ b/src/main/java/net/dv8tion/jda/api/audit/ActionType.java
@@ -18,6 +18,7 @@
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.ScheduledEvent;
+import net.dv8tion.jda.api.entities.channel.attribute.IVoiceStatusChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji;
@@ -635,6 +636,27 @@ public enum ActionType
*/
AUTO_MODERATION_MEMBER_TIMEOUT( 145, TargetType.MEMBER),
+ /**
+ * A user updated the {@link IVoiceStatusChannel#getStatus() status} of a voice channel.
+ *
+ *
Possible Keys
+ *
+ * {@link AuditLogKey#CHANNEL_VOICE_STATUS CHANNEL_VOICE_STATUS}
+ * {@link AuditLogKey#CHANNEL_ID CHANNEL_ID}
+ *
+ */
+ VOICE_CHANNEL_STATUS_UPDATE(192, TargetType.CHANNEL),
+
+ /**
+ * A user removed the {@link IVoiceStatusChannel#getStatus() status} of a voice channel.
+ *
+ * Possible Keys
+ *
+ * {@link AuditLogKey#CHANNEL_ID CHANNEL_ID}
+ *
+ */
+ VOICE_CHANNEL_STATUS_DELETE(193, TargetType.CHANNEL),
+
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 e4e6831ed62..7b1522aac92 100644
--- a/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java
+++ b/src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java
@@ -240,6 +240,14 @@ public enum AuditLogKey
*/
CHANNEL_TOPIC("topic"),
+ /**
+ * Change of the {@link VoiceChannel#getStatus() VoiceChannel.getStatus()} value.
+ * Only for {@link ChannelType#VOICE ChannelType.VOICE}
+ *
+ * Expected type: String
+ */
+ CHANNEL_VOICE_STATUS("status"),
+
/**
* Change of the {@link ISlowmodeChannel#getSlowmode()} value.
*
diff --git a/src/main/java/net/dv8tion/jda/api/entities/ApplicationInfo.java b/src/main/java/net/dv8tion/jda/api/entities/ApplicationInfo.java
index 2b03ba1c309..46befd6c4c7 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/ApplicationInfo.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/ApplicationInfo.java
@@ -315,6 +315,39 @@ default String getInviteUrl(long guildId, @Nullable Permission... permissions)
@Nonnull
List getTags();
+ /**
+ * A {@link java.util.List} containing the OAuth2 redirect URIs of this bot's application.
+ *
+ * This List is empty if no redirect URIs are set in the Developer Portal .
+ *
+ * @return Immutable list containing the OAuth2 redirect URIs of this bot's application
+ */
+ @Nonnull
+ List getRedirectUris();
+
+ /**
+ * The interaction endpoint URL of this bot's application.
+ *
+ * This returns {@code null} if no interaction endpoint URL is set in the Developer Portal .
+ *
+ *
A non-null value means your bot will no longer receive {@link net.dv8tion.jda.api.interactions.Interaction interactions}
+ * through JDA, such as slash commands, components and modals.
+ *
+ * @return Interaction endpoint URL of this bot's application, or {@code null} if it has not been set
+ */
+ @Nullable
+ String getInteractionsEndpointUrl();
+
+ /**
+ * The role connections (linked roles) verification URL of this bot's application.
+ *
+ *
This returns {@code null} if no role connection verification URL is set in the Developer Portal .
+ *
+ * @return Role connections verification URL of this bot's application, or {@code null} if it has not been set
+ */
+ @Nullable
+ String getRoleConnectionsVerificationUrl();
+
/**
* The custom Authorization URL of this bot's application.
*
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 10af96368ba..c409d0ab6ee 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java
@@ -27,6 +27,7 @@
import net.dv8tion.jda.api.entities.automod.AutoModTriggerType;
import net.dv8tion.jda.api.entities.automod.build.AutoModRuleData;
import net.dv8tion.jda.api.entities.channel.Channel;
+import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.attribute.ICopyableChannel;
import net.dv8tion.jda.api.entities.channel.attribute.IGuildChannelContainer;
import net.dv8tion.jda.api.entities.channel.attribute.IInviteContainer;
@@ -59,10 +60,7 @@
import net.dv8tion.jda.api.utils.FileUpload;
import net.dv8tion.jda.api.utils.ImageProxy;
import net.dv8tion.jda.api.utils.MiscUtil;
-import net.dv8tion.jda.api.utils.cache.CacheFlag;
-import net.dv8tion.jda.api.utils.cache.MemberCacheView;
-import net.dv8tion.jda.api.utils.cache.SnowflakeCacheView;
-import net.dv8tion.jda.api.utils.cache.SortedSnowflakeCacheView;
+import net.dv8tion.jda.api.utils.cache.*;
import net.dv8tion.jda.api.utils.concurrent.Task;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import net.dv8tion.jda.internal.requests.DeferredRestAction;
@@ -93,7 +91,7 @@
* @see JDA#getGuildsByName(String, boolean)
* @see JDA#getGuilds()
*/
-public interface Guild extends IGuildChannelContainer, ISnowflake
+public interface Guild extends IGuildChannelContainer, ISnowflake
{
/** Template for {@link #getIconUrl()}. */
String ICON_URL = "https://cdn.discordapp.com/icons/%s/%s.%s";
@@ -906,7 +904,7 @@ default ImageProxy getBanner()
* This will only check cached members!
* See {@link net.dv8tion.jda.api.utils.MemberCachePolicy MemberCachePolicy}
*
- * @return Possibly-immutable list of members who boost this guild
+ * @return Immutable list of members who boost this guild
*/
@Nonnull
List getBoosters();
@@ -1561,6 +1559,23 @@ default List getScheduledEvents()
@Override
SortedSnowflakeCacheView getForumChannelCache();
+ /**
+ * {@link SortedChannelCacheView SortedChannelCacheView} of {@link GuildChannel}.
+ *
+ * Provides cache access to all channels of this guild, including thread channels (unlike {@link #getChannels()}).
+ * The cache view attempts to provide a sorted list, based on how channels are displayed in the client.
+ * Various methods like {@link SortedChannelCacheView#forEachUnordered(Consumer)} or {@link SortedChannelCacheView#lockedIterator()}
+ * bypass sorting for optimization reasons.
+ *
+ *
It is possible to filter the channels to more specific types using
+ * {@link ChannelCacheView#getElementById(ChannelType, long)} or {@link SortedChannelCacheView#ofType(Class)}.
+ *
+ * @return {@link SortedChannelCacheView SortedChannelCacheView}
+ */
+ @Nonnull
+ @Override
+ SortedChannelCacheView getChannelCache();
+
/**
* Populated list of {@link GuildChannel channels} for this guild.
* This includes all types of channels, except for threads.
diff --git a/src/main/java/net/dv8tion/jda/api/entities/Message.java b/src/main/java/net/dv8tion/jda/api/entities/Message.java
index b0062f494fe..d10b6cdd8d4 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/Message.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/Message.java
@@ -60,6 +60,7 @@
import net.dv8tion.jda.internal.entities.ReceivedMessage;
import net.dv8tion.jda.internal.requests.FunctionalCallback;
import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.IOUtil;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
@@ -698,7 +699,7 @@ default List getActionRows()
.stream()
.filter(ActionRow.class::isInstance)
.map(ActionRow.class::cast)
- .collect(Collectors.toList());
+ .collect(Helpers.toUnmodifiableList());
}
/**
@@ -714,7 +715,7 @@ default List getButtons()
return getComponents().stream()
.map(LayoutComponent::getButtons)
.flatMap(List::stream)
- .collect(Collectors.toList());
+ .collect(Helpers.toUnmodifiableList());
}
/**
@@ -765,7 +766,7 @@ default List getButtonsByLabel(@Nonnull String label, boolean ignoreCase
filter = b -> label.equals(b.getLabel());
return getButtons().stream()
.filter(filter)
- .collect(Collectors.toList());
+ .collect(Helpers.toUnmodifiableList());
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/entities/MessageEmbed.java b/src/main/java/net/dv8tion/jda/api/entities/MessageEmbed.java
index b54a2709652..d6771470591 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/MessageEmbed.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/MessageEmbed.java
@@ -115,6 +115,13 @@ public class MessageEmbed implements SerializableData
@ForRemoval
public static final int EMBED_MAX_LENGTH_CLIENT = 2000;
+ /**
+ * The maximum amount of total embed fields the embed can hold
+ *
+ * @see net.dv8tion.jda.api.EmbedBuilder#addField(String, String, boolean)
+ */
+ public static final int MAX_FIELD_AMOUNT = 25;
+
protected final Object mutex = new Object();
protected final String url;
diff --git a/src/main/java/net/dv8tion/jda/api/entities/MessageType.java b/src/main/java/net/dv8tion/jda/api/entities/MessageType.java
index 5e6bafc4158..0a462d5c460 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/MessageType.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/MessageType.java
@@ -195,6 +195,45 @@ public enum MessageType
*/
GUILD_APPLICATION_PREMIUM_SUBSCRIPTION(32, true, true),
+// /**
+// * Sent when an application is added as integration to a private channel or group channel.
+// */
+// PRIVATE_CHANNEL_INTEGRATION_ADDED(33, true, true),
+//
+// /**
+// * Sent when an application integration is removed from a private channel or group channel.
+// */
+// PRIVATE_CHANNEL_INTEGRATION_REMOVED(34, true, true),
+
+// /**
+// * Unclear what this is for or if its used at all
+// */
+// PREMIUM_REFERRAL(35, true, true),
+
+ /**
+ * Sent when a moderator activates a temporary security measure, such as pausing invites or direct messages.
+ * The message content is an ISO 8601 timestamp, which indicates when the action expires and disables the security measures automatically.
+ *
+ * @see java.time.OffsetDateTime#parse(CharSequence)
+ */
+ GUILD_INCIDENT_ALERT_MODE_ENABLED(36, true, false),
+
+ /**
+ * Sent when a moderator deactivates a temporary security measure, such as pausing invites or direct messages.
+ */
+ GUILD_INCIDENT_ALERT_MODE_DISABLED(37, true, false),
+
+ /**
+ * Sent when a moderator reports a raid in a guild.
+ * The message author is the reporter.
+ */
+ GUILD_INCIDENT_REPORT_RAID(38, true, false),
+
+ /**
+ * Sent when a moderator reports a raid as a false alarm in a guild.
+ */
+ GUILD_INCIDENT_REPORT_FALSE_ALARM(39, true, false),
+
/**
* Unknown MessageType.
*/
diff --git a/src/main/java/net/dv8tion/jda/api/entities/StageInstance.java b/src/main/java/net/dv8tion/jda/api/entities/StageInstance.java
index 958ac4fa922..d3fb7aea852 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/StageInstance.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/StageInstance.java
@@ -21,12 +21,11 @@
import net.dv8tion.jda.api.entities.channel.concrete.StageChannel;
import net.dv8tion.jda.api.managers.StageInstanceManager;
import net.dv8tion.jda.api.requests.RestAction;
+import net.dv8tion.jda.internal.utils.Helpers;
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.
@@ -77,15 +76,15 @@ public interface StageInstance extends ISnowflake
* 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
+ * @return Immutable {@link List} of {@link Member Members} which can speak in this stage instance
*/
@Nonnull
default List getSpeakers()
{
- return Collections.unmodifiableList(getChannel().getMembers()
+ return 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()));
+ .collect(Helpers.toUnmodifiableList());
}
/**
@@ -98,15 +97,15 @@ default List getSpeakers()
* 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
+ * @return Immutable {@link List} of {@link Member Members} which cannot speak in this stage instance
*/
@Nonnull
default List getAudience()
{
- return Collections.unmodifiableList(getChannel().getMembers()
+ return 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()));
+ .collect(Helpers.toUnmodifiableList());
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java
index 681c1d15a05..4b069e77f7e 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/ChannelField.java
@@ -168,6 +168,14 @@ public enum ChannelField
* @see VoiceChannel#getUserLimit()
*/
USER_LIMIT("userlimit", AuditLogKey.CHANNEL_USER_LIMIT),
+ /**
+ * The status of the channel.
+ *
+ * Limited to {@link VoiceChannel Voice Channels}.
+ *
+ * @see VoiceChannel#getStatus()
+ */
+ VOICE_STATUS("status", AuditLogKey.CHANNEL_VOICE_STATUS),
//Thread Specific
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/ICategorizableChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/ICategorizableChannel.java
index 80e42151b65..d890949a789 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/ICategorizableChannel.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/ICategorizableChannel.java
@@ -31,12 +31,24 @@
* @see Category
* @see net.dv8tion.jda.api.entities.Guild#getCategories()
*/
-public interface ICategorizableChannel extends GuildChannel, IPermissionContainer
+public interface ICategorizableChannel extends GuildChannel, IPermissionContainer, IPositionableChannel
{
@Override
@Nonnull
ICategorizableChannelManager, ?> getManager();
+ /**
+ * Computes the relative position of this channel in the {@link #getParentCategory() parent category}.
+ * This is effectively the same as {@code getParentCategory().getChannels().indexOf(channel)}.
+ *
+ * @return The relative position in the parent category, or {@code -1} if no parent is set
+ */
+ default int getPositionInCategory()
+ {
+ Category parent = getParentCategory();
+ return parent == null ? -1 : parent.getChannels().indexOf(this);
+ }
+
/**
* Get the snowflake of the {@link Category} that contains this channel.
*
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java
index ec5a15e0c86..1e16fd7b731 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IGuildChannelContainer.java
@@ -25,6 +25,7 @@
import net.dv8tion.jda.api.sharding.ShardManager;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.cache.CacheView;
+import net.dv8tion.jda.api.utils.cache.ChannelCacheView;
import net.dv8tion.jda.api.utils.cache.SnowflakeCacheView;
import net.dv8tion.jda.internal.utils.Checks;
@@ -45,8 +46,20 @@
*
For the most efficient usage, it is recommended to use {@link CacheView} getters such as {@link #getTextChannelCache()}.
* List getters usually require making a snapshot copy of the underlying cache view, which may introduce an undesirable performance hit.
*/
-public interface IGuildChannelContainer
+public interface IGuildChannelContainer
{
+ /**
+ * Unified cache of all channels associated with this shard or guild.
+ *
+ * This {@link ChannelCacheView} stores all channels in individually typed maps based on {@link ChannelType}.
+ * You can use {@link ChannelCacheView#getElementById(ChannelType, long)} or {@link ChannelCacheView#ofType(Class)} to filter
+ * out more specific types.
+ *
+ * @return {@link ChannelCacheView}
+ */
+ @Nonnull
+ ChannelCacheView getChannelCache();
+
/**
* Get a channel of the specified type by id.
*
@@ -67,7 +80,7 @@ public interface IGuildChannelContainer
* @return The casted channel, if it exists and is assignable to the provided class, or null
*/
@Nullable
- default T getChannelById(@Nonnull Class type, @Nonnull String id)
+ default T getChannelById(@Nonnull Class type, @Nonnull String id)
{
return getChannelById(type, MiscUtil.parseSnowflake(id));
}
@@ -92,11 +105,10 @@ default T getChannelById(@Nonnull Class type, @Nonnull St
* @return The casted channel, if it exists and is assignable to the provided class, or null
*/
@Nullable
- default T getChannelById(@Nonnull Class type, long id)
+ default T getChannelById(@Nonnull Class type, long id)
{
Checks.notNull(type, "Class");
- GuildChannel channel = getGuildChannelById(id);
- return type.isInstance(channel) ? type.cast(channel) : null;
+ return getChannelCache().ofType(type).getElementById(id);
}
/**
@@ -164,24 +176,8 @@ default GuildChannel getGuildChannelById(@Nonnull String id)
@Nullable
default GuildChannel getGuildChannelById(long id)
{
- //TODO-v5-unified-channel-cache
- GuildChannel channel = getTextChannelById(id);
- if (channel == null)
- channel = getNewsChannelById(id);
- if (channel == null)
- channel = getVoiceChannelById(id);
- if (channel == null)
- channel = getStageChannelById(id);
- if (channel == null)
- channel = getCategoryById(id);
- if (channel == null)
- channel = getThreadChannelById(id);
- if (channel == null)
- channel = getForumChannelById(id);
- if (channel == null)
- channel = getMediaChannelById(id);
-
- return channel;
+ C channel = getChannelCache().getElementById(id);
+ return channel instanceof GuildChannel ? (GuildChannel) channel : null;
}
/**
@@ -260,29 +256,8 @@ default GuildChannel getGuildChannelById(@Nonnull ChannelType type, @Nonnull Str
@Nullable
default GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id)
{
- Checks.notNull(type, "ChannelType");
- switch (type)
- {
- case NEWS:
- return getNewsChannelById(id);
- case TEXT:
- return getTextChannelById(id);
- case VOICE:
- return getVoiceChannelById(id);
- case STAGE:
- return getStageChannelById(id);
- case CATEGORY:
- return getCategoryById(id);
- case FORUM:
- return getForumChannelById(id);
- case MEDIA:
- return getMediaChannelById(id);
- }
-
- if (type.isThread())
- return getThreadChannelById(id);
-
- return null;
+ C channel = getChannelCache().getElementById(type, id);
+ return channel instanceof GuildChannel ? (GuildChannel) channel : null;
}
@@ -352,7 +327,7 @@ default List getStageChannelsByName(@Nonnull String name, boolean
@Nullable
default StageChannel getStageChannelById(@Nonnull String id)
{
- return getStageChannelCache().getElementById(id);
+ return (StageChannel) getChannelCache().getElementById(ChannelType.STAGE, id);
}
/**
@@ -374,7 +349,7 @@ default StageChannel getStageChannelById(@Nonnull String id)
@Nullable
default StageChannel getStageChannelById(long id)
{
- return getStageChannelCache().getElementById(id);
+ return (StageChannel) getChannelCache().getElementById(ChannelType.STAGE, id);
}
/**
@@ -473,7 +448,7 @@ default List getThreadChannelsByName(@Nonnull String name, boolea
@Nullable
default ThreadChannel getThreadChannelById(@Nonnull String id)
{
- return getThreadChannelCache().getElementById(id);
+ return (ThreadChannel) getChannelCache().getElementById(ChannelType.GUILD_PUBLIC_THREAD, id);
}
/**
@@ -497,7 +472,7 @@ default ThreadChannel getThreadChannelById(@Nonnull String id)
@Nullable
default ThreadChannel getThreadChannelById(long id)
{
- return getThreadChannelCache().getElementById(id);
+ return (ThreadChannel) getChannelCache().getElementById(ChannelType.GUILD_PUBLIC_THREAD, id);
}
/**
@@ -595,7 +570,7 @@ default List getCategoriesByName(@Nonnull String name, boolean ignoreC
@Nullable
default Category getCategoryById(@Nonnull String id)
{
- return getCategoryCache().getElementById(id);
+ return (Category) getChannelCache().getElementById(ChannelType.CATEGORY, id);
}
/**
@@ -617,7 +592,7 @@ default Category getCategoryById(@Nonnull String id)
@Nullable
default Category getCategoryById(long id)
{
- return getCategoryCache().getElementById(id);
+ return (Category) getChannelCache().getElementById(ChannelType.CATEGORY, id);
}
/**
@@ -711,7 +686,7 @@ default List getTextChannelsByName(@Nonnull String name, boolean ig
@Nullable
default TextChannel getTextChannelById(@Nonnull String id)
{
- return getTextChannelCache().getElementById(id);
+ return (TextChannel) getChannelCache().getElementById(ChannelType.TEXT, id);
}
/**
@@ -733,7 +708,7 @@ default TextChannel getTextChannelById(@Nonnull String id)
@Nullable
default TextChannel getTextChannelById(long id)
{
- return getTextChannelCache().getElementById(id);
+ return (TextChannel) getChannelCache().getElementById(ChannelType.TEXT, id);
}
/**
@@ -827,7 +802,7 @@ default List getNewsChannelsByName(@Nonnull String name, boolean ig
@Nullable
default NewsChannel getNewsChannelById(@Nonnull String id)
{
- return getNewsChannelCache().getElementById(id);
+ return (NewsChannel) getChannelCache().getElementById(ChannelType.NEWS, id);
}
/**
@@ -849,7 +824,7 @@ default NewsChannel getNewsChannelById(@Nonnull String id)
@Nullable
default NewsChannel getNewsChannelById(long id)
{
- return getNewsChannelCache().getElementById(id);
+ return (NewsChannel) getChannelCache().getElementById(ChannelType.NEWS, id);
}
/**
@@ -943,7 +918,7 @@ default List getVoiceChannelsByName(@Nonnull String name, boolean
@Nullable
default VoiceChannel getVoiceChannelById(@Nonnull String id)
{
- return getVoiceChannelCache().getElementById(id);
+ return (VoiceChannel) getChannelCache().getElementById(ChannelType.VOICE, id);
}
/**
@@ -965,7 +940,7 @@ default VoiceChannel getVoiceChannelById(@Nonnull String id)
@Nullable
default VoiceChannel getVoiceChannelById(long id)
{
- return getVoiceChannelCache().getElementById(id);
+ return (VoiceChannel) getChannelCache().getElementById(ChannelType.VOICE, id);
}
/**
@@ -1058,7 +1033,7 @@ default List getForumChannelsByName(@Nonnull String name, boolean
@Nullable
default ForumChannel getForumChannelById(@Nonnull String id)
{
- return getForumChannelCache().getElementById(id);
+ return (ForumChannel) getChannelCache().getElementById(ChannelType.FORUM, id);
}
/**
@@ -1080,7 +1055,7 @@ default ForumChannel getForumChannelById(@Nonnull String id)
@Nullable
default ForumChannel getForumChannelById(long id)
{
- return getForumChannelCache().getElementById(id);
+ return (ForumChannel) getChannelCache().getElementById(ChannelType.FORUM, id);
}
/**
@@ -1172,7 +1147,7 @@ default List getMediaChannelsByName(@Nonnull String name, boolean
@Nullable
default MediaChannel getMediaChannelById(@Nonnull String id)
{
- return getMediaChannelCache().getElementById(id);
+ return (MediaChannel) getChannelCache().getElementById(ChannelType.MEDIA, id);
}
/**
@@ -1194,7 +1169,7 @@ default MediaChannel getMediaChannelById(@Nonnull String id)
@Nullable
default MediaChannel getMediaChannelById(long id)
{
- return getMediaChannelCache().getElementById(id);
+ return (MediaChannel) getChannelCache().getElementById(ChannelType.MEDIA, id);
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPermissionContainer.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPermissionContainer.java
index f0c09208388..fcd83467cf6 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPermissionContainer.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPermissionContainer.java
@@ -21,13 +21,12 @@
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.managers.channel.attribute.IPermissionContainerManager;
import net.dv8tion.jda.api.requests.restaction.PermissionOverrideAction;
+import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Represents a {@link GuildChannel} that uses {@link net.dv8tion.jda.api.entities.PermissionOverride Permission Overrides}.
@@ -89,9 +88,9 @@ public interface IPermissionContainer extends GuildChannel
@Nonnull
default List getMemberPermissionOverrides()
{
- return Collections.unmodifiableList(getPermissionOverrides().stream()
+ return getPermissionOverrides().stream()
.filter(PermissionOverride::isMemberOverride)
- .collect(Collectors.toList()));
+ .collect(Helpers.toUnmodifiableList());
}
/**
@@ -105,9 +104,9 @@ default List getMemberPermissionOverrides()
@Nonnull
default List getRolePermissionOverrides()
{
- return Collections.unmodifiableList(getPermissionOverrides().stream()
+ return getPermissionOverrides().stream()
.filter(PermissionOverride::isRoleOverride)
- .collect(Collectors.toList()));
+ .collect(Helpers.toUnmodifiableList());
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPositionableChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPositionableChannel.java
index 03d50a898b1..e7d5936f652 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPositionableChannel.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IPositionableChannel.java
@@ -34,8 +34,6 @@ public interface IPositionableChannel extends GuildChannel
@Nonnull
IPositionableChannelManager, ?> getManager();
- //TODO-v5: We should probably introduce getPositionInCategory (name pending) that returns index in Category#getChannels or -1
-
/**
* The position of this channel in the channel list of the guild.
* This does not account for thread channels, as they do not have positions.
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java
index ff9db5180d4..e2e1c6cfadd 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IThreadContainer.java
@@ -26,12 +26,11 @@
import net.dv8tion.jda.api.requests.restaction.pagination.ThreadChannelPaginationAction;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
-import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Abstraction of all channel types, which can contain or manage {@link ThreadChannel ThreadChannels}.
@@ -59,11 +58,10 @@ public interface IThreadContainer extends GuildChannel, IPermissionContainer
@Nonnull
default List getThreadChannels()
{
- return Collections.unmodifiableList(
- getGuild().getThreadChannelCache().applyStream(stream ->
- stream.filter(thread -> thread.getParentChannel() == this)
- .collect(Collectors.toList())
- ));
+ return getGuild().getThreadChannelCache().applyStream(stream ->
+ stream.filter(thread -> thread.getParentChannel() == this)
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IVoiceStatusChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IVoiceStatusChannel.java
new file mode 100644
index 00000000000..47195b4eb47
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/attribute/IVoiceStatusChannel.java
@@ -0,0 +1,68 @@
+/*
+ * 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.channel.attribute;
+
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.channel.Channel;
+import net.dv8tion.jda.api.requests.restaction.AuditableRestAction;
+
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nonnull;
+
+/**
+ * Channel with a modifiable voice status.
+ * This can be used to indicate what is going on to people outside the channel.
+ */
+public interface IVoiceStatusChannel extends Channel
+{
+ /** The maximum length of a voice status {@value} */
+ int MAX_STATUS_LENGTH = 500;
+
+ /**
+ * The current voice channel status.
+ * This can be configured by users who are connected
+ * and have the {@link net.dv8tion.jda.api.Permission#VOICE_SET_STATUS set voice channel status} permission.
+ *
+ * @return The current voice channel status, or empty string if unset
+ */
+ @Nonnull
+ String getStatus();
+
+ /**
+ * Change the current voice channel status.
+ * This can be configured by users who are connected
+ * and have the {@link net.dv8tion.jda.api.Permission#VOICE_SET_STATUS set voice channel status} permission.
+ *
+ * @param status
+ * The new status, or empty to unset
+ *
+ * @throws IllegalArgumentException
+ * If the status is null or longer than {@value #MAX_STATUS_LENGTH} characters
+ * @throws net.dv8tion.jda.api.exceptions.MissingAccessException
+ * If the currently logged in account does not have {@link Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL} in this channel
+ * @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
+ *
+ * If the currently logged in account is not connected and does not have the {@link Permission#MANAGE_CHANNEL MANAGE_CHANNEL} permission.
+ * If the currently logged in account is connected and does not have the {@link Permission#VOICE_SET_STATUS VOICE_SET_STATUS} permission.
+ *
+ *
+ * @return {@link AuditableRestAction}
+ */
+ @Nonnull
+ @CheckReturnValue
+ AuditableRestAction modifyStatus(@Nonnull String status);
+}
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java
index 5ff2369865f..790e8c8f6cc 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/Category.java
@@ -21,30 +21,23 @@
import net.dv8tion.jda.api.entities.IPermissionHolder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.Channel;
-import net.dv8tion.jda.api.entities.channel.attribute.ICopyableChannel;
-import net.dv8tion.jda.api.entities.channel.attribute.IMemberContainer;
-import net.dv8tion.jda.api.entities.channel.attribute.IPermissionContainer;
-import net.dv8tion.jda.api.entities.channel.attribute.IPositionableChannel;
+import net.dv8tion.jda.api.entities.channel.attribute.*;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.managers.channel.concrete.CategoryManager;
import net.dv8tion.jda.api.requests.restaction.ChannelAction;
import net.dv8tion.jda.api.requests.restaction.order.CategoryOrderAction;
import net.dv8tion.jda.api.requests.restaction.order.ChannelOrderAction;
import net.dv8tion.jda.api.requests.restaction.order.OrderAction;
+import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Represents a channel category in the official Discord API.
* Categories are used to keep order in a Guild by dividing the channels into groups.
*
- * @since 3.4.0
- *
* @see Guild#getCategoryCache()
* @see Guild#getCategories()
* @see Guild#getCategoriesByName(String, boolean)
@@ -66,16 +59,14 @@ public interface Category extends GuildChannel, ICopyableChannel, IPositionableC
@Nonnull
default List getChannels()
{
- List channels = new ArrayList<>();
- channels.addAll(getTextChannels());
- channels.addAll(getVoiceChannels());
- channels.addAll(getStageChannels());
- channels.addAll(getNewsChannels());
- channels.addAll(getForumChannels());
- channels.addAll(getMediaChannels());
- Collections.sort(channels);
-
- return Collections.unmodifiableList(channels);
+ return getGuild()
+ .getChannelCache()
+ .ofType(ICategorizableChannel.class)
+ .applyStream(stream -> stream
+ .filter(it -> this.equals(it.getParentCategory()))
+ .sorted()
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
@@ -87,11 +78,11 @@ default List getChannels()
@Nonnull
default List getTextChannels()
{
- return Collections.unmodifiableList(getGuild().getTextChannelCache().applyStream(stream ->
+ return getGuild().getTextChannelCache().applyStream(stream ->
stream.filter(channel -> equals(channel.getParentCategory()))
.sorted()
- .collect(Collectors.toList())
- ));
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
@@ -103,11 +94,11 @@ default List getTextChannels()
@Nonnull
default List getNewsChannels()
{
- return Collections.unmodifiableList(getGuild().getNewsChannelCache().applyStream(stream ->
+ return getGuild().getNewsChannelCache().applyStream(stream ->
stream.filter(channel -> equals(channel.getParentCategory()))
.sorted()
- .collect(Collectors.toList())
- ));
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
@@ -118,11 +109,11 @@ default List getNewsChannels()
@Nonnull
default List getForumChannels()
{
- return Collections.unmodifiableList(getGuild().getForumChannelCache().applyStream(stream ->
+ return getGuild().getForumChannelCache().applyStream(stream ->
stream.filter(channel -> equals(channel.getParentCategory()))
.sorted()
- .collect(Collectors.toList())
- ));
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
@@ -133,11 +124,11 @@ default List getForumChannels()
@Nonnull
default List getMediaChannels()
{
- return Collections.unmodifiableList(getGuild().getMediaChannelCache().applyStream(stream ->
+ return getGuild().getMediaChannelCache().applyStream(stream ->
stream.filter(channel -> equals(channel.getParentCategory()))
.sorted()
- .collect(Collectors.toList())
- ));
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
@@ -149,11 +140,11 @@ default List getMediaChannels()
@Nonnull
default List getVoiceChannels()
{
- return Collections.unmodifiableList(getGuild().getVoiceChannelCache().applyStream(stream ->
+ return getGuild().getVoiceChannelCache().applyStream(stream ->
stream.filter(channel -> equals(channel.getParentCategory()))
.sorted()
- .collect(Collectors.toList())
- ));
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
@@ -165,11 +156,11 @@ default List getVoiceChannels()
@Nonnull
default List getStageChannels()
{
- return Collections.unmodifiableList(getGuild().getStageChannelCache().applyStream(stream ->
+ return getGuild().getStageChannelCache().applyStream(stream ->
stream.filter(channel -> equals(channel.getParentCategory()))
.sorted()
- .collect(Collectors.toList())
- ));
+ .collect(Helpers.toUnmodifiableList())
+ );
}
/**
@@ -454,13 +445,13 @@ default List getStageChannels()
@Override
default List getMembers()
{
- return Collections.unmodifiableList(getChannels().stream()
+ return getChannels().stream()
.filter(IMemberContainer.class::isInstance)
.map(IMemberContainer.class::cast)
.map(IMemberContainer::getMembers)
.flatMap(List::stream)
.distinct()
- .collect(Collectors.toList()));
+ .collect(Helpers.toUnmodifiableList());
}
@Nonnull
diff --git a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/VoiceChannel.java b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/VoiceChannel.java
index 7c18c463b5d..d1c957c62c9 100644
--- a/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/VoiceChannel.java
+++ b/src/main/java/net/dv8tion/jda/api/entities/channel/concrete/VoiceChannel.java
@@ -19,6 +19,7 @@
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel;
import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel;
+import net.dv8tion.jda.api.entities.channel.attribute.IVoiceStatusChannel;
import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer;
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
@@ -47,7 +48,7 @@
* @see JDA#getVoiceChannelsByName(String, boolean)
* @see JDA#getVoiceChannelById(long)
*/
-public interface VoiceChannel extends StandardGuildChannel, GuildMessageChannel, AudioChannel, IWebhookContainer, IAgeRestrictedChannel, ISlowmodeChannel
+public interface VoiceChannel extends StandardGuildChannel, GuildMessageChannel, AudioChannel, IWebhookContainer, IAgeRestrictedChannel, ISlowmodeChannel, IVoiceStatusChannel
{
/**
* The maximum limit you can set with {@link VoiceChannelManager#setUserLimit(int)}. ({@value})
diff --git a/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateVoiceStatusEvent.java b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateVoiceStatusEvent.java
new file mode 100644
index 00000000000..de172cb414e
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/events/channel/update/ChannelUpdateVoiceStatusEvent.java
@@ -0,0 +1,44 @@
+/*
+ * 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.channel.update;
+
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.entities.channel.Channel;
+import net.dv8tion.jda.api.entities.channel.ChannelField;
+import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Indicates that a {@link Channel Channel's} voice channel status has been updated.
+ *
+ * Can be used to retrieve the old status and the new one.
+ *
+ *
Limited to {@link VoiceChannel VoiceChannels}.
+ *
+ * @see VoiceChannel#getStatus()
+ */
+public class ChannelUpdateVoiceStatusEvent extends GenericChannelUpdateEvent
+{
+ public static final ChannelField FIELD = ChannelField.VOICE_STATUS;
+ public static final String IDENTIFIER = FIELD.getFieldName();
+
+ public ChannelUpdateVoiceStatusEvent(@Nonnull JDA api, long responseNumber, Channel channel, String oldValue, String newValue)
+ {
+ super(api, responseNumber, channel, FIELD, oldValue, newValue);
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/exceptions/InvalidTokenException.java b/src/main/java/net/dv8tion/jda/api/exceptions/InvalidTokenException.java
index af96b95a336..573cd151f47 100644
--- a/src/main/java/net/dv8tion/jda/api/exceptions/InvalidTokenException.java
+++ b/src/main/java/net/dv8tion/jda/api/exceptions/InvalidTokenException.java
@@ -26,18 +26,6 @@ public class InvalidTokenException extends RuntimeException
*/
public InvalidTokenException()
{
- super();
+ super("The provided token is invalid!");
}
-
- /**
- * Constructs an {@code InvalidTokenException} with the specified detail message.
- *
- * @param message
- * The detail message.
- */
- public InvalidTokenException(String message)
- {
- super(message);
- }
-
}
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 aa26bce27cc..3df7247bae9 100644
--- a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java
+++ b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java
@@ -212,6 +212,7 @@ public void onChannelUpdateDefaultReaction(@Nonnull ChannelUpdateDefaultReaction
public void onChannelUpdateDefaultSortOrder(@Nonnull ChannelUpdateDefaultSortOrderEvent event) {}
public void onChannelUpdateDefaultLayout(@Nonnull ChannelUpdateDefaultLayoutEvent event) {}
public void onChannelUpdateTopic(@Nonnull ChannelUpdateTopicEvent event) {}
+ public void onChannelUpdateVoiceStatus(@Nonnull ChannelUpdateVoiceStatusEvent event) {}
public void onChannelUpdateType(@Nonnull ChannelUpdateTypeEvent event) {}
public void onChannelUpdateUserLimit(@Nonnull ChannelUpdateUserLimitEvent event) {}
public void onChannelUpdateArchived(@Nonnull ChannelUpdateArchivedEvent event) {}
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java b/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java
index 69fd15f0e92..b68752ac39e 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/components/LayoutComponent.java
@@ -20,6 +20,7 @@
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.utils.data.SerializableData;
import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
@@ -74,7 +75,7 @@ default List getActionComponents()
return getComponents().stream()
.filter(ActionComponent.class::isInstance)
.map(ActionComponent.class::cast)
- .collect(Collectors.toList());
+ .collect(Helpers.toUnmodifiableList());
}
/**
@@ -85,11 +86,10 @@ default List getActionComponents()
@Nonnull
default List getButtons()
{
- return Collections.unmodifiableList(
- getComponents().stream()
+ return getComponents().stream()
.filter(Button.class::isInstance)
.map(Button.class::cast)
- .collect(Collectors.toList()));
+ .collect(Helpers.toUnmodifiableList());
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/components/selections/StringSelectInteraction.java b/src/main/java/net/dv8tion/jda/api/interactions/components/selections/StringSelectInteraction.java
index d85bbab88cf..82e3c6e5bc3 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/components/selections/StringSelectInteraction.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/components/selections/StringSelectInteraction.java
@@ -17,10 +17,10 @@
package net.dv8tion.jda.api.interactions.components.selections;
import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;
+import net.dv8tion.jda.internal.utils.Helpers;
import javax.annotation.Nonnull;
import java.util.List;
-import java.util.stream.Collectors;
/**
* Component Interaction for a {@link StringSelectMenu}.
@@ -42,7 +42,7 @@ public interface StringSelectInteraction extends SelectMenuInteractionIt is recommended to check {@link #getValues()} directly instead of using the options.
*
- * @return {@link List} of the selected options
+ * @return Immutable {@link List} of the selected options
*/
@Nonnull
default List getSelectedOptions()
@@ -52,6 +52,6 @@ default List getSelectedOptions()
return menu.getOptions()
.stream()
.filter(it -> values.contains(it.getValue()))
- .collect(Collectors.toList());
+ .collect(Helpers.toUnmodifiableList());
}
}
diff --git a/src/main/java/net/dv8tion/jda/api/interactions/components/text/TextInput.java b/src/main/java/net/dv8tion/jda/api/interactions/components/text/TextInput.java
index ae56e51a63a..ad752a3954f 100644
--- a/src/main/java/net/dv8tion/jda/api/interactions/components/text/TextInput.java
+++ b/src/main/java/net/dv8tion/jda/api/interactions/components/text/TextInput.java
@@ -281,21 +281,25 @@ public Builder setRequired(boolean required)
/**
* Sets the minimum length of this input field. Default is -1 (No minimum length).
*
- * This has to be between 0 and {@value #MAX_VALUE_LENGTH}
+ *
This has to be between 0 and {@value #MAX_VALUE_LENGTH}, or -1 for no minimum length
*
* @param minLength
- * The minimum amount of characters that need to be written
+ * The minimum amount of characters that need to be written, or -1
*
* @throws IllegalArgumentException
- * If minLength is negative or greater than {@value #MAX_VALUE_LENGTH}
+ * If minLength is not -1 and is negative or greater than {@value #MAX_VALUE_LENGTH}
*
* @return The same builder instance for chaining
*/
@Nonnull
public Builder setMinLength(int minLength)
{
- Checks.notNegative(minLength, "Minimum length");
- Checks.check(minLength <= MAX_VALUE_LENGTH, "Minimum length cannot be longer than %d characters!", MAX_VALUE_LENGTH);
+ if (minLength != -1)
+ {
+ Checks.notNegative(minLength, "Minimum length");
+ Checks.check(minLength <= MAX_VALUE_LENGTH, "Minimum length cannot be longer than %d characters!", MAX_VALUE_LENGTH);
+ }
+
this.minLength = minLength;
return this;
}
@@ -303,21 +307,24 @@ public Builder setMinLength(int minLength)
/**
* Sets the maximum length of this input field. Default is -1 (No maximum length).
*
- *
This has to be between 1 and {@value #MAX_VALUE_LENGTH}
+ *
This has to be between 1 and {@value #MAX_VALUE_LENGTH}, or -1 for no maximum length
*
* @param maxLength
- * The maximum amount of characters that need to be written
+ * The maximum amount of characters that need to be written, or -1
*
* @throws IllegalArgumentException
- * If maxLength is smaller than 1 or greater than {@value #MAX_VALUE_LENGTH}
+ * If maxLength is not -1 and is smaller than 1 or greater than {@value #MAX_VALUE_LENGTH}
*
* @return The same builder instance for chaining
*/
@Nonnull
public Builder setMaxLength(int maxLength)
{
- Checks.check(maxLength >= 1, "Maximum length cannot be smaller than 1 character!");
- Checks.check(maxLength <= MAX_VALUE_LENGTH, "Maximum length cannot be longer than " + MAX_VALUE_LENGTH + " characters!");
+ if (maxLength != -1)
+ {
+ Checks.check(maxLength >= 1, "Maximum length cannot be smaller than 1 character!");
+ Checks.check(maxLength <= MAX_VALUE_LENGTH, "Maximum length cannot be longer than %d characters!", MAX_VALUE_LENGTH);
+ }
this.maxLength = maxLength;
return this;
@@ -327,14 +334,14 @@ public Builder setMaxLength(int maxLength)
* Sets the minimum and maximum required length on this TextInput component
*
* @param min
- * Minimum length of the text input
+ * Minimum length of the text input, or -1 for none
* @param max
- * Maximum length of the text input
+ * Maximum length of the text input, or -1 for none
* @throws IllegalArgumentException
*
- * If min is negative or greater than {@link #MAX_VALUE_LENGTH}
- * If max is smaller than 1, smaller than min or greater than {@link #MAX_VALUE_LENGTH}
+ * If min is not -1 and is negative or greater than {@link #MAX_VALUE_LENGTH}
+ * If max is not -1 and is smaller than 1, smaller than min or greater than {@link #MAX_VALUE_LENGTH}
*
*
* @return The same builder instance for chaining
@@ -342,7 +349,7 @@ public Builder setMaxLength(int maxLength)
@Nonnull
public Builder setRequiredRange(int min, int max)
{
- if (min > max)
+ if (min != -1 && max != -1 && min > max)
throw new IllegalArgumentException("minimum cannot be greater than maximum!");
setMinLength(min);
diff --git a/src/main/java/net/dv8tion/jda/api/managers/channel/attribute/ICategorizableChannelManager.java b/src/main/java/net/dv8tion/jda/api/managers/channel/attribute/ICategorizableChannelManager.java
index b6bc4ef1f90..3bc820dbf2c 100644
--- a/src/main/java/net/dv8tion/jda/api/managers/channel/attribute/ICategorizableChannelManager.java
+++ b/src/main/java/net/dv8tion/jda/api/managers/channel/attribute/ICategorizableChannelManager.java
@@ -37,7 +37,7 @@
* @param The manager type
*/
public interface ICategorizableChannelManager>
- extends ChannelManager, IPermissionContainerManager
+ extends ChannelManager, IPermissionContainerManager, IPositionableChannelManager
{
/**
* Sets the {@link Category Parent Category}
diff --git a/src/main/java/net/dv8tion/jda/api/requests/Request.java b/src/main/java/net/dv8tion/jda/api/requests/Request.java
index 434c6084976..468a2df017d 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/Request.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/Request.java
@@ -113,11 +113,13 @@ public void onSuccess(T successObj)
return;
done = true;
cleanup();
+ RestActionImpl.LOG.trace("Scheduling success callback for request with route {}/{}", route.getMethod(), route.getCompiledRoute());
api.getCallbackPool().execute(() ->
{
try (ThreadLocalReason.Closable __ = ThreadLocalReason.closable(localReason);
CallbackContext ___ = CallbackContext.getInstance())
{
+ RestActionImpl.LOG.trace("Running success callback for request with route {}/{}", route.getMethod(), route.getCompiledRoute());
onSuccess.accept(successObj);
}
catch (Throwable t)
@@ -151,11 +153,13 @@ public void onFailure(Throwable failException)
return;
done = true;
cleanup();
+ RestActionImpl.LOG.trace("Scheduling failure callback for request with route {}/{}", route.getMethod(), route.getCompiledRoute());
api.getCallbackPool().execute(() ->
{
try (ThreadLocalReason.Closable __ = ThreadLocalReason.closable(localReason);
CallbackContext ___ = CallbackContext.getInstance())
{
+ RestActionImpl.LOG.trace("Running failure callback for request with route {}/{}", route.getMethod(), route.getCompiledRoute());
onFailure.accept(failException);
if (failException instanceof Error)
api.handleEvent(new ExceptionEvent(api, failException, false));
@@ -285,6 +289,7 @@ public boolean isCancelled()
public void handleResponse(@Nonnull Response response)
{
+ RestActionImpl.LOG.trace("Handling response for request with route {}/{} and code {}", route.getMethod(), route.getCompiledRoute(), response.code);
restAction.handleResponse(response, this);
api.handleEvent(new HttpRequestEvent(this, response));
}
diff --git a/src/main/java/net/dv8tion/jda/api/requests/Response.java b/src/main/java/net/dv8tion/jda/api/requests/Response.java
index c09486c9837..cd3ccfa4fce 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/Response.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/Response.java
@@ -20,6 +20,7 @@
import net.dv8tion.jda.api.utils.IOFunction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
+import net.dv8tion.jda.internal.requests.RestActionImpl;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.IOUtil;
@@ -214,6 +215,7 @@ private Optional parseBody(boolean opt, Class clazz, IOFunctionThis pool can potentially scale up and down depending on use.
+ *
+ * It is also possible that this pool is identical to {@link #getScheduler()}.
+ *
+ * @return The elastic {@link ExecutorService}
+ */
+ @Nonnull
+ public ExecutorService getElastic()
+ {
+ return elastic;
}
/**
diff --git a/src/main/java/net/dv8tion/jda/api/requests/Route.java b/src/main/java/net/dv8tion/jda/api/requests/Route.java
index e28e1ca5e16..f15462924ca 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/Route.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/Route.java
@@ -203,6 +203,7 @@ public static class Channels
public static final Route CREATE_PERM_OVERRIDE = new Route(PUT, "channels/{channel_id}/permissions/{permoverride_id}");
public static final Route MODIFY_PERM_OVERRIDE = new Route(PUT, "channels/{channel_id}/permissions/{permoverride_id}");
public static final Route DELETE_PERM_OVERRIDE = new Route(DELETE, "channels/{channel_id}/permissions/{permoverride_id}");
+ public static final Route SET_STATUS = new Route(PUT, "channels/{channel_id}/voice-status");
public static final Route SEND_TYPING = new Route(POST, "channels/{channel_id}/typing");
public static final Route GET_PERMISSIONS = new Route(GET, "channels/{channel_id}/permissions");
diff --git a/src/main/java/net/dv8tion/jda/api/requests/SequentialRestRateLimiter.java b/src/main/java/net/dv8tion/jda/api/requests/SequentialRestRateLimiter.java
index 877a125f75a..1068ffb9fe3 100644
--- a/src/main/java/net/dv8tion/jda/api/requests/SequentialRestRateLimiter.java
+++ b/src/main/java/net/dv8tion/jda/api/requests/SequentialRestRateLimiter.java
@@ -24,10 +24,7 @@
import javax.annotation.Nonnull;
import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -89,7 +86,7 @@ public final class SequentialRestRateLimiter implements RestRateLimiter
public SequentialRestRateLimiter(@Nonnull RateLimitConfig config)
{
this.config = config;
- this.cleanupWorker = config.getPool().scheduleAtFixedRate(this::cleanup, 30, 30, TimeUnit.SECONDS);
+ this.cleanupWorker = config.getScheduler().scheduleAtFixedRate(this::cleanup, 30, 30, TimeUnit.SECONDS);
}
@Override
@@ -179,7 +176,7 @@ private void cleanup()
bucket.requests.removeIf(Work::isSkipped); // Remove cancelled requests
// Check if the bucket is empty
- if (bucket.requests.isEmpty())
+ if (bucket.requests.isEmpty() && !rateLimitQueue.containsKey(bucket))
{
// remove uninit if requests are empty
if (bucket.isUninit())
@@ -225,14 +222,46 @@ private Bucket getBucket(Route.CompiledRoute route)
});
}
+ private void scheduleElastic(Bucket bucket)
+ {
+ if (isShutdown)
+ return;
+
+ ExecutorService elastic = config.getElastic();
+ ScheduledExecutorService scheduler = config.getScheduler();
+
+ try
+ {
+ // Avoid context switch if unnecessary
+ if (elastic == scheduler)
+ bucket.run();
+ else
+ elastic.execute(bucket);
+ }
+ catch (RejectedExecutionException ex)
+ {
+ if (!isShutdown)
+ log.error("Failed to execute bucket worker", ex);
+ }
+ catch (Throwable t)
+ {
+ log.error("Caught throwable in bucket worker", t);
+ if (t instanceof Error)
+ throw t;
+ }
+ }
+
private void runBucket(Bucket bucket)
{
if (isShutdown)
return;
// Schedule a new bucket worker if no worker is running
MiscUtil.locked(lock, () ->
- rateLimitQueue.computeIfAbsent(bucket,
- (k) -> config.getPool().schedule(bucket, bucket.getRateLimit(), TimeUnit.MILLISECONDS)));
+ rateLimitQueue.computeIfAbsent(bucket,
+ k -> config.getScheduler().schedule(
+ () -> scheduleElastic(bucket),
+ bucket.getRateLimit(), TimeUnit.MILLISECONDS))
+ );
}
private long parseLong(String input)
@@ -252,9 +281,9 @@ private long getNow()
return System.currentTimeMillis();
}
- private void updateBucket(Route.CompiledRoute route, Response response)
+ private Bucket updateBucket(Route.CompiledRoute route, Response response)
{
- MiscUtil.locked(lock, () ->
+ return MiscUtil.locked(lock, () ->
{
try
{
@@ -302,7 +331,7 @@ else if (cloudflare)
boolean firstHit = hitRatelimit.add(baseRoute) && retryAfter < 60000;
// Update the bucket to the new information
bucket.remaining = 0;
- bucket.reset = getNow() + retryAfter;
+ bucket.reset = now + retryAfter;
// don't log warning if we hit the rate limit for the first time, likely due to initialization of the bucket
// unless its a long retry-after delay (more than a minute)
if (firstHit)
@@ -310,6 +339,8 @@ else if (cloudflare)
else
log.warn("Encountered 429 on route {} with bucket {} Retry-After: {} ms Scope: {}", baseRoute, bucket.bucketId, retryAfter, scope);
}
+
+ log.trace("Updated bucket {} to retry after {}", bucket.bucketId, bucket.reset - now);
return bucket;
}
@@ -367,7 +398,8 @@ public void enqueue(Work request)
public void retry(Work request)
{
- requests.addFirst(request);
+ if (!moveRequest(request))
+ requests.addFirst(request);
}
public long getReset()
@@ -423,7 +455,7 @@ public Queue getRequests()
return requests;
}
- protected Boolean moveRequest(Work request)
+ protected boolean moveRequest(Work request)
{
return MiscUtil.locked(lock, () ->
{
@@ -433,9 +465,8 @@ protected Boolean moveRequest(Work request)
{
bucket.enqueue(request);
runBucket(bucket);
- return true;
}
- return false;
+ return bucket != this;
});
}
@@ -480,12 +511,8 @@ public void run()
if (request.isSkipped())
continue;
- // Check if a bucket has been discovered and initialized for this route
- if (isUninit())
- {
- boolean shouldSkip = moveRequest(request);
- if (shouldSkip) continue;
- }
+ if (isUninit() && moveRequest(request))
+ continue;
if (execute(request)) break;
}
diff --git a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java
index 1e6eb0f592a..2ee98b3ff96 100644
--- a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java
+++ b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManager.java
@@ -22,18 +22,18 @@
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.SelfUser;
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
-import net.dv8tion.jda.api.requests.GatewayIntent;
-import net.dv8tion.jda.api.requests.RestConfig;
-import net.dv8tion.jda.api.requests.Route;
+import net.dv8tion.jda.api.requests.*;
import net.dv8tion.jda.api.utils.ChunkingFilter;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.SessionController;
import net.dv8tion.jda.api.utils.cache.ShardCacheView;
+import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.entities.SelfUserImpl;
import net.dv8tion.jda.internal.managers.PresenceImpl;
import net.dv8tion.jda.internal.requests.RestActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.IOUtil;
import net.dv8tion.jda.internal.utils.JDALogger;
import net.dv8tion.jda.internal.utils.UnlockHook;
import net.dv8tion.jda.internal.utils.cache.ShardCacheViewImpl;
@@ -42,17 +42,23 @@
import net.dv8tion.jda.internal.utils.config.SessionConfig;
import net.dv8tion.jda.internal.utils.config.ThreadingConfig;
import net.dv8tion.jda.internal.utils.config.sharding.*;
+import okhttp3.Call;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
import java.util.function.IntFunction;
/**
@@ -366,6 +372,9 @@ public void shutdown()
{
this.executor.shutdown();
}
+
+ // Shutdown shared pools
+ this.threadingConfig.shutdown();
}
@Override
@@ -486,10 +495,17 @@ protected JDAImpl buildInstance(final int shardId)
httpClient = sessionConfig.getHttpBuilder().build();
}
+ retrieveShardTotal(httpClient);
+ threadingConfig.init(queue.isEmpty() ? getShardsTotal() : queue.size());
+
// imagine if we had macros or closures or destructuring :)
- ExecutorPair rateLimitPair = resolveExecutor(threadingConfig.getRateLimitPoolProvider(), shardId);
- ScheduledExecutorService rateLimitPool = rateLimitPair.executor;
- boolean shutdownRateLimitPool = rateLimitPair.automaticShutdown;
+ ExecutorPair rateLimitSchedulerPair = resolveExecutor(threadingConfig.getRateLimitSchedulerProvider(), shardId);
+ ScheduledExecutorService rateLimitScheduler = rateLimitSchedulerPair.executor;
+ boolean shutdownRateLimitScheduler = rateLimitSchedulerPair.automaticShutdown;
+
+ ExecutorPair rateLimitElasticPair = resolveExecutor(threadingConfig.getRateLimitElasticProvider(), shardId);
+ ExecutorService rateLimitElastic = rateLimitElasticPair.executor;
+ boolean shutdownRateLimitElastic = rateLimitElasticPair.automaticShutdown;
ExecutorPair gatewayPair = resolveExecutor(threadingConfig.getGatewayPoolProvider(), shardId);
ScheduledExecutorService gatewayPool = gatewayPair.executor;
@@ -510,7 +526,8 @@ protected JDAImpl buildInstance(final int shardId)
AuthorizationConfig authConfig = new AuthorizationConfig(token);
SessionConfig sessionConfig = this.sessionConfig.toSessionConfig(httpClient);
ThreadingConfig threadingConfig = new ThreadingConfig();
- threadingConfig.setRateLimitPool(rateLimitPool, shutdownRateLimitPool);
+ threadingConfig.setRateLimitScheduler(rateLimitScheduler, shutdownRateLimitScheduler);
+ threadingConfig.setRateLimitElastic(rateLimitElastic, shutdownRateLimitElastic);
threadingConfig.setGatewayPool(gatewayPool, shutdownGatewayPool);
threadingConfig.setCallbackPool(callbackPool, shutdownCallbackPool);
threadingConfig.setEventPool(eventPool, shutdownEventPool);
@@ -557,21 +574,9 @@ protected JDAImpl buildInstance(final int shardId)
this.sessionConfig.getSessionController().setConcurrency(gateway.getConcurrency());
this.gatewayURL = gateway.getUrl();
if (this.gatewayURL == null)
- LOG.error("Acquired null gateway url from SessionController");
+ throw new IllegalStateException("Acquired null gateway url from SessionController");
else
LOG.info("Login Successful!");
-
- if (getShardsTotal() == -1)
- {
- shardingConfig.setShardsTotal(gateway.getShardTotal());
- this.shards = new ShardCacheViewImpl(getShardsTotal());
-
- synchronized (queue)
- {
- for (int i = 0; i < getShardsTotal(); i++)
- queue.add(i);
- }
- }
}
final JDA.ShardInfo shardInfo = new JDA.ShardInfo(shardId, getShardsTotal());
@@ -591,9 +596,7 @@ protected JDAImpl buildInstance(final int shardId)
jda.setSelfUser(selfUser);
jda.setStatus(JDA.Status.INITIALIZED); //This is already set by JDA internally, but this is to make sure the listeners catch it.
- final int shardTotal = jda.login(this.gatewayURL, shardInfo, this.metaConfig.getCompression(), false, shardingConfig.getIntents(), this.metaConfig.getEncoding());
- if (getShardsTotal() == -1)
- shardingConfig.setShardsTotal(shardTotal);
+ jda.login(this.gatewayURL, shardInfo, this.metaConfig.getCompression(), false, shardingConfig.getIntents(), this.metaConfig.getEncoding());
return jda;
}
@@ -635,6 +638,51 @@ public void setStatusProvider(IntFunction statusProvider)
presenceConfig.setStatusProvider(statusProvider);
}
+ private synchronized void retrieveShardTotal(OkHttpClient httpClient)
+ {
+ if (getShardsTotal() != -1)
+ return;
+
+ LOG.debug("Fetching shard total using temporary rate-limiter");
+
+ CompletableFuture future = new CompletableFuture<>();
+ ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor(task -> {
+ Thread thread = new Thread(task, "DefaultShardManager retrieveShardTotal");
+ thread.setDaemon(true);
+ return thread;
+ });
+
+ try
+ {
+ RestRateLimiter.RateLimitConfig rateLimitConfig = new RestRateLimiter.RateLimitConfig(pool, RestRateLimiter.GlobalRateLimit.create(), true);
+ SequentialRestRateLimiter rateLimiter = new SequentialRestRateLimiter(rateLimitConfig);
+ rateLimiter.enqueue(new ShardTotalTask(future, httpClient));
+
+ int shardTotal = future.join();
+ this.shardingConfig.setShardsTotal(shardTotal);
+ this.shards = new ShardCacheViewImpl(shardTotal);
+
+ synchronized (queue)
+ {
+ for (int i = 0; i < shardTotal; i++)
+ queue.add(i);
+ }
+ }
+ catch (CompletionException ex)
+ {
+ if (ex.getCause() instanceof RuntimeException)
+ throw (RuntimeException) ex.getCause();
+ if (ex.getCause() instanceof Error)
+ throw (Error) ex.getCause();
+ throw ex;
+ }
+ finally
+ {
+ future.cancel(false);
+ pool.shutdownNow();
+ }
+ }
+
/**
* This method creates the internal {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService}.
* It is intended as a hook for custom implementations to create their own executor.
@@ -673,4 +721,141 @@ protected ExecutorPair(E executor, boolean automaticShutdown)
this.automaticShutdown = automaticShutdown;
}
}
+
+ protected class ShardTotalTask implements RestRateLimiter.Work
+ {
+ private final CompletableFuture future;
+ private final OkHttpClient httpClient;
+ private int failedAttempts = 0;
+
+ protected ShardTotalTask(CompletableFuture future, OkHttpClient httpClient)
+ {
+ this.future = future;
+ this.httpClient = httpClient;
+ }
+
+ @Nonnull
+ @Override
+ public Route.CompiledRoute getRoute()
+ {
+ return Route.Misc.GATEWAY_BOT.compile();
+ }
+
+ @Nonnull
+ @Override
+ public JDA getJDA()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Nullable
+ @Override
+ public okhttp3.Response execute()
+ {
+ try
+ {
+ RestConfig config = restConfigProvider.apply(0);
+ String url = config.getBaseUrl() + getRoute().getCompiledRoute();
+ LOG.debug("Requesting shard total with url {}", url);
+
+ okhttp3.Request.Builder builder = new okhttp3.Request.Builder()
+ .get()
+ .url(url)
+ .header("authorization", "Bot " + token)
+ .header("accept-encoding", "gzip")
+ .header("user-agent", config.getUserAgent());
+
+ Consumer super okhttp3.Request.Builder> customBuilder = config.getCustomBuilder();
+ if (customBuilder != null)
+ customBuilder.accept(builder);
+
+ Call call = httpClient.newCall(builder.build());
+ okhttp3.Response response = call.execute();
+
+ try
+ {
+ LOG.debug("Received response with code {}", response.code());
+ InputStream body = IOUtil.getBody(response);
+
+ if (response.isSuccessful())
+ {
+ DataObject json = DataObject.fromJson(body);
+ int shardTotal = json.getInt("shards");
+ future.complete(shardTotal);
+ }
+ else if (response.code() == 401)
+ {
+ future.completeExceptionally(new InvalidTokenException());
+ }
+ else if (response.code() != 429 && response.code() < 500 || ++failedAttempts > 4)
+ {
+ future.completeExceptionally(new IllegalStateException(
+ "Failed to fetch recommended shard total! Code: " + response.code() + "\n" +
+ new String(IOUtil.readFully(body), StandardCharsets.UTF_8)
+ ));
+ }
+ else if (response.code() >= 500)
+ {
+ int backoff = 1 << failedAttempts;
+ LOG.warn("Failed to retrieve recommended shard total. Code: {} ... retrying in {}s", response.code(), backoff);
+ response = response.newBuilder()
+ .headers(response.headers()
+ .newBuilder()
+ .set(RestRateLimiter.RESET_AFTER_HEADER, String.valueOf(backoff))
+ .set(RestRateLimiter.REMAINING_HEADER, String.valueOf(0))
+ .set(RestRateLimiter.LIMIT_HEADER, String.valueOf(1))
+ .set(RestRateLimiter.SCOPE_HEADER, "custom")
+ .build())
+ .build();
+ }
+
+ return response;
+ }
+ finally
+ {
+ response.close();
+ }
+ }
+ catch (IOException e)
+ {
+ future.completeExceptionally(e);
+ throw new UncheckedIOException(e);
+ }
+ catch (Throwable e)
+ {
+ future.completeExceptionally(e);
+ throw e;
+ }
+ }
+
+ @Override
+ public boolean isSkipped()
+ {
+ return isCancelled();
+ }
+
+ @Override
+ public boolean isDone()
+ {
+ return future.isDone();
+ }
+
+ @Override
+ public boolean isPriority()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ return future.isCancelled();
+ }
+
+ @Override
+ public void cancel()
+ {
+ future.cancel(false);
+ }
+ }
}
diff --git a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java
index c6e0ef2e8bf..02700abd03c 100644
--- a/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java
+++ b/src/main/java/net/dv8tion/jda/api/sharding/DefaultShardManagerBuilder.java
@@ -36,6 +36,7 @@
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.concurrent.CountingThreadFactory;
import net.dv8tion.jda.internal.utils.config.flags.ConfigFlag;
import net.dv8tion.jda.internal.utils.config.flags.ShardingConfigFlag;
import net.dv8tion.jda.internal.utils.config.sharding.*;
@@ -82,8 +83,23 @@ public class DefaultShardManagerBuilder
protected IntFunction extends Activity> activityProvider = null;
protected IntFunction extends ConcurrentMap> contextProvider = null;
protected IntFunction extends IEventManager> eventManagerProvider = null;
- protected ThreadPoolProvider extends ScheduledExecutorService> rateLimitPoolProvider = null;
- protected ThreadPoolProvider extends ScheduledExecutorService> gatewayPoolProvider = null;
+ protected ThreadPoolProvider extends ScheduledExecutorService> rateLimitSchedulerProvider = ThreadPoolProvider.lazy(
+ (total) -> Executors.newScheduledThreadPool(Math.max(2, 2 * (int) Math.log(total)), new CountingThreadFactory(() -> "JDA", "RateLimit-Scheduler", true))
+ );
+ protected ThreadPoolProvider extends ExecutorService> rateLimitElasticProvider = ThreadPoolProvider.lazy(
+ (total) -> {
+ ExecutorService pool = Executors.newCachedThreadPool(new CountingThreadFactory(() -> "JDA", "RateLimit-Elastic", true));
+ if (pool instanceof ThreadPoolExecutor)
+ {
+ ((ThreadPoolExecutor) pool).setCorePoolSize(Math.max(1, (int) Math.log(total)));
+ ((ThreadPoolExecutor) pool).setKeepAliveTime(2, TimeUnit.MINUTES);
+ }
+ return pool;
+ }
+ );
+ protected ThreadPoolProvider extends ScheduledExecutorService> gatewayPoolProvider = ThreadPoolProvider.lazy(
+ (total) -> Executors.newScheduledThreadPool(Math.max(1, (int) Math.log(total)), new CountingThreadFactory(() -> "JDA", "Gateway"))
+ );
protected ThreadPoolProvider extends ExecutorService> callbackPoolProvider = null;
protected ThreadPoolProvider extends ExecutorService> eventPoolProvider = null;
protected ThreadPoolProvider extends ScheduledExecutorService> audioPoolProvider = null;
@@ -1309,14 +1325,19 @@ public DefaultShardManagerBuilder setHttpClient(@Nullable OkHttpClient client)
* Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
* and similar methods.
*
- * Default: {@link ScheduledThreadPoolExecutor} with 5 threads (per shard).
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code 2 * }{@link #setShardsTotal(int) shard_total}) threads.
*
* @param pool
* The thread-pool to use for rate-limit handling
*
* @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ *
+ * @deprecated This pool is now split into two pools.
+ * You should use {@link #setRateLimitScheduler(ScheduledExecutorService)} and {@link #setRateLimitElastic(ExecutorService)} instead.
*/
@Nonnull
+ @Deprecated
+ @ReplaceWith("setRateLimitScheduler(pool) or setRateLimitElastic(pool)")
public DefaultShardManagerBuilder setRateLimitPool(@Nullable ScheduledExecutorService pool)
{
return setRateLimitPool(pool, pool == null);
@@ -1332,7 +1353,7 @@ public DefaultShardManagerBuilder setRateLimitPool(@Nullable ScheduledExecutorSe
* Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
* and similar methods.
*
- *
Default: {@link ScheduledThreadPoolExecutor} with 5 threads (per shard).
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code 2 * }{@link #setShardsTotal(int) shard_total}) threads.
*
* @param pool
* The thread-pool to use for rate-limit handling
@@ -1340,8 +1361,13 @@ public DefaultShardManagerBuilder setRateLimitPool(@Nullable ScheduledExecutorSe
* Whether {@link net.dv8tion.jda.api.JDA#shutdown()} should automatically shutdown this pool
*
* @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ *
+ * @deprecated This pool is now split into two pools.
+ * You should use {@link #setRateLimitScheduler(ScheduledExecutorService, boolean)} and {@link #setRateLimitElastic(ExecutorService, boolean)} instead.
*/
@Nonnull
+ @Deprecated
+ @ReplaceWith("setRateLimitScheduler(pool, automaticShutdown) or setRateLimitElastic(pool, automaticShutdown)")
public DefaultShardManagerBuilder setRateLimitPool(@Nullable ScheduledExecutorService pool, boolean automaticShutdown)
{
return setRateLimitPoolProvider(pool == null ? null : new ThreadPoolProviderImpl<>(pool, automaticShutdown));
@@ -1356,17 +1382,164 @@ public DefaultShardManagerBuilder setRateLimitPool(@Nullable ScheduledExecutorSe
* Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
* and similar methods.
*
- *
Default: {@link ScheduledThreadPoolExecutor} with 5 threads (per shard).
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code 2 * }{@link #setShardsTotal(int) shard_total}) threads.
*
* @param provider
* The thread-pool provider to use for rate-limit handling
*
* @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ *
+ * @deprecated This pool is now split into two pools.
+ * You should use {@link #setRateLimitPoolProvider(ThreadPoolProvider)} and {@link #setRateLimitElasticProvider(ThreadPoolProvider)} instead.
*/
@Nonnull
+ @Deprecated
+ @ReplaceWith("setRateLimitSchedulerProvider(provider) or setRateLimitElasticProvider(provider)")
public DefaultShardManagerBuilder setRateLimitPoolProvider(@Nullable ThreadPoolProvider extends ScheduledExecutorService> provider)
{
- this.rateLimitPoolProvider = provider;
+ this.rateLimitSchedulerProvider = provider;
+ return this;
+ }
+
+ /**
+ * Sets the {@link ScheduledExecutorService ScheduledExecutorService} that should be used in
+ * the JDA rate-limit handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ * This will override the rate-limit pool provider set from {@link #setRateLimitPoolProvider(ThreadPoolProvider)}.
+ * This automatically disables the automatic shutdown of the rate-limit pool, you can enable
+ * it using {@link #setRateLimitPool(ScheduledExecutorService, boolean) setRateLimiPool(executor, true)}
+ *
+ *
This is used mostly by the Rate-Limiter to handle backoff delays by using scheduled executions.
+ * Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
+ * and similar methods. Requests are handed off to the {@link #setRateLimitElastic(ExecutorService) elastic pool} for blocking execution.
+ *
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code 2 * } log({@link #setShardsTotal(int) shard_total})) threads.
+ *
+ * @param pool
+ * The thread-pool to use for rate-limit handling
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRateLimitScheduler(@Nullable ScheduledExecutorService pool)
+ {
+ return setRateLimitScheduler(pool, pool == null);
+ }
+
+ /**
+ * Sets the {@link ScheduledExecutorService ScheduledExecutorService} that should be used in
+ * the JDA rate-limit handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ * This will override the rate-limit pool provider set from {@link #setRateLimitPoolProvider(ThreadPoolProvider)}.
+ *
+ *
This is used mostly by the Rate-Limiter to handle backoff delays by using scheduled executions.
+ * Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
+ * and similar methods. Requests are handed off to the {@link #setRateLimitElastic(ExecutorService) elastic pool} for blocking execution.
+ *
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code 2 * } log({@link #setShardsTotal(int) shard_total})) threads.
+ *
+ * @param pool
+ * The thread-pool to use for rate-limit handling
+ * @param automaticShutdown
+ * Whether {@link net.dv8tion.jda.api.JDA#shutdown()} should automatically shutdown this pool
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRateLimitScheduler(@Nullable ScheduledExecutorService pool, boolean automaticShutdown)
+ {
+ return setRateLimitSchedulerProvider(pool == null ? null : new ThreadPoolProviderImpl<>(pool, automaticShutdown));
+ }
+
+ /**
+ * Sets the {@link ScheduledExecutorService ScheduledExecutorService} provider that should be used in
+ * the JDA rate-limit handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ *
+ *
This is used mostly by the Rate-Limiter to handle backoff delays by using scheduled executions.
+ * Besides that it is also used by planned execution for {@link net.dv8tion.jda.api.requests.RestAction#queueAfter(long, TimeUnit)}
+ * and similar methods. Requests are handed off to the {@link #setRateLimitElastic(ExecutorService) elastic pool} for blocking execution.
+ *
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code 2 * } log({@link #setShardsTotal(int) shard_total})) threads.
+ *
+ * @param provider
+ * The thread-pool provider to use for rate-limit handling
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRateLimitSchedulerProvider(@Nullable ThreadPoolProvider extends ScheduledExecutorService> provider)
+ {
+ this.rateLimitSchedulerProvider = provider;
+ return this;
+ }
+
+ /**
+ * Sets the {@link ExecutorService} that should be used in
+ * the JDA request handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ * This will override the rate-limit pool provider set from {@link #setRateLimitElasticProvider(ThreadPoolProvider)}.
+ * This automatically disables the automatic shutdown of the rate-limit elastic pool, you can enable
+ * it using {@link #setRateLimitElastic(ExecutorService, boolean) setRateLimitElastic(executor, true)}
+ *
+ *
This is used mostly by the Rate-Limiter to execute the blocking HTTP requests at runtime.
+ *
+ *
Default: {@link Executors#newCachedThreadPool()} shared between all shards.
+ *
+ * @param pool
+ * The thread-pool to use for executing http requests
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRateLimitElastic(@Nullable ExecutorService pool)
+ {
+ return setRateLimitElastic(pool, pool == null);
+ }
+
+ /**
+ * Sets the {@link ExecutorService} that should be used in
+ * the JDA request handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ * This will override the rate-limit pool provider set from {@link #setRateLimitElasticProvider(ThreadPoolProvider)}.
+ * This automatically disables the automatic shutdown of the rate-limit elastic pool, you can enable
+ * it using {@link #setRateLimitElastic(ExecutorService, boolean) setRateLimitElastic(executor, true)}
+ *
+ *
This is used mostly by the Rate-Limiter to execute the blocking HTTP requests at runtime.
+ *
+ *
Default: {@link Executors#newCachedThreadPool()} shared between all shards.
+ *
+ * @param pool
+ * The thread-pool to use for executing http requests
+ * @param automaticShutdown
+ * Whether {@link net.dv8tion.jda.api.JDA#shutdown()} should automatically shutdown this pool
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRateLimitElastic(@Nullable ExecutorService pool, boolean automaticShutdown)
+ {
+ return setRateLimitElasticProvider(pool == null ? null : new ThreadPoolProviderImpl<>(pool, automaticShutdown));
+ }
+
+ /**
+ * Sets the {@link ExecutorService} that should be used in
+ * the JDA request handler. Changing this can drastically change the JDA behavior for RestAction execution
+ * and should be handled carefully. Only change this pool if you know what you're doing.
+ *
+ *
This is used mostly by the Rate-Limiter to execute the blocking HTTP requests at runtime.
+ *
+ *
Default: {@link Executors#newCachedThreadPool()} shared between all shards.
+ *
+ * @param provider
+ * The thread-pool provider to use for executing http requests
+ *
+ * @return The DefaultShardManagerBuilder instance. Useful for chaining.
+ */
+ @Nonnull
+ public DefaultShardManagerBuilder setRateLimitElasticProvider(@Nullable ThreadPoolProvider extends ExecutorService> provider)
+ {
+ this.rateLimitElasticProvider = provider;
return this;
}
@@ -1389,7 +1562,7 @@ public DefaultShardManagerBuilder setRateLimitPoolProvider(@Nullable ThreadPoolP
* Once a new payload is sent we switch to "rapid mode" which means more tasks will be submitted until no more payloads
* have to be sent.
*
- *
Default: {@link ScheduledThreadPoolExecutor} with 1 thread (per shard)
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code log}({@link #setShardsTotal(int) shard_total})) threads.
*
* @param pool
* The thread-pool to use for main WebSocket workers
@@ -1419,7 +1592,7 @@ public DefaultShardManagerBuilder setGatewayPool(@Nullable ScheduledExecutorServ
* Once a new payload is sent we switch to "rapid mode" which means more tasks will be submitted until no more payloads
* have to be sent.
*
- *
Default: {@link ScheduledThreadPoolExecutor} with 1 thread (per shard)
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code log}({@link #setShardsTotal(int) shard_total})) threads.
*
* @param pool
* The thread-pool to use for main WebSocket workers
@@ -1450,7 +1623,7 @@ public DefaultShardManagerBuilder setGatewayPool(@Nullable ScheduledExecutorServ
* Once a new payload is sent we switch to "rapid mode" which means more tasks will be submitted until no more payloads
* have to be sent.
*
- *
Default: {@link ScheduledThreadPoolExecutor} with 1 thread (per shard)
+ *
Default: Shared {@link ScheduledThreadPoolExecutor} with ({@code log}({@link #setShardsTotal(int) shard_total})) threads.
*
* @param provider
* The thread-pool provider to use for main WebSocket workers
@@ -1542,6 +1715,8 @@ public DefaultShardManagerBuilder setCallbackPoolProvider(@Nullable ThreadPoolPr
*
The executor will not be shutdown automatically when the shard is shutdown.
* To shut it down automatically use {@link #setEventPool(ExecutorService, boolean)}.
*
+ *
Default: Disabled
+ *
* @param executor
* The executor for the event proxy, or null to use calling thread
*
@@ -1559,6 +1734,8 @@ public DefaultShardManagerBuilder setEventPool(@Nullable ExecutorService executo
* Sets the {@link ExecutorService ExecutorService} that should be used by the
* event proxy to schedule events. This will be done on the calling thread by default.
*
+ *
Default: Disabled
+ *
* @param executor
* The executor for the event proxy, or null to use calling thread
* @param automaticShutdown
@@ -1583,7 +1760,7 @@ public DefaultShardManagerBuilder setEventPool(@Nullable ExecutorService executo
*
This is used to handle callbacks of {@link RestAction#queue()}, similarly it is used to
* finish {@link RestAction#submit()} and {@link RestAction#complete()} tasks which build on queue.
*
- *
Default: {@link ForkJoinPool#commonPool()}
+ *
Default: Disabled
*
* @param provider
* The thread-pool provider to use for callback handling
@@ -2239,7 +2416,7 @@ public ShardManager build(boolean login) throws IllegalArgumentException
presenceConfig.setActivityProvider(activityProvider);
presenceConfig.setStatusProvider(statusProvider);
presenceConfig.setIdleProvider(idleProvider);
- final ThreadingProviderConfig threadingConfig = new ThreadingProviderConfig(rateLimitPoolProvider, gatewayPoolProvider, callbackPoolProvider, eventPoolProvider, audioPoolProvider, threadFactory);
+ final ThreadingProviderConfig threadingConfig = new ThreadingProviderConfig(rateLimitSchedulerProvider, rateLimitElasticProvider, gatewayPoolProvider, callbackPoolProvider, eventPoolProvider, audioPoolProvider, threadFactory);
final ShardingSessionConfig sessionConfig = new ShardingSessionConfig(sessionController, voiceDispatchInterceptor, httpClient, httpClientBuilder, wsFactory, audioSendFactory, flags, shardingFlags, maxReconnectDelay, largeThreshold);
final ShardingMetaConfig metaConfig = new ShardingMetaConfig(maxBufferSize, contextProvider, cacheFlags, flags, compression, encoding);
final DefaultShardManager manager = new DefaultShardManager(this.token, this.shards, shardingConfig, eventConfig, presenceConfig, threadingConfig, sessionConfig, metaConfig, restConfigProvider, chunkingFilter);
@@ -2304,6 +2481,7 @@ else if (!membersIntent && chunkingFilter != ChunkingFilter.NONE)
throw new IllegalArgumentException("Cannot use CacheFlag." + flag + " without GatewayIntent." + intent + "!");
}
}
+
//Avoid having multiple anonymous classes
private static class ThreadPoolProviderImpl implements ThreadPoolProvider
{
diff --git a/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java b/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java
index a611589ac0a..d14eb62dd67 100644
--- a/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java
+++ b/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java
@@ -32,12 +32,15 @@
import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.utils.MiscUtil;
import net.dv8tion.jda.api.utils.cache.CacheView;
+import net.dv8tion.jda.api.utils.cache.ChannelCacheView;
import net.dv8tion.jda.api.utils.cache.ShardCacheView;
import net.dv8tion.jda.api.utils.cache.SnowflakeCacheView;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
import net.dv8tion.jda.internal.utils.Checks;
+import net.dv8tion.jda.internal.utils.Helpers;
+import net.dv8tion.jda.internal.utils.cache.UnifiedChannelCacheView;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
@@ -426,11 +429,10 @@ default List getGuilds()
default List getMutualGuilds(@Nonnull final Collection users)
{
Checks.noneNull(users, "users");
- return Collections.unmodifiableList(
- this.getGuildCache().stream()
+ return this.getGuildCache().stream()
.filter(guild -> users.stream()
.allMatch(guild::isMember))
- .collect(Collectors.toList()));
+ .collect(Helpers.toUnmodifiableList());
}
/**
@@ -685,17 +687,6 @@ default List getRolesByName(@Nonnull final String name, final boolean igno
return this.getRoleCache().getElementsByName(name, ignoreCase);
}
- @Nullable
- @Override
- default T getChannelById(@Nonnull Class type, long id)
- {
- Checks.notNull(type, "Class");
- Channel channel = getPrivateChannelById(id);
- if (channel != null)
- return type.isInstance(channel) ? type.cast(channel) : null;
- return IGuildChannelContainer.super.getChannelById(type, id);
- }
-
/**
* This returns the {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannel} which has the same id as the one provided.
* If there is no known {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannel} with an id that matches the provided
@@ -819,6 +810,13 @@ default SnowflakeCacheView getMediaChannelCache()
return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getMediaChannelCache));
}
+ @Nonnull
+ @Override
+ default ChannelCacheView getChannelCache()
+ {
+ return new UnifiedChannelCacheView<>(() -> this.getShardCache().stream().map(JDA::getChannelCache));
+ }
+
/**
* This returns the {@link net.dv8tion.jda.api.JDA JDA} instance which has the same id as the one provided.
* If there is no shard with an id that matches the provided one, this will return {@code null}.
diff --git a/src/main/java/net/dv8tion/jda/api/sharding/ThreadPoolProvider.java b/src/main/java/net/dv8tion/jda/api/sharding/ThreadPoolProvider.java
index 0281ea69717..f3ad44706a3 100644
--- a/src/main/java/net/dv8tion/jda/api/sharding/ThreadPoolProvider.java
+++ b/src/main/java/net/dv8tion/jda/api/sharding/ThreadPoolProvider.java
@@ -16,8 +16,12 @@
package net.dv8tion.jda.api.sharding;
+import net.dv8tion.jda.internal.utils.Checks;
+
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.ExecutorService;
+import java.util.function.IntFunction;
/**
* Called by {@link DefaultShardManager} when building a JDA instance.
@@ -52,4 +56,75 @@ default boolean shouldShutdownAutomatically(int shardId)
{
return false;
}
+
+ /**
+ * Provider that initializes with a {@link DefaultShardManagerBuilder#setShardsTotal(int) shard_total}
+ * and provides the same pool to share between shards.
+ *
+ * @param init
+ * Function to initialize the shared pool, called with the shard total
+ *
+ * @param
+ * The type of executor
+ *
+ * @return The lazy pool provider
+ */
+ @Nonnull
+ static LazySharedProvider lazy(@Nonnull IntFunction init)
+ {
+ Checks.notNull(init, "Initializer");
+ return new LazySharedProvider<>(init);
+ }
+
+ final class LazySharedProvider implements ThreadPoolProvider
+ {
+ private final IntFunction initializer;
+ private T pool;
+
+ LazySharedProvider(@Nonnull IntFunction initializer)
+ {
+ this.initializer = initializer;
+ }
+
+ /**
+ * Called with the shard total to initialize the shared pool.
+ *
+ * This also destroys the temporary pool created for fetching the recommended shard total.
+ *
+ * @param shardTotal
+ * The shard total
+ */
+ public synchronized void init(int shardTotal)
+ {
+ if (pool == null)
+ pool = initializer.apply(shardTotal);
+ }
+
+ /**
+ * Shuts down the shared pool and the temporary pool.
+ */
+ public synchronized void shutdown()
+ {
+ if (pool != null)
+ {
+ pool.shutdown();
+ pool = null;
+ }
+ }
+
+ /**
+ * Provides the initialized pool or the temporary pool if not initialized yet.
+ *
+ * @param shardId
+ * The current shard id
+ *
+ * @return The thread pool instance
+ */
+ @Nullable
+ @Override
+ public synchronized T provide(int shardId)
+ {
+ return pool;
+ }
+ }
}
diff --git a/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java b/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java
index cdb64dcdc4f..adadd90fd8a 100644
--- a/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java
+++ b/src/main/java/net/dv8tion/jda/api/utils/SessionControllerAdapter.java
@@ -96,7 +96,7 @@ public void handleResponse(Response response, Request request)
else if (response.code == 401)
{
api.shutdownNow();
- request.onFailure(new InvalidTokenException("The provided token is invalid!"));
+ request.onFailure(new InvalidTokenException());
}
else
{
diff --git a/src/main/java/net/dv8tion/jda/api/utils/cache/ChannelCacheView.java b/src/main/java/net/dv8tion/jda/api/utils/cache/ChannelCacheView.java
new file mode 100644
index 00000000000..fd49d35d828
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/utils/cache/ChannelCacheView.java
@@ -0,0 +1,85 @@
+/*
+ * 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.utils.cache;
+
+import net.dv8tion.jda.api.entities.channel.Channel;
+import net.dv8tion.jda.api.entities.channel.ChannelType;
+import net.dv8tion.jda.api.utils.MiscUtil;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Specialized {@link SnowflakeCacheView} type used for handling channels.
+ * This type caches all relevant channel types, including threads.
+ *
+ * Internally, this cache view makes a distinction between the varying {@link ChannelType ChannelTypes} and provides convenient methods to access a filtered subset.
+ *
+ * @param
+ * The channel type
+ */
+public interface ChannelCacheView extends SnowflakeCacheView
+{
+ /**
+ * Creates a decorator around this cache, filtered to only provide access to the given type.
+ *
+ * @param type
+ * The type class (Like {@code TextChannel.class})
+ * @param
+ * The type parameter
+ *
+ * @throws IllegalArgumentException
+ * If null is provided
+ *
+ * @return The filtered cache view
+ */
+ @Nonnull
+ ChannelCacheView ofType(@Nonnull Class type);
+
+ /**
+ * Retrieves the entity represented by the provided ID.
+ *
+ * @param type
+ * The expected {@link ChannelType}
+ * @param id
+ * The ID of the entity
+ *
+ * @return Possibly-null entity for the specified ID, null if the expected type is different from the actual type
+ */
+ @Nullable
+ T getElementById(@Nonnull ChannelType type, long id);
+
+ /**
+ * Retrieves the entity represented by the provided ID.
+ *
+ * @param type
+ * The expected {@link ChannelType}
+ * @param id
+ * The ID of the entity
+ *
+ * @throws java.lang.NumberFormatException
+ * If the provided String is {@code null} or
+ * cannot be resolved to an unsigned long id
+ *
+ * @return Possibly-null entity for the specified ID, null if the expected type is different from the actual type
+ */
+ @Nullable
+ default T getElementById(@Nonnull ChannelType type, @Nonnull String id)
+ {
+ return getElementById(type, MiscUtil.parseSnowflake(id));
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/utils/cache/SortedChannelCacheView.java b/src/main/java/net/dv8tion/jda/api/utils/cache/SortedChannelCacheView.java
new file mode 100644
index 00000000000..7e46c4f3f2e
--- /dev/null
+++ b/src/main/java/net/dv8tion/jda/api/utils/cache/SortedChannelCacheView.java
@@ -0,0 +1,40 @@
+/*
+ * 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.utils.cache;
+
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.channel.Channel;
+import net.dv8tion.jda.api.entities.channel.ChannelType;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Specialized {@link ChannelCacheView} type used for handling sorted lists of channels.
+ * Sorting is done with respect to the positioning in the official Discord client, by comparing positions and category information.
+ *
+ * Internally, this cache view makes a distinction between the varying {@link ChannelType ChannelTypes} and provides convenient methods to access a filtered subset.
+ *
+ * @param
+ * The channel type
+ *
+ * @see Guild#getChannels()
+ */
+public interface SortedChannelCacheView> extends ChannelCacheView, SortedSnowflakeCacheView
+{
+ @Nonnull
+ SortedChannelCacheView ofType(@Nonnull Class type);
+}
diff --git a/src/main/java/net/dv8tion/jda/api/utils/messages/MessageEditData.java b/src/main/java/net/dv8tion/jda/api/utils/messages/MessageEditData.java
index a4b4a25db4e..cce4f5913e0 100644
--- a/src/main/java/net/dv8tion/jda/api/utils/messages/MessageEditData.java
+++ b/src/main/java/net/dv8tion/jda/api/utils/messages/MessageEditData.java
@@ -25,11 +25,11 @@
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.api.utils.data.SerializableData;
+import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.IOUtil;
import javax.annotation.Nonnull;
import java.util.*;
-import java.util.stream.Collectors;
import static net.dv8tion.jda.api.utils.messages.MessageEditBuilder.*;
@@ -328,9 +328,16 @@ public synchronized DataObject toData()
if (isSet(ATTACHMENTS))
{
DataArray attachments = DataArray.empty();
+
+ int fileUploadCount = 0;
+ for (AttachedFile file : files)
+ {
+ attachments.add(file.toAttachmentData(fileUploadCount));
+ if (file instanceof FileUpload)
+ fileUploadCount++;
+ }
+
json.put("attachments", attachments);
- for (int i = 0; i < files.size(); i++)
- attachments.add(files.get(i).toAttachmentData(i));
}
return json;
@@ -344,12 +351,10 @@ public synchronized DataObject toData()
@Nonnull
public synchronized List getFiles()
{
- return Collections.unmodifiableList(
- files.stream()
- .filter(FileUpload.class::isInstance)
- .map(FileUpload.class::cast)
- .collect(Collectors.toList())
- );
+ return files.stream()
+ .filter(FileUpload.class::isInstance)
+ .map(FileUpload.class::cast)
+ .collect(Helpers.toUnmodifiableList());
}
@Override
diff --git a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java
index cdab6694272..f55f9801110 100644
--- a/src/main/java/net/dv8tion/jda/internal/JDAImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/JDAImpl.java
@@ -26,7 +26,9 @@
import net.dv8tion.jda.api.audio.hooks.ConnectionStatus;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.channel.Channel;
+import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.concrete.*;
+import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji;
import net.dv8tion.jda.api.entities.sticker.StickerPack;
import net.dv8tion.jda.api.entities.sticker.StickerSnowflake;
@@ -54,6 +56,7 @@
import net.dv8tion.jda.api.utils.*;
import net.dv8tion.jda.api.utils.cache.CacheFlag;
import net.dv8tion.jda.api.utils.cache.CacheView;
+import net.dv8tion.jda.api.utils.cache.ChannelCacheView;
import net.dv8tion.jda.api.utils.cache.SnowflakeCacheView;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
@@ -75,6 +78,7 @@
import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.*;
import net.dv8tion.jda.internal.utils.cache.AbstractCacheView;
+import net.dv8tion.jda.internal.utils.cache.ChannelCacheViewImpl;
import net.dv8tion.jda.internal.utils.cache.SnowflakeCacheViewImpl;
import net.dv8tion.jda.internal.utils.config.AuthorizationConfig;
import net.dv8tion.jda.internal.utils.config.MetaConfig;
@@ -101,16 +105,8 @@ public class JDAImpl implements JDA
protected final SnowflakeCacheViewImpl userCache = new SnowflakeCacheViewImpl<>(User.class, User::getName);
protected final SnowflakeCacheViewImpl guildCache = new SnowflakeCacheViewImpl<>(Guild.class, Guild::getName);
- protected final SnowflakeCacheViewImpl categories = new SnowflakeCacheViewImpl<>(Category.class, Channel::getName);
- protected final SnowflakeCacheViewImpl textChannelCache = new SnowflakeCacheViewImpl<>(TextChannel.class, Channel::getName);
- protected final SnowflakeCacheViewImpl newsChannelCache = new SnowflakeCacheViewImpl<>(NewsChannel.class, Channel::getName);
- protected final SnowflakeCacheViewImpl voiceChannelCache = new SnowflakeCacheViewImpl<>(VoiceChannel.class, Channel::getName);
- protected final SnowflakeCacheViewImpl stageChannelCache = new SnowflakeCacheViewImpl<>(StageChannel.class, Channel::getName);
- protected final SnowflakeCacheViewImpl threadChannelsCache = new SnowflakeCacheViewImpl<>(ThreadChannel.class, Channel::getName);
- protected final SnowflakeCacheViewImpl forumChannelsCache = new SnowflakeCacheViewImpl<>(ForumChannel.class, Channel::getName);
- protected final SnowflakeCacheViewImpl mediaChannelsCache = new SnowflakeCacheViewImpl<>(MediaChannel.class, Channel::getName);
- protected final SnowflakeCacheViewImpl privateChannelCache = new SnowflakeCacheViewImpl<>(PrivateChannel.class, Channel::getName);
- protected final LinkedList privateChannelLRU = new LinkedList<>();
+ protected final ChannelCacheViewImpl channelCache = new ChannelCacheViewImpl<>(Channel.class);
+ protected final ArrayDeque privateChannelLRU = new ArrayDeque<>();
protected final AbstractCacheView audioManagers = new CacheView.SimpleCacheView<>(AudioManager.class, m -> m.getGuild().getName());
@@ -269,7 +265,7 @@ public void usedPrivateChannel(long id)
if (privateChannelLRU.size() > 10) // This could probably be a config option
{
long removed = privateChannelLRU.removeLast();
- privateChannelCache.remove(removed);
+ channelCache.remove(ChannelType.PRIVATE, removed);
}
}
}
@@ -280,7 +276,8 @@ public void initRequester()
return;
RestRateLimiter rateLimiter = this.restConfig.getRateLimiterFactory().apply(
new RestRateLimiter.RateLimitConfig(
- this.threadConfig.getRateLimitPool(),
+ this.threadConfig.getRateLimitScheduler(),
+ this.threadConfig.getRateLimitElastic(),
getSessionController().getRateLimitHandle(),
this.sessionConfig.isRelativeRateLimit() && this.restConfig.isRelativeRateLimit()
));
@@ -412,7 +409,7 @@ else if (response.code == 401)
return;
}
- throw new InvalidTokenException("The provided token is invalid!");
+ throw new InvalidTokenException();
}
catch (Throwable error)
{
@@ -569,7 +566,7 @@ public int cancelRequests()
@Override
public ScheduledExecutorService getRateLimitPool()
{
- return threadConfig.getRateLimitPool();
+ return threadConfig.getRateLimitScheduler();
}
@Nonnull
@@ -618,9 +615,9 @@ public List getMutualGuilds(@Nonnull Collection users)
Checks.notNull(users, "users");
for(User u : users)
Checks.notNull(u, "All users");
- return Collections.unmodifiableList(getGuilds().stream()
+ return getGuilds().stream()
.filter(guild -> users.stream().allMatch(guild::isMember))
- .collect(Collectors.toList()));
+ .collect(Helpers.toUnmodifiableList());
}
@Nonnull
@@ -727,67 +724,74 @@ public SnowflakeCacheView getScheduledEventCache()
return CacheView.allSnowflakes(() -> guildCache.stream().map(Guild::getScheduledEventCache));
}
+ @Nonnull
+ @Override
+ public ChannelCacheView getChannelCache()
+ {
+ return channelCache;
+ }
+
@Nonnull
@Override
public SnowflakeCacheView getCategoryCache()
{
- return categories;
+ return channelCache.ofType(Category.class);
}
@Nonnull
@Override
public SnowflakeCacheView getTextChannelCache()
{
- return textChannelCache;
+ return channelCache.ofType(TextChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getNewsChannelCache()
{
- return newsChannelCache;
+ return channelCache.ofType(NewsChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getVoiceChannelCache()
{
- return voiceChannelCache;
+ return channelCache.ofType(VoiceChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getStageChannelCache()
{
- return stageChannelCache;
+ return channelCache.ofType(StageChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getThreadChannelCache()
{
- return threadChannelsCache;
+ return channelCache.ofType(ThreadChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getForumChannelCache()
{
- return forumChannelsCache;
+ return channelCache.ofType(ForumChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getMediaChannelCache()
{
- return mediaChannelsCache;
+ return channelCache.ofType(MediaChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getPrivateChannelCache()
{
- return privateChannelCache;
+ return channelCache.ofType(PrivateChannel.class);
}
@Override
@@ -805,6 +809,25 @@ public PrivateChannel getPrivateChannelById(long id)
return channel;
}
+ @Override
+ public T getChannelById(@Nonnull Class type, long id)
+ {
+ return channelCache.ofType(type).getElementById(id);
+ }
+
+ @Override
+ public GuildChannel getGuildChannelById(long id)
+ {
+ return channelCache.ofType(GuildChannel.class).getElementById(id);
+ }
+
+ @Override
+ public GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id)
+ {
+ Channel channel = channelCache.getElementById(type, id);
+ return channel instanceof GuildChannel ? (GuildChannel) channel : null;
+ }
+
@Nonnull
@Override
public CacheRestAction openPrivateChannelById(long userId)
@@ -1251,49 +1274,9 @@ public SnowflakeCacheViewImpl getGuildsView()
return guildCache;
}
- public SnowflakeCacheViewImpl getCategoriesView()
- {
- return categories;
- }
-
- public SnowflakeCacheViewImpl getTextChannelsView()
- {
- return textChannelCache;
- }
-
- public SnowflakeCacheViewImpl getNewsChannelView()
- {
- return newsChannelCache;
- }
-
- public SnowflakeCacheViewImpl getVoiceChannelsView()
- {
- return voiceChannelCache;
- }
-
- public SnowflakeCacheViewImpl getStageChannelView()
- {
- return stageChannelCache;
- }
-
- public SnowflakeCacheViewImpl getThreadChannelsView()
- {
- return threadChannelsCache;
- }
-
- public SnowflakeCacheViewImpl getForumChannelsView()
- {
- return forumChannelsCache;
- }
-
- public SnowflakeCacheViewImpl getMediaChannelsView()
- {
- return mediaChannelsCache;
- }
-
- public SnowflakeCacheViewImpl getPrivateChannelsView()
+ public ChannelCacheViewImpl getChannelsView()
{
- return privateChannelCache;
+ return this.channelCache;
}
public AbstractCacheView getAudioManagersView()
diff --git a/src/main/java/net/dv8tion/jda/internal/entities/ApplicationInfoImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/ApplicationInfoImpl.java
index c67c8591808..c7bd9b81a16 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/ApplicationInfoImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/ApplicationInfoImpl.java
@@ -47,6 +47,9 @@ public class ApplicationInfoImpl implements ApplicationInfo
private final User owner;
private final ApplicationTeam team;
private final List tags;
+ private final List redirectUris;
+ private final String interactionsEndpointUrl;
+ private final String roleConnectionsVerificationUrl;
private final String customAuthUrl;
private final long defaultAuthUrlPerms;
private final List defaultAuthUrlScopes;
@@ -54,7 +57,8 @@ public class ApplicationInfoImpl implements ApplicationInfo
public ApplicationInfoImpl(JDA api, String description, boolean doesBotRequireCodeGrant, String iconId, long id, long flags,
boolean isBotPublic, String name, String termsOfServiceUrl, String privacyPolicyUrl, User owner, ApplicationTeam team,
- List tags, String customAuthUrl, long defaultAuthUrlPerms, List defaultAuthUrlScopes)
+ List tags, List redirectUris, String interactionsEndpointUrl, String roleConnectionsVerificationUrl,
+ String customAuthUrl, long defaultAuthUrlPerms, List defaultAuthUrlScopes)
{
this.api = api;
this.description = description;
@@ -69,6 +73,9 @@ public ApplicationInfoImpl(JDA api, String description, boolean doesBotRequireCo
this.owner = owner;
this.team = team;
this.tags = Collections.unmodifiableList(tags);
+ this.redirectUris = Collections.unmodifiableList(redirectUris);
+ this.interactionsEndpointUrl = interactionsEndpointUrl;
+ this.roleConnectionsVerificationUrl = roleConnectionsVerificationUrl;
this.customAuthUrl = customAuthUrl;
this.defaultAuthUrlPerms = defaultAuthUrlPerms;
this.defaultAuthUrlScopes = Collections.unmodifiableList(defaultAuthUrlScopes);
@@ -207,6 +214,27 @@ public List getTags()
return tags;
}
+ @Nonnull
+ @Override
+ public List getRedirectUris()
+ {
+ return redirectUris;
+ }
+
+ @Nullable
+ @Override
+ public String getInteractionsEndpointUrl()
+ {
+ return interactionsEndpointUrl;
+ }
+
+ @Nullable
+ @Override
+ public String getRoleConnectionsVerificationUrl()
+ {
+ return roleConnectionsVerificationUrl;
+ }
+
@Nullable
@Override
public String getCustomAuthorizationUrl()
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 09b06f2ad8e..6b30eab45b2 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
@@ -29,6 +29,7 @@
import net.dv8tion.jda.api.entities.Guild.Timeout;
import net.dv8tion.jda.api.entities.Guild.VerificationLevel;
import net.dv8tion.jda.api.entities.MessageEmbed.*;
+import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer;
@@ -69,6 +70,7 @@
import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.JDALogger;
import net.dv8tion.jda.internal.utils.UnlockHook;
+import net.dv8tion.jda.internal.utils.cache.ChannelCacheViewImpl;
import net.dv8tion.jda.internal.utils.cache.MemberCacheViewImpl;
import net.dv8tion.jda.internal.utils.cache.SnowflakeCacheViewImpl;
import net.dv8tion.jda.internal.utils.cache.SortedSnowflakeCacheViewImpl;
@@ -1074,21 +1076,20 @@ public Category createCategory(GuildImpl guild, DataObject json, long guildId)
{
boolean playbackCache = false;
final long id = json.getLong("id");
- CategoryImpl channel = (CategoryImpl) getJDA().getCategoriesView().get(id);
+ CategoryImpl channel = (CategoryImpl) getJDA().getCategoryById(id);
if (channel == null)
{
if (guild == null)
guild = (GuildImpl) getJDA().getGuildsView().get(guildId);
- SnowflakeCacheViewImpl
- guildCategoryView = guild.getCategoriesView(),
- categoryView = getJDA().getCategoriesView();
+ ChannelCacheViewImpl guildView = guild.getChannelView();
+ ChannelCacheViewImpl globalView = getJDA().getChannelsView();
try (
- UnlockHook glock = guildCategoryView.writeLock();
- UnlockHook jlock = categoryView.writeLock())
+ UnlockHook glock = guildView.writeLock();
+ UnlockHook jlock = globalView.writeLock())
{
channel = new CategoryImpl(id, guild);
- guildCategoryView.getMap().put(id, channel);
- playbackCache = categoryView.getMap().put(id, channel) == null;
+ guildView.put(channel);
+ playbackCache = globalView.put(channel) == null;
}
}
@@ -1112,21 +1113,20 @@ public TextChannel createTextChannel(GuildImpl guildObj, DataObject json, long g
{
boolean playbackCache = false;
final long id = json.getLong("id");
- TextChannelImpl channel = (TextChannelImpl) getJDA().getTextChannelsView().get(id);
+ TextChannelImpl channel = (TextChannelImpl) getJDA().getTextChannelById(id);
if (channel == null)
{
if (guildObj == null)
guildObj = (GuildImpl) getJDA().getGuildsView().get(guildId);
- SnowflakeCacheViewImpl
- guildTextView = guildObj.getTextChannelsView(),
- textView = getJDA().getTextChannelsView();
+ ChannelCacheViewImpl guildView = guildObj.getChannelView();
+ ChannelCacheViewImpl globalView = getJDA().getChannelsView();
try (
- UnlockHook glock = guildTextView.writeLock();
- UnlockHook jlock = textView.writeLock())
+ UnlockHook glock = guildView.writeLock();
+ UnlockHook jlock = globalView.writeLock())
{
channel = new TextChannelImpl(id, guildObj);
- guildTextView.getMap().put(id, channel);
- playbackCache = textView.getMap().put(id, channel) == null;
+ guildView.put(channel);
+ playbackCache = globalView.put(channel) == null;
}
}
@@ -1156,21 +1156,20 @@ public NewsChannel createNewsChannel(GuildImpl guildObj, DataObject json, long g
{
boolean playbackCache = false;
final long id = json.getLong("id");
- NewsChannelImpl channel = (NewsChannelImpl) getJDA().getNewsChannelView().get(id);
+ NewsChannelImpl channel = (NewsChannelImpl) getJDA().getNewsChannelById(id);
if (channel == null)
{
if (guildObj == null)
guildObj = (GuildImpl) getJDA().getGuildsView().get(guildId);
- SnowflakeCacheViewImpl
- guildNewsView = guildObj.getNewsChannelView(),
- newsView = getJDA().getNewsChannelView();
+ ChannelCacheViewImpl guildView = guildObj.getChannelView();
+ ChannelCacheViewImpl globalView = getJDA().getChannelsView();
try (
- UnlockHook glock = guildNewsView.writeLock();
- UnlockHook jlock = newsView.writeLock())
+ UnlockHook glock = guildView.writeLock();
+ UnlockHook jlock = globalView.writeLock())
{
channel = new NewsChannelImpl(id, guildObj);
- guildNewsView.getMap().put(id, channel);
- playbackCache = newsView.getMap().put(id, channel) == null;
+ guildView.put(channel);
+ playbackCache = globalView.put(channel) == null;
}
}
@@ -1197,21 +1196,20 @@ public VoiceChannel createVoiceChannel(GuildImpl guild, DataObject json, long gu
{
boolean playbackCache = false;
final long id = json.getLong("id");
- VoiceChannelImpl channel = ((VoiceChannelImpl) getJDA().getVoiceChannelsView().get(id));
+ VoiceChannelImpl channel = (VoiceChannelImpl) getJDA().getVoiceChannelById(id);
if (channel == null)
{
if (guild == null)
guild = (GuildImpl) getJDA().getGuildsView().get(guildId);
- SnowflakeCacheViewImpl
- guildVoiceView = guild.getVoiceChannelsView(),
- voiceView = getJDA().getVoiceChannelsView();
+ ChannelCacheViewImpl guildView = guild.getChannelView();
+ ChannelCacheViewImpl globalView = getJDA().getChannelsView();
try (
- UnlockHook vlock = guildVoiceView.writeLock();
- UnlockHook jlock = voiceView.writeLock())
+ UnlockHook glock = guildView.writeLock();
+ UnlockHook jlock = globalView.writeLock())
{
channel = new VoiceChannelImpl(id, guild);
- guildVoiceView.getMap().put(id, channel);
- playbackCache = voiceView.getMap().put(id, channel) == null;
+ guildView.put(channel);
+ playbackCache = globalView.put(channel) == null;
}
}
@@ -1219,6 +1217,7 @@ public VoiceChannel createVoiceChannel(GuildImpl guild, DataObject json, long gu
.setParentCategory(json.getLong("parent_id", 0))
.setLatestMessageIdLong(json.getLong("last_message_id", 0))
.setName(json.getString("name"))
+ .setStatus(json.getString("status", ""))
.setPosition(json.getInt("position"))
.setUserLimit(json.getInt("user_limit"))
.setNSFW(json.getBoolean("nsfw"))
@@ -1242,21 +1241,20 @@ public StageChannel createStageChannel(GuildImpl guild, DataObject json, long gu
{
boolean playbackCache = false;
final long id = json.getLong("id");
- StageChannelImpl channel = ((StageChannelImpl) getJDA().getStageChannelView().get(id));
+ StageChannelImpl channel = (StageChannelImpl) getJDA().getStageChannelById(id);
if (channel == null)
{
if (guild == null)
guild = (GuildImpl) getJDA().getGuildsView().get(guildId);
- SnowflakeCacheViewImpl
- guildStageView = guild.getStageChannelsView(),
- stageView = getJDA().getStageChannelView();
+ ChannelCacheViewImpl guildView = guild.getChannelView();
+ ChannelCacheViewImpl globalView = getJDA().getChannelsView();
try (
- UnlockHook vlock = guildStageView.writeLock();
- UnlockHook jlock = stageView.writeLock())
+ UnlockHook glock = guildView.writeLock();
+ UnlockHook jlock = globalView.writeLock())
{
channel = new StageChannelImpl(id, guild);
- guildStageView.getMap().put(id, channel);
- playbackCache = stageView.getMap().put(id, channel) == null;
+ guildView.put(channel);
+ playbackCache = globalView.put(channel) == null;
}
}
@@ -1302,12 +1300,11 @@ public ThreadChannel createThreadChannel(GuildImpl guild, DataObject json, long
if (parent == null)
throw new IllegalArgumentException(MISSING_CHANNEL);
- ThreadChannelImpl channel = ((ThreadChannelImpl) getJDA().getThreadChannelsView().get(id));
+ ThreadChannelImpl channel = ((ThreadChannelImpl) getJDA().getThreadChannelById(id));
if (channel == null)
{
- SnowflakeCacheViewImpl
- guildThreadView = guild.getThreadChannelsView(),
- threadView = getJDA().getThreadChannelsView();
+ ChannelCacheViewImpl guildThreadView = guild.getChannelView();
+ ChannelCacheViewImpl threadView = getJDA().getChannelsView();
try (
UnlockHook vlock = guildThreadView.writeLock();
UnlockHook jlock = threadView.writeLock())
@@ -1315,8 +1312,8 @@ public ThreadChannel createThreadChannel(GuildImpl guild, DataObject json, long
channel = new ThreadChannelImpl(id, guild, type);
if (modifyCache)
{
- guildThreadView.getMap().put(id, channel);
- playbackCache = threadView.getMap().put(id, channel) == null;
+ guildThreadView.put(channel);
+ playbackCache = threadView.put(channel) == null;
}
}
}
@@ -1389,21 +1386,20 @@ public ForumChannel createForumChannel(GuildImpl guild, DataObject json, long gu
{
boolean playbackCache = false;
final long id = json.getLong("id");
- ForumChannelImpl channel = (ForumChannelImpl) getJDA().getForumChannelsView().get(id);
+ ForumChannelImpl channel = (ForumChannelImpl) getJDA().getForumChannelById(id);
if (channel == null)
{
if (guild == null)
guild = (GuildImpl) getJDA().getGuildsView().get(guildId);
- SnowflakeCacheViewImpl
- guildView = guild.getForumChannelsView(),
- globalView = getJDA().getForumChannelsView();
+ ChannelCacheViewImpl guildView = guild.getChannelView();
+ ChannelCacheViewImpl globalView = getJDA().getChannelsView();
try (
- UnlockHook vlock = guildView.writeLock();
+ UnlockHook glock = guildView.writeLock();
UnlockHook jlock = globalView.writeLock())
{
channel = new ForumChannelImpl(id, guild);
- guildView.getMap().put(id, channel);
- playbackCache = globalView.getMap().put(id, channel) == null;
+ guildView.put(channel);
+ playbackCache = globalView.put(channel) == null;
}
}
@@ -1442,21 +1438,20 @@ public MediaChannel createMediaChannel(GuildImpl guild, DataObject json, long gu
{
boolean playbackCache = false;
final long id = json.getLong("id");
- MediaChannelImpl channel = (MediaChannelImpl) getJDA().getMediaChannelsView().get(id);
+ MediaChannelImpl channel = (MediaChannelImpl) getJDA().getMediaChannelById(id);
if (channel == null)
{
if (guild == null)
guild = (GuildImpl) getJDA().getGuildsView().get(guildId);
- SnowflakeCacheViewImpl
- guildView = guild.getMediaChannelsView(),
- globalView = getJDA().getMediaChannelsView();
+ ChannelCacheViewImpl guildView = guild.getChannelView();
+ ChannelCacheViewImpl globalView = getJDA().getChannelsView();
try (
- UnlockHook vlock = guildView.writeLock();
+ UnlockHook glock = guildView.writeLock();
UnlockHook jlock = globalView.writeLock())
{
channel = new MediaChannelImpl(id, guild);
- guildView.getMap().put(id, channel);
- playbackCache = globalView.getMap().put(id, channel) == null;
+ guildView.put(channel);
+ playbackCache = globalView.put(channel) == null;
}
}
@@ -1555,11 +1550,8 @@ public PrivateChannel createPrivateChannel(DataObject json, UserImpl user)
private void cachePrivateChannel(PrivateChannelImpl priv)
{
- SnowflakeCacheViewImpl privateView = getJDA().getPrivateChannelsView();
- try (UnlockHook hook = privateView.writeLock())
- {
- privateView.getMap().put(priv.getIdLong(), priv);
- }
+ ChannelCacheViewImpl privateView = getJDA().getChannelsView();
+ privateView.put(priv);
api.usedPrivateChannel(priv.getIdLong());
getJDA().getEventCache().playbackCache(EventCache.Type.CHANNEL, priv.getIdLong());
}
@@ -1679,7 +1671,7 @@ public ReceivedMessage createMessageWithLookup(DataObject json, @Nullable Guild
if (guild == null)
return createMessage0(json, createPrivateChannelByMessage(json), null, modifyCache);
//If we know that the message was sent in a guild, we can use the guild to resolve the channel directly
- MessageChannel channel = guild.getChannelById(MessageChannel.class, json.getUnsignedLong("channel_id"));
+ MessageChannel channel = guild.getChannelById(GuildMessageChannel.class, json.getUnsignedLong("channel_id"));
// if (channel == null)
// throw new IllegalArgumentException(MISSING_CHANNEL);
return createMessage0(json, channel, (GuildImpl) guild, modifyCache);
@@ -2484,6 +2476,11 @@ public ApplicationInfo createApplicationInfo(DataObject object)
final List tags = object.optArray("tags").orElseGet(DataArray::empty)
.stream(DataArray::getString)
.collect(Collectors.toList());
+ final List redirectUris = object.optArray("redirect_uris").orElseGet(DataArray::empty)
+ .stream(DataArray::getString)
+ .collect(Collectors.toList());
+ final String interactionsEndpointUrl = object.getString("interactions_endpoint_url", null);
+ final String roleConnectionsVerificationUrl = object.getString("role_connections_verification_url", null);
final Optional installParams = object.optObject("install_params");
@@ -2496,7 +2493,8 @@ public ApplicationInfo createApplicationInfo(DataObject object)
.orElse(Collections.emptyList());
return new ApplicationInfoImpl(getJDA(), description, doesBotRequireCodeGrant, iconId, id, flags, isBotPublic, name,
- termsOfServiceUrl, privacyPolicyUrl, owner, team, tags, customAuthUrl, defaultAuthUrlPerms, defaultAuthUrlScopes);
+ termsOfServiceUrl, privacyPolicyUrl, owner, team, tags, redirectUris, interactionsEndpointUrl,
+ roleConnectionsVerificationUrl, customAuthUrl, defaultAuthUrlPerms, defaultAuthUrlScopes);
}
public ApplicationTeam createApplicationTeam(DataObject object)
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 11ecc5bc7f2..c13416bf144 100644
--- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
+++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
@@ -27,6 +27,7 @@
import net.dv8tion.jda.api.entities.automod.build.AutoModRuleData;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.ChannelType;
+import net.dv8tion.jda.api.entities.channel.attribute.ICategorizableChannel;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
import net.dv8tion.jda.api.entities.channel.concrete.*;
import net.dv8tion.jda.api.entities.channel.middleman.AudioChannel;
@@ -77,10 +78,7 @@
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.UnlockHook;
-import net.dv8tion.jda.internal.utils.cache.AbstractCacheView;
-import net.dv8tion.jda.internal.utils.cache.MemberCacheViewImpl;
-import net.dv8tion.jda.internal.utils.cache.SnowflakeCacheViewImpl;
-import net.dv8tion.jda.internal.utils.cache.SortedSnowflakeCacheViewImpl;
+import net.dv8tion.jda.internal.utils.cache.*;
import net.dv8tion.jda.internal.utils.concurrent.task.GatewayTask;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
@@ -103,15 +101,8 @@ public class GuildImpl implements Guild
private final long id;
private final JDAImpl api;
- private final SortedSnowflakeCacheViewImpl categoryCache = new SortedSnowflakeCacheViewImpl<>(Category.class, Channel::getName, Comparator.naturalOrder());
private final SortedSnowflakeCacheViewImpl scheduledEventCache = new SortedSnowflakeCacheViewImpl<>(ScheduledEvent.class, ScheduledEvent::getName, Comparator.naturalOrder());
- private final SortedSnowflakeCacheViewImpl voiceChannelCache = new SortedSnowflakeCacheViewImpl<>(VoiceChannel.class, Channel::getName, Comparator.naturalOrder());
- private final SortedSnowflakeCacheViewImpl textChannelCache = new SortedSnowflakeCacheViewImpl<>(TextChannel.class, Channel::getName, Comparator.naturalOrder());
- private final SortedSnowflakeCacheViewImpl newsChannelCache = new SortedSnowflakeCacheViewImpl<>(NewsChannel.class, Channel::getName, Comparator.naturalOrder());
- private final SortedSnowflakeCacheViewImpl stageChannelCache = new SortedSnowflakeCacheViewImpl<>(StageChannel.class, Channel::getName, Comparator.naturalOrder());
- private final SortedSnowflakeCacheViewImpl threadChannelCache = new SortedSnowflakeCacheViewImpl<>(ThreadChannel.class, Channel::getName, Comparator.naturalOrder());
- private final SortedSnowflakeCacheViewImpl forumChannelCache = new SortedSnowflakeCacheViewImpl<>(ForumChannel.class, Channel::getName, Comparator.naturalOrder());
- private final SortedSnowflakeCacheViewImpl mediaChannelCache = new SortedSnowflakeCacheViewImpl<>(MediaChannel.class, Channel::getName, Comparator.naturalOrder());
+ private final SortedChannelCacheViewImpl channelCache = new SortedChannelCacheViewImpl<>(GuildChannel.class);
private final SortedSnowflakeCacheViewImpl roleCache = new SortedSnowflakeCacheViewImpl<>(Role.class, Role::getName, Comparator.reverseOrder());
private final SnowflakeCacheViewImpl emojicache = new SnowflakeCacheViewImpl<>(RichCustomEmoji.class, RichCustomEmoji::getName);
private final SnowflakeCacheViewImpl stickerCache = new SnowflakeCacheViewImpl<>(GuildSticker.class, GuildSticker::getName);
@@ -159,57 +150,12 @@ public void invalidate()
{
//Remove everything from global cache
// this prevents some race-conditions for getting audio managers from guilds
- SnowflakeCacheViewImpl guildView = getJDA().getGuildsView();
- SnowflakeCacheViewImpl stageView = getJDA().getStageChannelView();
- SnowflakeCacheViewImpl textView = getJDA().getTextChannelsView();
- SnowflakeCacheViewImpl threadView = getJDA().getThreadChannelsView();
- SnowflakeCacheViewImpl newsView = getJDA().getNewsChannelView();
- SnowflakeCacheViewImpl forumView = getJDA().getForumChannelsView();
- SnowflakeCacheViewImpl mediaView = getJDA().getMediaChannelsView();
- SnowflakeCacheViewImpl voiceView = getJDA().getVoiceChannelsView();
- SnowflakeCacheViewImpl categoryView = getJDA().getCategoriesView();
-
- guildView.remove(id);
-
- try (UnlockHook hook = stageView.writeLock())
- {
- getStageChannelCache()
- .forEachUnordered(chan -> stageView.getMap().remove(chan.getIdLong()));
- }
- try (UnlockHook hook = textView.writeLock())
- {
- getTextChannelCache()
- .forEachUnordered(chan -> textView.getMap().remove(chan.getIdLong()));
- }
- try (UnlockHook hook = threadView.writeLock())
- {
- getThreadChannelsView()
- .forEachUnordered(chan -> threadView.getMap().remove(chan.getIdLong()));
- }
- try (UnlockHook hook = newsView.writeLock())
- {
- getNewsChannelCache()
- .forEachUnordered(chan -> newsView.getMap().remove(chan.getIdLong()));
- }
- try (UnlockHook hook = forumView.writeLock())
- {
- getForumChannelCache()
- .forEachUnordered(chan -> forumView.getMap().remove(chan.getIdLong()));
- }
- try (UnlockHook hook = mediaView.writeLock())
- {
- getMediaChannelsView()
- .forEachUnordered(chan -> mediaView.getMap().remove(chan.getIdLong()));
- }
- try (UnlockHook hook = voiceView.writeLock())
- {
- getVoiceChannelCache()
- .forEachUnordered(chan -> voiceView.getMap().remove(chan.getIdLong()));
- }
- try (UnlockHook hook = categoryView.writeLock())
+ getJDA().getGuildsView().remove(id);
+
+ ChannelCacheViewImpl channelsView = getJDA().getChannelsView();
+ try (UnlockHook hook = channelsView.writeLock())
{
- getCategoryCache()
- .forEachUnordered(chan -> categoryView.getMap().remove(chan.getIdLong()));
+ getChannels().forEach(channel -> channelsView.remove(channel.getType(), channel.getIdLong()));
}
// Clear audio connection
@@ -245,71 +191,28 @@ public void invalidate()
public void uncacheChannel(GuildChannel channel, boolean keepThreads)
{
long id = channel.getIdLong();
- switch (channel.getType())
- {
- case TEXT:
- api.getTextChannelsView().remove(id);
- this.getTextChannelsView().remove(id);
- break;
- case NEWS:
- api.getNewsChannelView().remove(id);
- this.getNewsChannelView().remove(id);
- break;
- case MEDIA:
- api.getMediaChannelsView().remove(id);
- this.getMediaChannelsView().remove(id);
- break;
- case FORUM:
- api.getForumChannelsView().remove(id);
- this.getForumChannelsView().remove(id);
- break;
- case VOICE:
- api.getVoiceChannelsView().remove(id);
- this.getVoiceChannelsView().remove(id);
- break;
- case STAGE:
- api.getStageChannelView().remove(id);
- this.getStageChannelsView().remove(id);
- break;
- case CATEGORY:
- api.getCategoriesView().remove(id);
- this.getCategoriesView().remove(id);
- break;
- case GUILD_NEWS_THREAD:
- case GUILD_PUBLIC_THREAD:
- case GUILD_PRIVATE_THREAD:
- api.getThreadChannelsView().remove(id);
- this.getThreadChannelsView().remove(id);
- break;
- }
+
+ // Enforce idempotence by checking the channel was in cache
+ // If the channel was not in cache, there is no reason to cleanup anything else.
+ // This idempotency makes sure that new cache is never affected by old cache
+ if (channelCache.remove(channel.getType(), id) == null)
+ return;
+
+ api.getChannelsView().remove(channel.getType(), id);
if (!keepThreads && channel instanceof IThreadContainer)
{
// Remove dangling threads
- SortedSnowflakeCacheViewImpl localView = this.getThreadChannelsView();
- SnowflakeCacheViewImpl globalView = api.getThreadChannelsView();
+ SortedChannelCacheViewImpl localView = this.getChannelView();
+ ChannelCacheViewImpl globalView = api.getChannelsView();
Predicate predicate = thread -> channel.equals(thread.getParentChannel());
try (UnlockHook hook1 = localView.writeLock(); UnlockHook hook2 = globalView.writeLock())
{
- localView.getMap().valueCollection().removeIf(predicate);
- globalView.getMap().valueCollection().removeIf(predicate);
+ localView.removeIf(ThreadChannel.class, predicate);
+ globalView.removeIf(ThreadChannel.class, predicate);
}
}
-
- // This might be too presumptuous, Channel#getParent still returns null regardless if the category is uncached
-// if (channel instanceof Category)
-// {
-// for (Channel chan : guild.getChannels())
-// {
-// if (!(chan instanceof ICategorizableChannelMixin>))
-// continue;
-//
-// ICategorizableChannelMixin> categoizable = (ICategorizableChannelMixin>) chan;
-// if (categoizable.getParentCategoryIdLong() == id)
-// categoizable.setParentCategory(0L);
-// }
-// }
}
@Nonnull
@@ -633,7 +536,7 @@ public List getBoosters()
return memberCache.applyStream((members) ->
members.filter(m -> m.getTimeBoosted() != null)
.sorted(Comparator.comparing(Member::getTimeBoosted))
- .collect(Collectors.toList()));
+ .collect(Helpers.toUnmodifiableList()));
}
@Override
@@ -819,56 +722,76 @@ public SortedSnowflakeCacheView getScheduledEventCache()
@Override
public SortedSnowflakeCacheView getCategoryCache()
{
- return categoryCache;
+ return channelCache.ofType(Category.class);
}
@Nonnull
@Override
public SortedSnowflakeCacheView getTextChannelCache()
{
- return textChannelCache;
+ return channelCache.ofType(TextChannel.class);
}
@Nonnull
@Override
public SortedSnowflakeCacheView getNewsChannelCache()
{
- return newsChannelCache;
+ return channelCache.ofType(NewsChannel.class);
}
@Nonnull
@Override
public SortedSnowflakeCacheView getVoiceChannelCache()
{
- return voiceChannelCache;
+ return channelCache.ofType(VoiceChannel.class);
}
@Nonnull
@Override
public SortedSnowflakeCacheView getForumChannelCache()
{
- return forumChannelCache;
+ return channelCache.ofType(ForumChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getMediaChannelCache()
{
- return mediaChannelCache;
+ return channelCache.ofType(MediaChannel.class);
}
@Nonnull
@Override
public SortedSnowflakeCacheView