diff --git a/settings.gradle.kts b/settings.gradle.kts index a5bc83e4b..f04e7c8bd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -42,7 +42,7 @@ fun VersionCatalogBuilder.common() { library("sentry", "io.sentry", "sentry-logback").version("5.4.0") library("org-json", "org.json", "json").version("20220924") - library("jda", "net.dv8tion", "JDA").version("5.0.0-beta.18") + library("jda", "net.dv8tion", "JDA").version("5.0.0-beta.20") library("trove", "net.sf.trove4j", "trove4j").version("3.0.3") @@ -94,7 +94,7 @@ fun VersionCatalogBuilder.database() { fun VersionCatalogBuilder.voice() { // library("lavalink-client", "dev.arbjerg", "lavalink-client").version("64f8b44e8164f5873ab107f453b58b1a99a8bc13-SNAPSHOT") - library("lavalink-client", "dev.arbjerg", "lavalink-client").version("2.2.0") + library("lavalink-client", "dev.arbjerg", "lavalink-client").version("2.3.1") } fun VersionCatalogBuilder.dashboard() { diff --git a/shared/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java b/shared/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java new file mode 100644 index 000000000..b814c393f --- /dev/null +++ b/shared/src/main/java/net/dv8tion/jda/api/sharding/ShardManager.java @@ -0,0 +1,1180 @@ +// TODO: TEMP hack until JDA updates this file +/* + * 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.sharding; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDA.Status; +import net.dv8tion.jda.api.OnlineStatus; +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.attribute.IGuildChannelContainer; +import net.dv8tion.jda.api.entities.channel.concrete.*; +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji; +import net.dv8tion.jda.api.exceptions.InvalidTokenException; +import net.dv8tion.jda.api.requests.GatewayIntent; +import net.dv8tion.jda.api.requests.RestAction; +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; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.stream.Collectors; + +/** + * This class acts as a manager for multiple shards. + * It contains several methods to make your life with sharding easier. + * + *
Custom implementations may not support all methods and throw + * {@link java.lang.UnsupportedOperationException UnsupportedOperationExceptions} instead. + * + * @since 3.4 + * @author Aljoscha Grebe + */ +public interface ShardManager extends IGuildChannelContainer +{ + /** + * Adds all provided listeners to the event-listeners that will be used to handle events. + * + *

Note: when using the {@link net.dv8tion.jda.api.hooks.InterfacedEventManager InterfacedEventListener} (default), + * the given listener must be an instance of {@link net.dv8tion.jda.api.hooks.EventListener EventListener}! + * + * @param listeners + * The listener(s) which will react to events. + * + * @throws java.lang.IllegalArgumentException + * If either listeners or one of it's objects is {@code null}. + */ + default void addEventListener(@Nonnull final Object... listeners) + { + Checks.noneNull(listeners, "listeners"); + this.getShardCache().forEach(jda -> jda.addEventListener(listeners)); + } + + /** + * Removes all provided listeners from the event-listeners and no longer uses them to handle events. + * + * @param listeners + * The listener(s) to be removed. + * + * @throws java.lang.IllegalArgumentException + * If either listeners or one of it's objects is {@code null}. + */ + default void removeEventListener(@Nonnull final Object... listeners) + { + Checks.noneNull(listeners, "listeners"); + this.getShardCache().forEach(jda -> jda.removeEventListener(listeners)); + } + + /** + * Adds listeners provided by the listener provider to each shard to the event-listeners that will be used to handle events. + * The listener provider gets a shard id applied and is expected to return a listener. + * + *

Note: when using the {@link net.dv8tion.jda.api.hooks.InterfacedEventManager InterfacedEventListener} (default), + * the given listener must be an instance of {@link net.dv8tion.jda.api.hooks.EventListener EventListener}! + * + * @param eventListenerProvider + * The provider of listener(s) which will react to events. + * + * @throws java.lang.IllegalArgumentException + * If the provided listener provider or any of the listeners or provides are {@code null}. + */ + default void addEventListeners(@Nonnull final IntFunction eventListenerProvider) + { + Checks.notNull(eventListenerProvider, "event listener provider"); + this.getShardCache().forEach(jda -> + { + Object listener = eventListenerProvider.apply(jda.getShardInfo().getShardId()); + if (listener != null) jda.addEventListener(listener); + }); + } + + /** + * Remove listeners from shards by their id. + * The provider takes shard ids, and returns a collection of listeners that shall be removed from the respective + * shards. + * + * @param eventListenerProvider + * Gets shard ids applied and is expected to return a collection of listeners that shall be removed from + * the respective shards + * + * @throws java.lang.IllegalArgumentException + * If the provided event listeners provider is {@code null}. + */ + default void removeEventListeners(@Nonnull final IntFunction> eventListenerProvider) + { + Checks.notNull(eventListenerProvider, "event listener provider"); + this.getShardCache().forEach(jda -> + jda.removeEventListener(eventListenerProvider.apply(jda.getShardInfo().getShardId())) + ); + } + + /** + * Remove a listener provider. This will stop further created / restarted shards from getting a listener added by + * that provider. + * + *

Default is a no-op for backwards compatibility, see implementations like + * {@link DefaultShardManager#removeEventListenerProvider(IntFunction)} for actual code + * + * @param eventListenerProvider + * The provider of listeners that shall be removed. + * + * @throws java.lang.IllegalArgumentException + * If the provided listener provider is {@code null}. + */ + default void removeEventListenerProvider(@Nonnull IntFunction eventListenerProvider) + { + } + + /** + * Returns the amount of shards queued for (re)connecting. + * + * @return The amount of shards queued for (re)connecting. + */ + int getShardsQueued(); + + /** + * Returns the amount of running shards. + * + * @return The amount of running shards. + */ + default int getShardsRunning() + { + return (int) this.getShardCache().size(); + } + + /** + * Returns the amount of shards managed by this {@link net.dv8tion.jda.api.sharding.ShardManager ShardManager}. + * This includes shards currently queued for a restart. + * + * @return The managed amount of shards. + */ + default int getShardsTotal() + { + return this.getShardsQueued() + this.getShardsRunning(); + } + + /** + * The {@link GatewayIntent GatewayIntents} for the JDA sessions of this shard manager. + * + * @return {@link EnumSet} of active gateway intents + */ + @Nonnull + default EnumSet getGatewayIntents() + { + //noinspection ConstantConditions + return getShardCache().applyStream((stream) -> + stream.map(JDA::getGatewayIntents) + .findAny() + .orElse(EnumSet.noneOf(GatewayIntent.class))); + } + + /** + * Used to access application details of this bot. + *
Since this is the same for every shard it picks {@link JDA#retrieveApplicationInfo()} from any shard. + * + * @throws java.lang.IllegalStateException + * If there is no running shard + * + * @return The Application registry for this bot. + */ + @Nonnull + default RestAction retrieveApplicationInfo() + { + return this.getShardCache().stream() + .findAny() + .orElseThrow(() -> new IllegalStateException("no active shards")) + .retrieveApplicationInfo(); + } + + /** + * The average time in milliseconds between all shards that discord took to respond to our last heartbeat. + * This roughly represents the WebSocket ping of this session. If there are no shards running, this will return {@code -1}. + * + *

{@link net.dv8tion.jda.api.requests.RestAction RestAction} request times do not + * correlate to this value! + * + * @return The average time in milliseconds between heartbeat and the heartbeat ack response + */ + default double getAverageGatewayPing() + { + return this.getShardCache() + .stream() + .mapToLong(JDA::getGatewayPing) + .filter(ping -> ping != -1) + .average() + .orElse(-1D); + } + + /** + * {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} of + * all cached {@link net.dv8tion.jda.api.entities.channel.concrete.Category Categories} visible to this ShardManager instance. + * + * @return {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} + */ + @Nonnull + default SnowflakeCacheView getCategoryCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getCategoryCache)); + } + + /** + * Retrieves a custom emoji matching the specified {@code id} if one is available in our cache. + * + *

Unicode emojis are not included as {@link RichCustomEmoji}! + * + * @param id + * The id of the requested {@link RichCustomEmoji}. + * + * @return An {@link RichCustomEmoji} represented by this id or null if none is found in + * our cache. + */ + @Nullable + default RichCustomEmoji getEmojiById(final long id) + { + return this.getEmojiCache().getElementById(id); + } + + /** + * Retrieves a custom emoji matching the specified {@code id} if one is available in our cache. + * + *

Unicode emojis are not included as {@link RichCustomEmoji}! + * + * @param id + * The id of the requested {@link RichCustomEmoji}. + * + * @throws java.lang.NumberFormatException + * If the provided {@code id} cannot be parsed by {@link Long#parseLong(String)} + * + * @return An {@link RichCustomEmoji} represented by this id or null if none is found in + * our cache. + */ + @Nullable + default RichCustomEmoji getEmojiById(@Nonnull final String id) + { + return this.getEmojiCache().getElementById(id); + } + + /** + * Unified {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} of + * all cached {@link RichCustomEmoji RichCustomEmojis} visible to this ShardManager instance. + * + * @return Unified {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} + */ + @Nonnull + default SnowflakeCacheView getEmojiCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getEmojiCache)); + } + + /** + * A collection of all known custom emojis (managed/restricted included). + * + *

Hint: To check whether you can use a {@link RichCustomEmoji} in a specific + * context you can use {@link RichCustomEmoji#canInteract(net.dv8tion.jda.api.entities.Member)} or {@link + * RichCustomEmoji#canInteract(net.dv8tion.jda.api.entities.User, MessageChannel)} + * + *

Unicode emojis are not included as {@link RichCustomEmoji}! + * + *

This copies the backing store into a list. This means every call + * creates a new list with O(n) complexity. It is recommended to store this into + * a local variable or use {@link #getEmojiCache()} and use its more efficient + * versions of handling these values. + * + * @return An immutable list of custom emojis (which may or may not be available to usage). + */ + @Nonnull + default List getEmojis() + { + return this.getEmojiCache().asList(); + } + + /** + * An unmodifiable list of all {@link RichCustomEmoji RichCustomEmojis} that have the same name as the one + * provided.
If there are no {@link RichCustomEmoji RichCustomEmojis} with the provided name, this will + * return an empty list. + * + *

Unicode emojis are not included as {@link RichCustomEmoji}! + * + * @param name + * The name of the requested {@link RichCustomEmoji RichCustomEmojis}. Without colons. + * @param ignoreCase + * Whether to ignore case or not when comparing the provided name to each {@link + * RichCustomEmoji#getName()}. + * + * @return Possibly-empty list of all the {@link RichCustomEmoji RichCustomEmojis} that all have the same + * name as the provided name. + */ + @Nonnull + default List getEmojisByName(@Nonnull final String name, final boolean ignoreCase) + { + return this.getEmojiCache().getElementsByName(name, ignoreCase); + } + + /** + * This returns the {@link net.dv8tion.jda.api.entities.Guild Guild} which has the same id as the one provided. + *
If there is no connected guild with an id that matches the provided one, this will return {@code null}. + * + * @param id + * The id of the {@link net.dv8tion.jda.api.entities.Guild Guild}. + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.Guild Guild} with matching id. + */ + @Nullable + default Guild getGuildById(final long id) + { + return getGuildCache().getElementById(id); + } + + /** + * This returns the {@link net.dv8tion.jda.api.entities.Guild Guild} which has the same id as the one provided. + *
If there is no connected guild with an id that matches the provided one, this will return {@code null}. + * + * @param id + * The id of the {@link net.dv8tion.jda.api.entities.Guild Guild}. + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.Guild Guild} with matching id. + */ + @Nullable + default Guild getGuildById(@Nonnull final String id) + { + return getGuildById(MiscUtil.parseSnowflake(id)); + } + + /** + * An unmodifiable list of all {@link net.dv8tion.jda.api.entities.Guild Guilds} that have the same name as the one provided. + *
If there are no {@link net.dv8tion.jda.api.entities.Guild Guilds} with the provided name, this will return an empty list. + * + * @param name + * The name of the requested {@link net.dv8tion.jda.api.entities.Guild Guilds}. + * @param ignoreCase + * Whether to ignore case or not when comparing the provided name to each {@link net.dv8tion.jda.api.entities.Guild#getName()}. + * + * @return Possibly-empty list of all the {@link net.dv8tion.jda.api.entities.Guild Guilds} that all have the same name as the provided name. + */ + @Nonnull + default List getGuildsByName(@Nonnull final String name, final boolean ignoreCase) + { + return this.getGuildCache().getElementsByName(name, ignoreCase); + } + + /** + * {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} of + * all cached {@link net.dv8tion.jda.api.entities.Guild Guilds} visible to this ShardManager instance. + * + * @return {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} + */ + @Nonnull + default SnowflakeCacheView getGuildCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getGuildCache)); + } + + /** + * An unmodifiable List of all {@link net.dv8tion.jda.api.entities.Guild Guilds} that the logged account is connected to. + *
If this account is not connected to any {@link net.dv8tion.jda.api.entities.Guild Guilds}, this will return + * an empty list. + * + *

This copies the backing store into a list. This means every call + * creates a new list with O(n) complexity. It is recommended to store this into + * a local variable or use {@link #getGuildCache()} and use its more efficient + * versions of handling these values. + * + * @return Possibly-empty list of all the {@link net.dv8tion.jda.api.entities.Guild Guilds} that this account is connected to. + */ + @Nonnull + default List getGuilds() + { + return this.getGuildCache().asList(); + } + + /** + * Gets all {@link net.dv8tion.jda.api.entities.Guild Guilds} that contain all given users as their members. + * + * @param users + * The users which all the returned {@link net.dv8tion.jda.api.entities.Guild Guilds} must contain. + * + * @return Unmodifiable list of all {@link net.dv8tion.jda.api.entities.Guild Guild} instances which have all {@link net.dv8tion.jda.api.entities.User Users} in them. + */ + @Nonnull + default List getMutualGuilds(@Nonnull final Collection users) + { + Checks.noneNull(users, "users"); + return this.getGuildCache().stream() + .filter(guild -> users.stream() + .allMatch(guild::isMember)) + .collect(Helpers.toUnmodifiableList()); + } + + /** + * Gets all {@link net.dv8tion.jda.api.entities.Guild Guilds} that contain all given users as their members. + * + * @param users + * The users which all the returned {@link net.dv8tion.jda.api.entities.Guild Guilds} must contain. + * + * @return Unmodifiable list of all {@link net.dv8tion.jda.api.entities.Guild Guild} instances which have all {@link net.dv8tion.jda.api.entities.User Users} in them. + */ + @Nonnull + default List getMutualGuilds(@Nonnull final User... users) + { + Checks.notNull(users, "users"); + return this.getMutualGuilds(Arrays.asList(users)); + } + + /** + * Attempts to retrieve a {@link net.dv8tion.jda.api.entities.User User} object based on the provided id. + *
This first calls {@link #getUserById(long)}, and if the return is {@code null} then a request + * is made to the Discord servers. + * + *

The returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} can encounter the following Discord errors: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER ErrorResponse.UNKNOWN_USER} + *
    Occurs when the provided id does not refer to a {@link net.dv8tion.jda.api.entities.User User} + * known by Discord. Typically occurs when developers provide an incomplete id (cut short).
  • + *
+ * + * @param id + * The id of the requested {@link net.dv8tion.jda.api.entities.User User}. + * + * @throws java.lang.IllegalArgumentException + * If the provided id String is not a valid snowflake. + * @throws java.lang.IllegalStateException + * If there isn't any active shards. + * + * @return {@link net.dv8tion.jda.api.requests.RestAction RestAction} - Type: {@link net.dv8tion.jda.api.entities.User User} + *
On request, gets the User with id matching provided id from Discord. + */ + @Nonnull + @CheckReturnValue + default RestAction retrieveUserById(@Nonnull String id) + { + return retrieveUserById(MiscUtil.parseSnowflake(id)); + } + + /** + * Attempts to retrieve a {@link net.dv8tion.jda.api.entities.User User} object based on the provided id. + *
This first calls {@link #getUserById(long)}, and if the return is {@code null} then a request + * is made to the Discord servers. + * + *

The returned {@link net.dv8tion.jda.api.requests.RestAction RestAction} can encounter the following Discord errors: + *

    + *
  • {@link net.dv8tion.jda.api.requests.ErrorResponse#UNKNOWN_USER ErrorResponse.UNKNOWN_USER} + *
    Occurs when the provided id does not refer to a {@link net.dv8tion.jda.api.entities.User User} + * known by Discord. Typically occurs when developers provide an incomplete id (cut short).
  • + *
+ * + * @param id + * The id of the requested {@link net.dv8tion.jda.api.entities.User User}. + * + * @throws java.lang.IllegalStateException + * If there isn't any active shards. + * + * @return {@link net.dv8tion.jda.api.requests.RestAction RestAction} - Type: {@link net.dv8tion.jda.api.entities.User User} + *
On request, gets the User with id matching provided id from Discord. + */ + @Nonnull + @CheckReturnValue + default RestAction retrieveUserById(long id) + { + JDA api = null; + for (JDA shard : getShardCache()) + { + api = shard; + EnumSet intents = shard.getGatewayIntents(); + User user = shard.getUserById(id); + boolean isUpdated = intents.contains(GatewayIntent.GUILD_PRESENCES) || intents.contains(GatewayIntent.GUILD_MEMBERS); + if (user != null && isUpdated) + return new CompletedRestAction<>(shard, user); + } + + if (api == null) + throw new IllegalStateException("no shards active"); + + JDAImpl jda = (JDAImpl) api; + Route.CompiledRoute route = Route.Users.GET_USER.compile(Long.toUnsignedString(id)); + return new RestActionImpl<>(jda, route, (response, request) -> jda.getEntityBuilder().createUser(response.getObject())); + } + + /** + * Searches for the first user that has the matching Discord Tag. + *
Format has to be in the form {@code Username#Discriminator} where the + * username must be between 2 and 32 characters (inclusive) matching the exact casing and the discriminator + * must be exactly 4 digits. + * + *

This will only check cached users! + * + *

This only checks users that are known to the currently logged in account (shards). If a user exists + * with the tag that is not available in the {@link #getUserCache() User-Cache} it will not be detected. + *
Currently Discord does not offer a way to retrieve a user by their discord tag. + * + * @param tag + * The Discord Tag in the format {@code Username#Discriminator} + * + * @throws java.lang.IllegalArgumentException + * If the provided tag is null or not in the described format + * + * @return The {@link net.dv8tion.jda.api.entities.User} for the discord tag or null if no user has the provided tag + */ + @Nullable + default User getUserByTag(@Nonnull String tag) + { + return getShardCache().applyStream(stream -> + stream.map(jda -> jda.getUserByTag(tag)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null) + ); + } + + /** + * Searches for the first user that has the matching Discord Tag. + *
Format has to be in the form {@code Username#Discriminator} where the + * username must be between 2 and 32 characters (inclusive) matching the exact casing and the discriminator + * must be exactly 4 digits. + * + *

This will only check cached users! + * + *

This only checks users that are known to the currently logged in account (shards). If a user exists + * with the tag that is not available in the {@link #getUserCache() User-Cache} it will not be detected. + *
Currently Discord does not offer a way to retrieve a user by their discord tag. + * + * @param username + * The name of the user + * @param discriminator + * The discriminator of the user + * + * @throws java.lang.IllegalArgumentException + * If the provided arguments are null or not in the described format + * + * @return The {@link net.dv8tion.jda.api.entities.User} for the discord tag or null if no user has the provided tag + */ + @Nullable + default User getUserByTag(@Nonnull String username, @Nonnull String discriminator) + { + return getShardCache().applyStream(stream -> + stream.map(jda -> jda.getUserByTag(username, discriminator)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null) + ); + } + + /** + * An unmodifiable list of all known {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannels}. + * + *

This copies the backing store into a list. This means every call + * creates a new list with O(n) complexity. It is recommended to store this into + * a local variable or use {@link #getPrivateChannelCache()} and use its more efficient + * versions of handling these values. + * + * @return Possibly-empty list of all {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannels}. + */ + @Nonnull + default List getPrivateChannels() + { + return this.getPrivateChannelCache().asList(); + } + + /** + * Retrieves the {@link net.dv8tion.jda.api.entities.Role Role} associated to the provided id.
This iterates + * over all {@link net.dv8tion.jda.api.entities.Guild Guilds} and check whether a Role from that Guild is assigned + * to the specified ID and will return the first that can be found. + * + * @param id + * The id of the searched Role + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.Role Role} for the specified ID + */ + @Nullable + default Role getRoleById(final long id) + { + return this.getRoleCache().getElementById(id); + } + + /** + * Retrieves the {@link net.dv8tion.jda.api.entities.Role Role} associated to the provided id.
This iterates + * over all {@link net.dv8tion.jda.api.entities.Guild Guilds} and check whether a Role from that Guild is assigned + * to the specified ID and will return the first that can be found. + * + * @param id + * The id of the searched Role + * + * @throws java.lang.NumberFormatException + * If the provided {@code id} cannot be parsed by {@link Long#parseLong(String)} + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.Role Role} for the specified ID + */ + @Nullable + default Role getRoleById(@Nonnull final String id) + { + return this.getRoleCache().getElementById(id); + } + + /** + * Unified {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} of + * all cached {@link net.dv8tion.jda.api.entities.Role Roles} visible to this ShardManager instance. + * + * @return Unified {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} + */ + @Nonnull + default SnowflakeCacheView getRoleCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getRoleCache)); + } + + /** + * All {@link net.dv8tion.jda.api.entities.Role Roles} this ShardManager instance can see.
This will iterate over each + * {@link net.dv8tion.jda.api.entities.Guild Guild} retrieved from {@link #getGuilds()} and collect its {@link + * net.dv8tion.jda.api.entities.Guild#getRoles() Guild.getRoles()}. + * + *

This copies the backing store into a list. This means every call + * creates a new list with O(n) complexity. It is recommended to store this into + * a local variable or use {@link #getRoleCache()} and use its more efficient + * versions of handling these values. + * + * @return Immutable List of all visible Roles + */ + @Nonnull + default List getRoles() + { + return this.getRoleCache().asList(); + } + + /** + * Retrieves all {@link net.dv8tion.jda.api.entities.Role Roles} visible to this ShardManager instance. + *
This simply filters the Roles returned by {@link #getRoles()} with the provided name, either using + * {@link String#equals(Object)} or {@link String#equalsIgnoreCase(String)} on {@link net.dv8tion.jda.api.entities.Role#getName()}. + * + * @param name + * The name for the Roles + * @param ignoreCase + * Whether to use {@link String#equalsIgnoreCase(String)} + * + * @return Immutable List of all Roles matching the parameters provided. + */ + @Nonnull + default List getRolesByName(@Nonnull final String name, final boolean ignoreCase) + { + return this.getRoleCache().getElementsByName(name, ignoreCase); + } + + /** + * 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 + * one, then this will return {@code null}. + * + * @param id + * The id of the {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannel}. + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannel} with matching id. + */ + @Nullable + default PrivateChannel getPrivateChannelById(final long id) + { + return this.getPrivateChannelCache().getElementById(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 + * one, this will return {@code null}. + * + * @param id + * The id of the {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannel}. + * + * @throws java.lang.NumberFormatException + * If the provided {@code id} cannot be parsed by {@link Long#parseLong(String)} + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannel} with matching id. + */ + @Nullable + default PrivateChannel getPrivateChannelById(@Nonnull final String id) + { + return this.getPrivateChannelCache().getElementById(id); + } + + /** + * {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} of + * all cached {@link net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel PrivateChannels} visible to this ShardManager instance. + * + * @return {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} + */ + @Nonnull + default SnowflakeCacheView getPrivateChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getPrivateChannelCache)); + } + + @Nullable + default GuildChannel getGuildChannelById(long id) + { + GuildChannel channel; + for (JDA shard : getShards()) + { + channel = shard.getGuildChannelById(id); + if (channel != null) + return channel; + } + + return null; + } + + @Nullable + default GuildChannel getGuildChannelById(@Nonnull ChannelType type, long id) + { + Checks.notNull(type, "ChannelType"); + GuildChannel channel; + for (JDA shard : getShards()) + { + channel = shard.getGuildChannelById(type, id); + if (channel != null) + return channel; + } + + return null; + } + + @Nonnull + default SnowflakeCacheView getTextChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getTextChannelCache)); + } + + @Nonnull + default SnowflakeCacheView getVoiceChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getVoiceChannelCache)); + } + + @Nonnull + @Override + default SnowflakeCacheView getStageChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getStageChannelCache)); + } + + @Nonnull + @Override + default SnowflakeCacheView getThreadChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getThreadChannelCache)); + } + + @Nonnull + @Override + default SnowflakeCacheView getNewsChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getNewsChannelCache)); + } + + @Nonnull + @Override + default SnowflakeCacheView getForumChannelCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getForumChannelCache)); + } + + @Nonnull + @Override + 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}. + * + * @param id + * The id of the shard. + * + * @return The {@link net.dv8tion.jda.api.JDA JDA} instance with the given shardId or + * {@code null} if no shard has the given id + */ + @Nullable + default JDA getShardById(final int id) + { + return this.getShardCache().getElementById(id); + } + + /** + * 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}. + * + * @param id + * The id of the shard. + * + * @return The {@link net.dv8tion.jda.api.JDA JDA} instance with the given shardId or + * {@code null} if no shard has the given id + */ + @Nullable + default JDA getShardById(@Nonnull final String id) + { + return this.getShardCache().getElementById(id); + } + + /** + * Unified {@link ShardCacheView ShardCacheView} of + * all cached {@link net.dv8tion.jda.api.JDA JDA} bound to this ShardManager instance. + * + * @return Unified {@link ShardCacheView ShardCacheView} + */ + @Nonnull + ShardCacheView getShardCache(); + + /** + * Gets all {@link net.dv8tion.jda.api.JDA JDA} instances bound to this ShardManager. + * + *

This copies the backing store into a list. This means every call + * creates a new list with O(n) complexity. It is recommended to store this into + * a local variable or use {@link #getShardCache()} and use its more efficient + * versions of handling these values. + * + * @return An immutable list of all managed {@link net.dv8tion.jda.api.JDA JDA} instances. + */ + @Nonnull + default List getShards() + { + return this.getShardCache().asList(); + } + + /** + * This returns the {@link net.dv8tion.jda.api.JDA.Status JDA.Status} of the shard 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}. + * + * @param shardId + * The id of the shard. + * + * @return The {@link net.dv8tion.jda.api.JDA.Status JDA.Status} of the shard with the given shardId or + * {@code null} if no shard has the given id + */ + @Nullable + default JDA.Status getStatus(final int shardId) + { + final JDA jda = this.getShardCache().getElementById(shardId); + return jda == null ? null : jda.getStatus(); + } + + /** + * Gets the current {@link net.dv8tion.jda.api.JDA.Status Status} of all shards. + * + * @return All current shard statuses. + */ + @Nonnull + default Map getStatuses() + { + return Collections.unmodifiableMap(this.getShardCache().stream() + .collect(Collectors.toMap(Function.identity(), JDA::getStatus))); + } + + /** + * This returns the {@link net.dv8tion.jda.api.entities.User User} which has the same id as the one provided. + *
If there is no visible user with an id that matches the provided one, this will return {@code null}. + * + * @param id + * The id of the requested {@link net.dv8tion.jda.api.entities.User User}. + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.User User} with matching id. + */ + @Nullable + default User getUserById(final long id) + { + return this.getUserCache().getElementById(id); + } + + /** + * This returns the {@link net.dv8tion.jda.api.entities.User User} which has the same id as the one provided. + *
If there is no visible user with an id that matches the provided one, this will return {@code null}. + * + * @param id + * The id of the requested {@link net.dv8tion.jda.api.entities.User User}. + * + * @return Possibly-null {@link net.dv8tion.jda.api.entities.User User} with matching id. + */ + @Nullable + default User getUserById(@Nonnull final String id) + { + return this.getUserCache().getElementById(id); + } + + /** + * {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} of + * all cached {@link net.dv8tion.jda.api.entities.User Users} visible to this ShardManager instance. + * + * @return {@link net.dv8tion.jda.api.utils.cache.SnowflakeCacheView SnowflakeCacheView} + */ + @Nonnull + default SnowflakeCacheView getUserCache() + { + return CacheView.allSnowflakes(() -> this.getShardCache().stream().map(JDA::getUserCache)); + } + + /** + * An unmodifiable list of all {@link net.dv8tion.jda.api.entities.User Users} that share a + * {@link net.dv8tion.jda.api.entities.Guild Guild} with the currently logged in account. + *
This list will never contain duplicates and represents all {@link net.dv8tion.jda.api.entities.User Users} + * that JDA can currently see. + * + *

If the developer is sharding, then only users from guilds connected to the specifically logged in + * shard will be returned in the List. + * + *

This copies the backing store into a list. This means every call + * creates a new list with O(n) complexity. It is recommended to store this into + * a local variable or use {@link #getUserCache()} and use its more efficient + * versions of handling these values. + * + * @return List of all {@link net.dv8tion.jda.api.entities.User Users} that are visible to JDA. + */ + @Nonnull + default List getUsers() + { + return this.getUserCache().asList(); + } + + /** + * Restarts all shards, shutting old ones down first. + * + *

As all shards need to connect to discord again this will take equally long as the startup of a new ShardManager + * (using the 5000ms + backoff as delay between starting new JDA instances). + * + * @throws java.util.concurrent.RejectedExecutionException + * If {@link #shutdown()} has already been invoked + */ + void restart(); + + /** + * Restarts the shards with the given id only. + *
If there is no shard with the given Id, this method acts like {@link #start(int)}. + * + * @param id + * The id of the target shard + * + * @throws java.lang.IllegalArgumentException + * If shardId is negative or higher than maxShardId + * @throws java.util.concurrent.RejectedExecutionException + * If {@link #shutdown()} has already been invoked + */ + void restart(int id); + + /** + * Sets the {@link net.dv8tion.jda.api.entities.Activity Activity} for all shards. + *
An Activity can be retrieved via {@link net.dv8tion.jda.api.entities.Activity#playing(String)}. + * For streams you provide a valid streaming url as second parameter. + * + *

This will also change the activity for shards that are created in the future. + * + * @param activity + * A {@link net.dv8tion.jda.api.entities.Activity Activity} instance or null to reset + * + * @see net.dv8tion.jda.api.entities.Activity#playing(String) + * @see net.dv8tion.jda.api.entities.Activity#streaming(String, String) + */ + default void setActivity(@Nullable final Activity activity) + { + this.setActivityProvider(id -> activity); + } + + /** + * Sets provider that provider the {@link net.dv8tion.jda.api.entities.Activity Activity} for all shards. + *
A Activity can be retrieved via {@link net.dv8tion.jda.api.entities.Activity#playing(String)}. + * For streams you provide a valid streaming url as second parameter. + * + *

This will also change the provider for shards that are created in the future. + * + * @param activityProvider + * Provider for an {@link net.dv8tion.jda.api.entities.Activity Activity} instance or null to reset + * + * @see net.dv8tion.jda.api.entities.Activity#playing(String) + * @see net.dv8tion.jda.api.entities.Activity#streaming(String, String) + */ + default void setActivityProvider(@Nullable final IntFunction activityProvider) + { + this.getShardCache().forEach(jda -> jda.getPresence().setActivity(activityProvider == null ? null : activityProvider.apply(jda.getShardInfo().getShardId()))); + } + + /** + * Sets whether all instances should be marked as afk or not + * + *

This is relevant to client accounts to monitor + * whether new messages should trigger mobile push-notifications. + * + *

This will also change the value for shards that are created in the future. + * + * @param idle + * boolean + */ + default void setIdle(final boolean idle) + { + this.setIdleProvider(id -> idle); + } + + /** + * Sets the provider that decides for all shards whether they should be marked as afk or not. + * + *

This will also change the provider for shards that are created in the future. + * + * @param idleProvider + * Provider for a boolean + */ + default void setIdleProvider(@Nonnull final IntFunction idleProvider) + { + this.getShardCache().forEach(jda -> jda.getPresence().setIdle(idleProvider.apply(jda.getShardInfo().getShardId()))); + } + + /** + * Sets the {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} and {@link net.dv8tion.jda.api.entities.Activity Activity} for all shards. + * + *

This will also change the status for shards that are created in the future. + * + * @param status + * The {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} + * to be used (OFFLINE/null {@literal ->} INVISIBLE) + * @param activity + * A {@link net.dv8tion.jda.api.entities.Activity Activity} instance or null to reset + * + * @throws java.lang.IllegalArgumentException + * If the provided OnlineStatus is {@link net.dv8tion.jda.api.OnlineStatus#UNKNOWN UNKNOWN} + * + * @see net.dv8tion.jda.api.entities.Activity#playing(String) + * @see net.dv8tion.jda.api.entities.Activity#streaming(String, String) + */ + default void setPresence(@Nullable final OnlineStatus status, @Nullable final Activity activity) + { + this.setPresenceProvider(id -> status, id -> activity); + } + + /** + * Sets the provider that provides the {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} and + * {@link net.dv8tion.jda.api.entities.Activity Activity} for all shards. + * + *

This will also change the status for shards that are created in the future. + * + * @param statusProvider + * The {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} + * to be used (OFFLINE/null {@literal ->} INVISIBLE) + * @param activityProvider + * A {@link net.dv8tion.jda.api.entities.Activity Activity} instance or null to reset + * + * @throws java.lang.IllegalArgumentException + * If the provided OnlineStatus is {@link net.dv8tion.jda.api.OnlineStatus#UNKNOWN UNKNOWN} + * + * @see net.dv8tion.jda.api.entities.Activity#playing(String) + * @see net.dv8tion.jda.api.entities.Activity#streaming(String, String) + */ + default void setPresenceProvider(@Nullable final IntFunction statusProvider, @Nullable final IntFunction activityProvider) + { + this.getShardCache().forEach(jda -> jda.getPresence().setPresence(statusProvider == null ? null : statusProvider.apply(jda.getShardInfo().getShardId()), activityProvider == null ? null : activityProvider.apply(jda.getShardInfo().getShardId()))); + } + + /** + * Sets the {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} for all shards. + * + *

This will also change the status for shards that are created in the future. + * + * @param status + * The {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} + * to be used (OFFLINE/null {@literal ->} INVISIBLE) + * + * @throws java.lang.IllegalArgumentException + * If the provided OnlineStatus is {@link net.dv8tion.jda.api.OnlineStatus#UNKNOWN UNKNOWN} + */ + default void setStatus(@Nullable final OnlineStatus status) + { + this.setStatusProvider(id -> status); + } + + /** + * Sets the provider that provides the {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} for all shards. + * + *

This will also change the provider for shards that are created in the future. + * + * @param statusProvider + * The {@link net.dv8tion.jda.api.OnlineStatus OnlineStatus} + * to be used (OFFLINE/null {@literal ->} INVISIBLE) + * + * @throws java.lang.IllegalArgumentException + * If the provided OnlineStatus is {@link net.dv8tion.jda.api.OnlineStatus#UNKNOWN UNKNOWN} + */ + default void setStatusProvider(@Nullable final IntFunction statusProvider) + { + this.getShardCache().forEach(jda -> jda.getPresence().setStatus(statusProvider == null ? null : statusProvider.apply(jda.getShardInfo().getShardId()))); + } + + /** + * Shuts down all JDA shards, closing all their connections. + * After this method has been called the ShardManager instance can not be used anymore. + * + *
This will shutdown the internal queue worker for (re-)starts of shards. + * This means {@link #restart(int)}, {@link #restart()}, and {@link #start(int)} will throw + * {@link java.util.concurrent.RejectedExecutionException}. + * + *

This will interrupt the default JDA event thread, due to the gateway connection being interrupted. + */ + void shutdown(); + + /** + * Shuts down the shard with the given id only. + *
If there is no shard with the given id, this will do nothing. + * + * @param shardId + * The id of the shard that should be stopped + */ + void shutdown(int shardId); + + /** + * Adds a new shard with the given id to this ShardManager and starts it. + * + * @param shardId + * The id of the shard that should be started + * + * @throws java.util.concurrent.RejectedExecutionException + * If {@link #shutdown()} has already been invoked + */ + void start(int shardId); + + /** + * Initializes and starts all shards. This should only be called once. + * + * @throws InvalidTokenException + * If the provided token is invalid. + */ + void login(); + +}