net.dv8tion.jda.internal.JDAImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JDA Show documentation
Show all versions of JDA Show documentation
Java wrapper for the popular chat & VOIP service: Discord https://discord.com
The newest version!
/*
* Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.dv8tion.jda.internal;
import com.neovisionaries.ws.client.WebSocketFactory;
import gnu.trove.set.TLongSet;
import net.dv8tion.jda.api.GatewayEncoding;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.audio.factory.DefaultSendFactory;
import net.dv8tion.jda.api.audio.factory.IAudioSendFactory;
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.ApplicationEmoji;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.entities.emoji.RichCustomEmoji;
import net.dv8tion.jda.api.entities.sticker.StickerPack;
import net.dv8tion.jda.api.entities.sticker.StickerSnowflake;
import net.dv8tion.jda.api.entities.sticker.StickerUnion;
import net.dv8tion.jda.api.events.GatewayPingEvent;
import net.dv8tion.jda.api.events.GenericEvent;
import net.dv8tion.jda.api.events.StatusChangeEvent;
import net.dv8tion.jda.api.events.session.ShutdownEvent;
import net.dv8tion.jda.api.exceptions.InvalidTokenException;
import net.dv8tion.jda.api.exceptions.ParsingException;
import net.dv8tion.jda.api.exceptions.RateLimitedException;
import net.dv8tion.jda.api.hooks.IEventManager;
import net.dv8tion.jda.api.hooks.InterfacedEventManager;
import net.dv8tion.jda.api.hooks.VoiceDispatchInterceptor;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.managers.AudioManager;
import net.dv8tion.jda.api.managers.Presence;
import net.dv8tion.jda.api.requests.*;
import net.dv8tion.jda.api.requests.restaction.*;
import net.dv8tion.jda.api.requests.restaction.pagination.EntitlementPaginationAction;
import net.dv8tion.jda.api.sharding.ShardManager;
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;
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.entities.UserImpl;
import net.dv8tion.jda.internal.handle.EventCache;
import net.dv8tion.jda.internal.handle.GuildSetupController;
import net.dv8tion.jda.internal.hooks.EventManagerProxy;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import net.dv8tion.jda.internal.interactions.command.CommandImpl;
import net.dv8tion.jda.internal.managers.AudioManagerImpl;
import net.dv8tion.jda.internal.managers.DirectAudioControllerImpl;
import net.dv8tion.jda.internal.managers.PresenceImpl;
import net.dv8tion.jda.internal.requests.*;
import net.dv8tion.jda.internal.requests.restaction.*;
import net.dv8tion.jda.internal.requests.restaction.pagination.EntitlementPaginationActionImpl;
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;
import net.dv8tion.jda.internal.utils.config.SessionConfig;
import net.dv8tion.jda.internal.utils.config.ThreadingConfig;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import org.slf4j.Logger;
import org.slf4j.MDC;
import javax.annotation.Nonnull;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
public class JDAImpl implements JDA
{
public static final Logger LOG = JDALogger.getLog(JDA.class);
protected final SnowflakeCacheViewImpl userCache = new SnowflakeCacheViewImpl<>(User.class, User::getName);
protected final SnowflakeCacheViewImpl guildCache = new SnowflakeCacheViewImpl<>(Guild.class, Guild::getName);
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());
protected final PresenceImpl presence;
protected final Thread shutdownHook;
protected final EntityBuilder entityBuilder = new EntityBuilder(this);
protected final EventCache eventCache;
protected final EventManagerProxy eventManager;
protected final GuildSetupController guildSetupController;
protected final DirectAudioControllerImpl audioController;
protected final AuthorizationConfig authConfig;
protected final ThreadingConfig threadConfig;
protected final SessionConfig sessionConfig;
protected final MetaConfig metaConfig;
protected final RestConfig restConfig;
public ShutdownReason shutdownReason = ShutdownReason.USER_SHUTDOWN; // indicates why shutdown happened in awaitStatus / awaitReady
protected WebSocketClient client;
protected Requester requester;
protected IAudioSendFactory audioSendFactory = new DefaultSendFactory();
protected SelfUser selfUser;
protected ShardInfo shardInfo;
protected long responseTotal;
protected long gatewayPing = -1;
protected String gatewayUrl;
protected ChunkingFilter chunkingFilter;
protected String clientId = null, requiredScopes = "bot";
protected ShardManager shardManager = null;
protected MemberCachePolicy memberCachePolicy = MemberCachePolicy.ALL;
protected final AtomicReference status = new AtomicReference<>(Status.INITIALIZING);
protected final ReentrantLock statusLock = new ReentrantLock();
protected final Condition statusCondition = statusLock.newCondition();
protected final AtomicBoolean requesterShutdown = new AtomicBoolean(false);
protected final AtomicReference shutdownEvent = new AtomicReference<>(null);
public JDAImpl(AuthorizationConfig authConfig)
{
this(authConfig, null, null, null, null);
}
public JDAImpl(
AuthorizationConfig authConfig, SessionConfig sessionConfig,
ThreadingConfig threadConfig, MetaConfig metaConfig, RestConfig restConfig)
{
this.authConfig = authConfig;
this.threadConfig = threadConfig == null ? ThreadingConfig.getDefault() : threadConfig;
this.sessionConfig = sessionConfig == null ? SessionConfig.getDefault() : sessionConfig;
this.metaConfig = metaConfig == null ? MetaConfig.getDefault() : metaConfig;
this.restConfig = restConfig == null ? new RestConfig() : restConfig;
this.shutdownHook = this.metaConfig.isUseShutdownHook() ? new Thread(this::shutdownNow, "JDA Shutdown Hook") : null;
this.presence = new PresenceImpl(this);
this.guildSetupController = new GuildSetupController(this);
this.audioController = new DirectAudioControllerImpl(this);
this.eventCache = new EventCache();
this.eventManager = new EventManagerProxy(new InterfacedEventManager(), this.threadConfig.getEventPool());
}
public void handleEvent(@Nonnull GenericEvent event)
{
eventManager.handle(event);
}
public boolean isRawEvents()
{
return sessionConfig.isRawEvents();
}
public boolean isEventPassthrough()
{
return sessionConfig.isEventPassthrough();
}
public boolean isCacheFlagSet(CacheFlag flag)
{
return metaConfig.getCacheFlags().contains(flag);
}
public boolean isIntent(GatewayIntent intent)
{
int raw = intent.getRawValue();
return (client.getGatewayIntents() & raw) == raw;
}
public int getLargeThreshold()
{
return sessionConfig.getLargeThreshold();
}
public int getMaxBufferSize()
{
return metaConfig.getMaxBufferSize();
}
public boolean chunkGuild(long id)
{
try
{
return isIntent(GatewayIntent.GUILD_MEMBERS) && chunkingFilter.filter(id);
}
catch (Exception e)
{
LOG.error("Uncaught exception from chunking filter", e);
return true;
}
}
public void setChunkingFilter(ChunkingFilter filter)
{
this.chunkingFilter = filter;
}
public boolean cacheMember(Member member)
{
try
{
return member.getUser().equals(getSelfUser()) // always cache self
|| memberCachePolicy.cacheMember(member); // ask policy, should we cache?
}
catch (Exception e)
{
LOG.error("Uncaught exception from member cache policy", e);
return true;
}
}
public void setMemberCachePolicy(MemberCachePolicy policy)
{
this.memberCachePolicy = policy;
}
public SessionController getSessionController()
{
return sessionConfig.getSessionController();
}
public GuildSetupController getGuildSetupController()
{
return guildSetupController;
}
public VoiceDispatchInterceptor getVoiceInterceptor()
{
return sessionConfig.getVoiceDispatchInterceptor();
}
public void usedPrivateChannel(long id)
{
synchronized (privateChannelLRU)
{
privateChannelLRU.remove(id); // We could probably make a special LRU cache view too, might not be worth it though
privateChannelLRU.addFirst(id);
if (privateChannelLRU.size() > 10) // This could probably be a config option
{
long removed = privateChannelLRU.removeLast();
channelCache.remove(ChannelType.PRIVATE, removed);
}
}
}
public void initRequester()
{
if (this.requester != null)
return;
RestRateLimiter rateLimiter = this.restConfig.getRateLimiterFactory().apply(
new RestRateLimiter.RateLimitConfig(
this.threadConfig.getRateLimitScheduler(),
this.threadConfig.getRateLimitElastic(),
getSessionController().getRateLimitHandle(),
this.sessionConfig.isRelativeRateLimit() && this.restConfig.isRelativeRateLimit()
));
this.requester = new Requester(this, this.authConfig, this.restConfig, rateLimiter);
this.requester.setRetryOnTimeout(this.sessionConfig.isRetryOnTimeout());
}
public int login()
{
return login(null, null, Compression.ZLIB, true, GatewayIntent.ALL_INTENTS, GatewayEncoding.JSON);
}
public int login(ShardInfo shardInfo, Compression compression, boolean validateToken, int intents, GatewayEncoding encoding)
{
return login(null, shardInfo, compression, validateToken, intents, encoding);
}
public int login(String gatewayUrl, ShardInfo shardInfo, Compression compression, boolean validateToken, int intents, GatewayEncoding encoding)
{
this.shardInfo = shardInfo;
// Delayed init for thread-pools so they can set the shard info as their name
this.threadConfig.init(this::getIdentifierString);
// Setup rest-module and rate-limiter subsystem
initRequester();
this.gatewayUrl = gatewayUrl == null ? getGateway() : gatewayUrl;
Checks.notNull(this.gatewayUrl, "Gateway URL");
setStatus(Status.LOGGING_IN);
Map previousContext = null;
ConcurrentMap contextMap = metaConfig.getMdcContextMap();
if (contextMap != null)
{
if (shardInfo != null)
{
contextMap.put("jda.shard", shardInfo.getShardString());
contextMap.put("jda.shard.id", String.valueOf(shardInfo.getShardId()));
contextMap.put("jda.shard.total", String.valueOf(shardInfo.getShardTotal()));
}
// set MDC metadata for build thread
previousContext = MDC.getCopyOfContextMap();
contextMap.forEach(MDC::put);
requester.setContextReady(true);
}
if (validateToken)
{
verifyToken();
LOG.info("Login Successful!");
}
client = new WebSocketClient(this, compression, intents, encoding);
// remove our MDC metadata when we exit our code
if (previousContext != null)
previousContext.forEach(MDC::put);
if (shutdownHook != null)
Runtime.getRuntime().addShutdownHook(shutdownHook);
return shardInfo == null ? -1 : shardInfo.getShardTotal();
}
public String getGateway()
{
return getSessionController().getGateway();
}
// This method also checks for a valid bot token as it is required to get the recommended shard count.
public SessionController.ShardedGateway getShardedGateway()
{
return getSessionController().getShardedGateway(this);
}
public ConcurrentMap getContextMap()
{
return metaConfig.getMdcContextMap() == null ? null : new ConcurrentHashMap<>(metaConfig.getMdcContextMap());
}
public void setContext()
{
if (metaConfig.getMdcContextMap() != null)
metaConfig.getMdcContextMap().forEach(MDC::put);
}
public void setToken(String token)
{
this.authConfig.setToken(token);
}
public void setStatus(Status status)
{
StatusChangeEvent event = MiscUtil.locked(statusLock, () -> {
Status oldStatus = this.status.getAndSet(status);
this.statusCondition.signalAll();
return new StatusChangeEvent(this, status, oldStatus);
});
if (event.getOldStatus() != event.getNewStatus())
handleEvent(event);
}
public void verifyToken()
{
RestActionImpl login = new RestActionImpl(this, Route.Self.GET_SELF.compile())
{
@Override
public void handleResponse(Response response, Request request)
{
if (response.isOk())
request.onSuccess(response.getObject());
else if (response.isRateLimit())
request.onFailure(new RateLimitedException(request.getRoute(), response.retryAfter));
else if (response.code == 401)
request.onSuccess(null);
else
request.onFailure(response);
}
}.priority();
try
{
DataObject userResponse = login.complete();
if (userResponse != null)
{
getEntityBuilder().createSelfUser(userResponse);
return;
}
throw new InvalidTokenException();
}
catch (Throwable error)
{
shutdownNow();
throw error;
}
}
public AuthorizationConfig getAuthorizationConfig()
{
return authConfig;
}
@Nonnull
@Override
public String getToken()
{
return authConfig.getToken();
}
@Override
public boolean isBulkDeleteSplittingEnabled()
{
return sessionConfig.isBulkDeleteSplittingEnabled();
}
@Override
public void setAutoReconnect(boolean autoReconnect)
{
sessionConfig.setAutoReconnect(autoReconnect);
WebSocketClient client = getClient();
if (client != null)
client.setAutoReconnect(autoReconnect);
}
@Override
public void setRequestTimeoutRetry(boolean retryOnTimeout)
{
requester.setRetryOnTimeout(retryOnTimeout);
}
@Override
public boolean isAutoReconnect()
{
return sessionConfig.isAutoReconnect();
}
@Nonnull
@Override
public Status getStatus()
{
return status.get();
}
@Nonnull
@Override
public EnumSet getGatewayIntents()
{
return GatewayIntent.getIntents(client.getGatewayIntents());
}
@Nonnull
@Override
public EnumSet getCacheFlags()
{
return Helpers.copyEnumSet(CacheFlag.class, metaConfig.getCacheFlags());
}
@Override
public boolean unloadUser(long userId)
{
if (userId == selfUser.getIdLong())
return false;
User user = getUserById(userId);
if (user == null)
return false;
// We avoid to lock both the guild cache and member cache to make a deadlock impossible
return getGuildCache().stream()
.filter(guild -> guild.unloadMember(userId)) // this also removes it from user cache
.count() > 0L; // we use count to make sure it iterates all guilds not just one
}
@Override
public long getGatewayPing()
{
return gatewayPing;
}
@Nonnull
@Override
public JDA awaitStatus(@Nonnull Status status, @Nonnull Status... failOn) throws InterruptedException
{
Checks.notNull(status, "Status");
if (getStatus() == Status.CONNECTED)
return this;
MiscUtil.tryLock(statusLock);
try
{
EnumSet endCondition = EnumSet.of(status, failOn);
Status current = getStatus();
while (!current.isInit() // In case of disconnects during startup
|| current.ordinal() < status.ordinal()) // If we missed the status (e.g. LOGGING_IN -> CONNECTED happened while waiting for lock)
{
if (current == Status.SHUTDOWN)
throw new IllegalStateException("Was shutdown trying to await status.\nReason: " + shutdownReason);
if (endCondition.contains(current))
return this;
statusCondition.await();
current = getStatus();
}
}
finally
{
statusLock.unlock();
}
return this;
}
@Override
public boolean awaitShutdown(long timeout, @Nonnull TimeUnit unit) throws InterruptedException
{
timeout = unit.toMillis(timeout);
long deadline = timeout == 0 ? Long.MAX_VALUE : System.currentTimeMillis() + timeout;
MiscUtil.tryLock(statusLock);
try
{
Status current = getStatus();
while (current != Status.SHUTDOWN)
{
if (!statusCondition.await(deadline - System.currentTimeMillis(), TimeUnit.MILLISECONDS))
return false;
current = getStatus();
}
return true;
}
finally
{
statusLock.unlock();
}
}
@Override
public int cancelRequests()
{
return requester.getRateLimiter().cancelRequests();
}
@Nonnull
@Override
public ScheduledExecutorService getRateLimitPool()
{
return threadConfig.getRateLimitScheduler();
}
@Nonnull
@Override
public ScheduledExecutorService getGatewayPool()
{
return threadConfig.getGatewayPool();
}
@Nonnull
@Override
public ExecutorService getCallbackPool()
{
return threadConfig.getCallbackPool();
}
@Nonnull
@Override
@SuppressWarnings("ConstantConditions") // this can't really happen unless you pass bad configs
public OkHttpClient getHttpClient()
{
return sessionConfig.getHttpClient();
}
@Nonnull
@Override
public DirectAudioControllerImpl getDirectAudioController()
{
if (!isIntent(GatewayIntent.GUILD_VOICE_STATES))
throw new IllegalStateException("Cannot use audio features with disabled GUILD_VOICE_STATES intent!");
return this.audioController;
}
@Nonnull
@Override
public List getMutualGuilds(@Nonnull User... users)
{
Checks.notNull(users, "users");
return getMutualGuilds(Arrays.asList(users));
}
@Nonnull
@Override
public List getMutualGuilds(@Nonnull Collection users)
{
Checks.notNull(users, "users");
for(User u : users)
Checks.notNull(u, "All users");
return getGuilds().stream()
.filter(guild -> users.stream().allMatch(guild::isMember))
.collect(Helpers.toUnmodifiableList());
}
@Nonnull
@Override
public CacheRestAction retrieveUserById(long id)
{
return new DeferredRestAction<>(this, User.class,
() -> isIntent(GatewayIntent.GUILD_MEMBERS) || isIntent(GatewayIntent.GUILD_PRESENCES) ? getUserById(id) : null,
() -> {
if (id == getSelfUser().getIdLong())
return new CompletedRestAction<>(this, getSelfUser());
Route.CompiledRoute route = Route.Users.GET_USER.compile(Long.toUnsignedString(id));
return new RestActionImpl<>(this, route,
(response, request) -> getEntityBuilder().createUser(response.getObject()));
});
}
@Nonnull
@Override
public CacheView getAudioManagerCache()
{
return audioManagers;
}
@Nonnull
@Override
public SnowflakeCacheView getGuildCache()
{
return guildCache;
}
@Nonnull
@Override
public Set getUnavailableGuilds()
{
TLongSet unavailableGuilds = guildSetupController.getUnavailableGuilds();
Set copy = new HashSet<>();
unavailableGuilds.forEach(id -> copy.add(Long.toUnsignedString(id)));
return copy;
}
@Override
public boolean isUnavailable(long guildId)
{
return guildSetupController.isUnavailable(guildId);
}
@Nonnull
@Override
public SnowflakeCacheView getRoleCache()
{
return CacheView.allSnowflakes(() -> guildCache.stream().map(Guild::getRoleCache));
}
@Nonnull
@Override
public SnowflakeCacheView getEmojiCache()
{
return CacheView.allSnowflakes(() -> guildCache.stream().map(Guild::getEmojiCache));
}
@Nonnull
@Override
public RestAction createApplicationEmoji(@Nonnull String name, @Nonnull Icon icon)
{
Checks.inRange(name, 2, CustomEmoji.EMOJI_NAME_MAX_LENGTH, "Emoji name");
Checks.matches(name, Checks.ALPHANUMERIC_WITH_DASH, "Emoji name");
Checks.notNull(icon, "Emoji icon");
DataObject body = DataObject.empty();
body.put("name", name);
body.put("image", icon.getEncoding());
final Route.CompiledRoute route = Route.Applications.CREATE_APPLICATION_EMOJI.compile(getSelfUser().getApplicationId());
return new RestActionImpl<>(this, route, body, (response, request) ->
{
final DataObject obj = response.getObject();
final User selfUser = getSelfUser();
return entityBuilder.createApplicationEmoji(this, obj, selfUser);
});
}
@Nonnull
@Override
public RestAction> retrieveApplicationEmojis()
{
Route.CompiledRoute route = Route.Applications.GET_APPLICATION_EMOJIS.compile(getSelfUser().getApplicationId());
return new RestActionImpl<>(this, route, (response, request) ->
{
DataArray emojis = response.getObject().getArray("items");
List list = new ArrayList<>(emojis.length());
for (int i = 0; i < emojis.length(); i++)
{
try
{
final DataObject emoji = emojis.getObject(i);
final User owner = emoji.optObject("user")
.map(entityBuilder::createUser)
.orElse(null);
list.add(entityBuilder.createApplicationEmoji(this, emoji, owner));
}
catch (ParsingException e)
{
LOG.error("Failed to parse application emoji with JSON: {}", emojis.getObject(i), e);
}
}
return Collections.unmodifiableList(list);
});
}
@Nonnull
@Override
public RestAction retrieveApplicationEmojiById(@Nonnull String emojiId)
{
Checks.isSnowflake(emojiId);
Route.CompiledRoute route = Route.Applications.GET_APPLICATION_EMOJI.compile(getSelfUser().getApplicationId(), emojiId);
return new RestActionImpl<>(this, route, (response, request) ->
{
final DataObject emoji = response.getObject();
final User owner = emoji.optObject("user")
.map(entityBuilder::createUser)
.orElse(null);
return entityBuilder.createApplicationEmoji(this, emoji, owner);
});
}
@Nonnull
@Override
public RestAction retrieveSticker(@Nonnull StickerSnowflake sticker)
{
Checks.notNull(sticker, "Sticker");
Route.CompiledRoute route = Route.Stickers.GET_STICKER.compile(sticker.getId());
return new RestActionImpl<>(this, route,
(response, request) -> entityBuilder.createRichSticker(response.getObject())
);
}
@Nonnull
@Override
public RestAction> retrieveNitroStickerPacks()
{
Route.CompiledRoute route = Route.Stickers.LIST_PACKS.compile();
return new RestActionImpl<>(this, route, (response, request) ->
{
DataArray array = response.getObject().getArray("sticker_packs");
List packs = new ArrayList<>(array.length());
for (int i = 0; i < array.length(); i++)
{
DataObject object = null;
try
{
object = array.getObject(i);
StickerPack pack = entityBuilder.createStickerPack(object);
packs.add(pack);
}
catch (ParsingException ex)
{
EntityBuilder.LOG.error("Failed to parse sticker pack. JSON: {}", object);
}
}
return Collections.unmodifiableList(packs);
});
}
@Nonnull
@Override
public SnowflakeCacheView getScheduledEventCache()
{
return CacheView.allSnowflakes(() -> guildCache.stream().map(Guild::getScheduledEventCache));
}
@Nonnull
@Override
public ChannelCacheView getChannelCache()
{
return channelCache;
}
@Nonnull
@Override
public SnowflakeCacheView getCategoryCache()
{
return channelCache.ofType(Category.class);
}
@Nonnull
@Override
public SnowflakeCacheView getTextChannelCache()
{
return channelCache.ofType(TextChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getNewsChannelCache()
{
return channelCache.ofType(NewsChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getVoiceChannelCache()
{
return channelCache.ofType(VoiceChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getStageChannelCache()
{
return channelCache.ofType(StageChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getThreadChannelCache()
{
return channelCache.ofType(ThreadChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getForumChannelCache()
{
return channelCache.ofType(ForumChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getMediaChannelCache()
{
return channelCache.ofType(MediaChannel.class);
}
@Nonnull
@Override
public SnowflakeCacheView getPrivateChannelCache()
{
return channelCache.ofType(PrivateChannel.class);
}
@Override
public PrivateChannel getPrivateChannelById(@Nonnull String id)
{
return getPrivateChannelById(MiscUtil.parseSnowflake(id));
}
@Override
public PrivateChannel getPrivateChannelById(long id)
{
PrivateChannel channel = JDA.super.getPrivateChannelById(id);
if (channel != null)
usedPrivateChannel(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)
{
if (selfUser != null && userId == selfUser.getIdLong())
throw new UnsupportedOperationException("Cannot open private channel with yourself!");
return new DeferredRestAction<>(this, PrivateChannel.class, () -> {
User user = getUserById(userId);
if (user instanceof UserImpl)
return ((UserImpl) user).getPrivateChannel();
return null;
}, () -> {
Route.CompiledRoute route = Route.Self.CREATE_PRIVATE_CHANNEL.compile();
DataObject body = DataObject.empty().put("recipient_id", userId);
return new RestActionImpl<>(this, route, body,
(response, request) -> getEntityBuilder().createPrivateChannel(response.getObject()));
});
}
@Nonnull
@Override
public SnowflakeCacheView getUserCache()
{
return userCache;
}
public boolean hasSelfUser()
{
return selfUser != null;
}
@Nonnull
@Override
public SelfUser getSelfUser()
{
if (selfUser == null)
throw new IllegalStateException("Session is not yet ready!");
return selfUser;
}
@Override
public synchronized void shutdownNow()
{
requester.stop(true, this::shutdownRequester); // stop all requests
shutdown();
threadConfig.shutdownNow();
}
@Override
public synchronized void shutdown()
{
Status status = getStatus();
if (status == Status.SHUTDOWN || status == Status.SHUTTING_DOWN)
return;
setStatus(Status.SHUTTING_DOWN);
WebSocketClient client = getClient();
if (client != null)
{
client.getChunkManager().shutdown();
client.shutdown();
}
else
{
shutdownInternals(new ShutdownEvent(this, OffsetDateTime.now(), 1000));
}
}
public void shutdownInternals(ShutdownEvent event)
{
if (getStatus() == Status.SHUTDOWN)
return;
//so we can shutdown from WebSocketClient properly
closeAudioConnections();
guildSetupController.close();
// stop accepting new requests
requester.stop(false, this::shutdownRequester);
threadConfig.shutdown();
if (shutdownHook != null)
{
try
{
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
catch (Exception ignored) {}
}
// If the requester has been shutdown too, we can fire the shutdown event
boolean signal = MiscUtil.locked(statusLock, () -> shutdownEvent.getAndSet(event) == null && requesterShutdown.get());
if (signal)
signalShutdown();
}
public void shutdownRequester()
{
// Stop all request processing
threadConfig.shutdownRequester();
// If the websocket has been shutdown too, we can fire the shutdown event
boolean signal = MiscUtil.locked(statusLock, () -> !requesterShutdown.getAndSet(true) && shutdownEvent.get() != null);
if (signal)
signalShutdown();
}
private void signalShutdown()
{
setStatus(Status.SHUTDOWN);
handleEvent(shutdownEvent.get());
}
private void closeAudioConnections()
{
getAudioManagerCache()
.stream()
.map(AudioManagerImpl.class::cast)
.forEach(m -> m.closeAudioConnection(ConnectionStatus.SHUTTING_DOWN));
}
@Override
public long getResponseTotal()
{
return responseTotal;
}
@Override
public int getMaxReconnectDelay()
{
return sessionConfig.getMaxReconnectDelay();
}
@Nonnull
@Override
public ShardInfo getShardInfo()
{
return shardInfo == null ? ShardInfo.SINGLE : shardInfo;
}
@Nonnull
@Override
public Presence getPresence()
{
return presence;
}
@Nonnull
@Override
public IEventManager getEventManager()
{
return eventManager.getSubject();
}
@Override
public void setEventManager(IEventManager eventManager)
{
this.eventManager.setSubject(eventManager);
}
@Override
public void addEventListener(@Nonnull Object... listeners)
{
Checks.noneNull(listeners, "listeners");
for (Object listener: listeners)
eventManager.register(listener);
}
@Override
public void removeEventListener(@Nonnull Object... listeners)
{
Checks.noneNull(listeners, "listeners");
for (Object listener: listeners)
eventManager.unregister(listener);
}
@Nonnull
@Override
public List