From bbc722e6896cb9e5c44d7b63e68ebf75188740e2 Mon Sep 17 00:00:00 2001 From: Vankka Date: Sat, 2 Apr 2022 16:24:34 +0300 Subject: [PATCH] Add channel auto locking, implement thread archiving. More fixes to shutdown not working properly/ errors when the server is stopped before DiscordSRV starts --- .../discordsrv/api/module/type/Module.java | 4 + .../discordsrv/common/AbstractDiscordSRV.java | 27 ++- .../ChannelShutdownBehaviourModule.java | 161 ++++++++++++++++++ .../channels/ShutdownBehaviourConfig.java | 59 +++++++ .../main/channels/base/BaseChannelConfig.java | 12 +- .../main/channels/base/ThreadConfig.java | 2 - .../common/discord/api/DiscordAPIImpl.java | 45 ++--- .../connection/jda/JDAConnectionManager.java | 10 +- .../impl/DependencyLoggingHandler.java | 11 +- .../common/logging/impl/DiscordSRVLogger.java | 9 +- .../DiscordMessageMirroringModule.java | 4 +- .../game/AbstractGameMessageModule.java | 28 +-- .../common/module/ModuleManager.java | 6 +- 13 files changed, 325 insertions(+), 53 deletions(-) create mode 100644 common/src/main/java/com/discordsrv/common/channel/ChannelShutdownBehaviourModule.java create mode 100644 common/src/main/java/com/discordsrv/common/config/main/channels/ShutdownBehaviourConfig.java diff --git a/api/src/main/java/com/discordsrv/api/module/type/Module.java b/api/src/main/java/com/discordsrv/api/module/type/Module.java index 79bb950b..807a7670 100644 --- a/api/src/main/java/com/discordsrv/api/module/type/Module.java +++ b/api/src/main/java/com/discordsrv/api/module/type/Module.java @@ -39,6 +39,10 @@ public interface Module { return 0; } + default int shutdownOrder() { + return priority(getClass()); + } + default void enable() { reload(); } diff --git a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java index 94282894..9b55eb36 100644 --- a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java @@ -26,6 +26,7 @@ import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent; import com.discordsrv.api.module.type.Module; import com.discordsrv.common.api.util.ApiInstanceUtil; import com.discordsrv.common.channel.ChannelConfigHelper; +import com.discordsrv.common.channel.ChannelShutdownBehaviourModule; import com.discordsrv.common.channel.ChannelUpdaterModule; import com.discordsrv.common.channel.GlobalChannelLookupModule; import com.discordsrv.common.command.game.GameCommandModule; @@ -388,7 +389,7 @@ public abstract class AbstractDiscordSRV invokeLifecycle(CheckedRunnable runnable) { - return invoke(() -> { + return invokeLifecycle(() -> { try { lifecycleLock.lock(); runnable.run(); @@ -398,11 +399,19 @@ public abstract class AbstractDiscordSRV invoke(CheckedRunnable runnable, String message, boolean enable) { + protected CompletableFuture invokeLifecycle(CheckedRunnable runnable, String message, boolean enable) { return CompletableFuture.runAsync(() -> { + if (status().isShutdown()) { + // Already shutdown/shutting down, don't bother + return; + } try { runnable.run(); } catch (Throwable t) { + if (status().isShutdown() && t instanceof NoClassDefFoundError) { + // Already shutdown, ignore errors for classes that already got unloaded + return; + } if (enable) { setStatus(Status.FAILED_TO_START); disable(); @@ -424,6 +433,7 @@ public abstract class AbstractDiscordSRV invokeDisable() { if (enableFuture != null && !enableFuture.isDone()) { + logger().warning("Start cancelled"); enableFuture.cancel(true); } return CompletableFuture.runAsync(this::disable, scheduler().executorService()); @@ -431,7 +441,7 @@ public abstract class AbstractDiscordSRV invokeReload(Set flags, boolean silent) { - return invoke(() -> reload(flags, silent), "Failed to reload", false); + return invokeLifecycle(() -> reload(flags, silent), "Failed to reload", false); } @OverridingMethodsMustInvokeSuper @@ -453,6 +463,7 @@ public abstract class AbstractDiscordSRV don't continue + return; + } + } } } catch (ExecutionException e) { throw e.getCause(); diff --git a/common/src/main/java/com/discordsrv/common/channel/ChannelShutdownBehaviourModule.java b/common/src/main/java/com/discordsrv/common/channel/ChannelShutdownBehaviourModule.java new file mode 100644 index 00000000..745d4128 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/channel/ChannelShutdownBehaviourModule.java @@ -0,0 +1,161 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.channel; + +import com.discordsrv.api.discord.api.entity.channel.DiscordThreadChannel; +import com.discordsrv.common.DiscordSRV; +import com.discordsrv.common.config.main.channels.ShutdownBehaviourConfig; +import com.discordsrv.common.config.main.channels.base.BaseChannelConfig; +import com.discordsrv.common.config.main.channels.base.IChannelConfig; +import com.discordsrv.common.function.OrDefault; +import com.discordsrv.common.module.type.AbstractModule; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.IPermissionHolder; +import net.dv8tion.jda.api.entities.PermissionOverride; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.requests.restaction.PermissionOverrideAction; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; + +public class ChannelShutdownBehaviourModule extends AbstractModule { + + public ChannelShutdownBehaviourModule(DiscordSRV discordSRV) { + super(discordSRV); + } + + @Override + public int shutdownOrder() { + return Integer.MIN_VALUE; + } + + @Override + public void enable() { + doForAllChannels((config, channelConfig) -> { + OrDefault shutdownConfig = config.map(cfg -> cfg.shutdownBehaviour); + OrDefault channels = shutdownConfig.map(cfg -> cfg.channels); + OrDefault threads = shutdownConfig.map(cfg -> cfg.threads); + + if (threads.get(cfg -> cfg.unarchive, true)) { + discordSRV.discordAPI().findOrCreateThreads(config, channelConfig, __ -> {}, new ArrayList<>(), false); + } + channelPermissions(channelConfig, channels, true); + }); + } + + @Override + public void disable() { + doForAllChannels((config, channelConfig) -> { + OrDefault shutdownConfig = config.map(cfg -> cfg.shutdownBehaviour); + OrDefault channels = shutdownConfig.map(cfg -> cfg.channels); + OrDefault threads = shutdownConfig.map(cfg -> cfg.threads); + + if (threads.get(cfg -> cfg.archive, true)) { + for (DiscordThreadChannel thread : discordSRV.discordAPI().findThreads(config, channelConfig)) { + thread.getAsJDAThreadChannel().getManager() + .setArchived(true) + .reason("DiscordSRV shutdown behaviour") + .queue(); + } + } + channelPermissions(channelConfig, channels, false); + }); + } + + private void channelPermissions( + IChannelConfig channelConfig, + OrDefault shutdownConfig, + boolean state + ) { + JDA jda = discordSRV.jda().orElse(null); + if (jda == null) { + return; + } + + boolean everyone = shutdownConfig.get(cfg -> cfg.everyone, false); + List roleIds = shutdownConfig.get(cfg -> cfg.roleIds, Collections.emptyList()); + if (!everyone && roleIds.isEmpty()) { + return; + } + + List permissions = new ArrayList<>(); + if (shutdownConfig.get(cfg -> cfg.read, false)) { + permissions.add(Permission.VIEW_CHANNEL); + } + if (shutdownConfig.get(cfg -> cfg.write, true)) { + permissions.add(Permission.MESSAGE_SEND); + } + if (shutdownConfig.get(cfg -> cfg.addReactions, true)) { + permissions.add(Permission.MESSAGE_ADD_REACTION); + } + + for (Long channelId : channelConfig.channelIds()) { + TextChannel channel = jda.getTextChannelById(channelId); + if (channel == null) { + continue; + } + + if (everyone) { + setPermission(channel, channel.getGuild().getPublicRole(), permissions, state); + } + for (Long roleId : roleIds) { + Role role = channel.getGuild().getRoleById(roleId); + if (role == null) { + continue; + } + + setPermission(channel, role, permissions, state); + } + } + } + + private void setPermission(TextChannel channel, IPermissionHolder holder, List permissions, boolean state) { + PermissionOverride override = channel.getPermissionOverride(holder); + if (override != null && (state ? override.getAllowed() : override.getDenied()).containsAll(permissions)) { + // Already correct + return; + } + + PermissionOverrideAction action = override != null + ? override.getManager() + : channel.putPermissionOverride(holder); + + if (state) { + action = action.grant(permissions); + } else { + action = action.deny(permissions); + } + action.reason("DiscordSRV shutdown behaviour").queue(); + } + + private void doForAllChannels(BiConsumer, IChannelConfig> channelConsumer) { + for (OrDefault config : discordSRV.channelConfig().getAllChannels()) { + IChannelConfig channelConfig = config.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null); + if (channelConfig == null) { + continue; + } + + channelConsumer.accept(config, channelConfig); + } + } +} diff --git a/common/src/main/java/com/discordsrv/common/config/main/channels/ShutdownBehaviourConfig.java b/common/src/main/java/com/discordsrv/common/config/main/channels/ShutdownBehaviourConfig.java new file mode 100644 index 00000000..1f808797 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/config/main/channels/ShutdownBehaviourConfig.java @@ -0,0 +1,59 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.config.main.channels; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; + +import java.util.ArrayList; +import java.util.List; + +@ConfigSerializable +public class ShutdownBehaviourConfig { + + public Channels channels = new Channels(); + public Threads threads = new Threads(); + + @ConfigSerializable + public static class Channels { + + @Comment("If the permissions should be taken from @everyone while the server is offline") + public boolean everyone = false; + + @Comment("Role ids for roles that should have the permissions taken while the server is offline") + public List roleIds = new ArrayList<>(); + + public boolean read = false; + public boolean write = true; + public boolean addReactions = true; + + } + + @ConfigSerializable + public static class Threads { + + @Comment("If threads should be archived while the server is shutdown") + public boolean archive = true; + + @Comment("If the bot will attempt to unarchive threads rather than make new threads") + public boolean unarchive = true; + + } + +} diff --git a/common/src/main/java/com/discordsrv/common/config/main/channels/base/BaseChannelConfig.java b/common/src/main/java/com/discordsrv/common/config/main/channels/base/BaseChannelConfig.java index b3b155d7..a9e5017c 100644 --- a/common/src/main/java/com/discordsrv/common/config/main/channels/base/BaseChannelConfig.java +++ b/common/src/main/java/com/discordsrv/common/config/main/channels/base/BaseChannelConfig.java @@ -31,14 +31,20 @@ public class BaseChannelConfig { public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig(); public JoinMessageConfig joinMessages = new JoinMessageConfig(); public LeaveMessageConfig leaveMessages = new LeaveMessageConfig(); + + @Untranslated(Untranslated.Type.VALUE) + @Order(10) + public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay"; + + @Order(20) public StartMessageConfig startMessage = new StartMessageConfig(); + @Order(20) public StopMessageConfig stopMessage = new StopMessageConfig(); - @Order(10) + @Order(30) @Comment("Settings for synchronizing messages between the defined Discord channels and threads") public MirroringConfig mirroring = new MirroringConfig(); - @Untranslated(Untranslated.Type.VALUE) @Order(50) - public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay"; + public ShutdownBehaviourConfig shutdownBehaviour = new ShutdownBehaviourConfig(); } diff --git a/common/src/main/java/com/discordsrv/common/config/main/channels/base/ThreadConfig.java b/common/src/main/java/com/discordsrv/common/config/main/channels/base/ThreadConfig.java index 187eda10..d42a0df0 100644 --- a/common/src/main/java/com/discordsrv/common/config/main/channels/base/ThreadConfig.java +++ b/common/src/main/java/com/discordsrv/common/config/main/channels/base/ThreadConfig.java @@ -28,6 +28,4 @@ public class ThreadConfig { public String threadName = "Minecraft Server chat bridge"; public boolean privateThread = false; - public boolean archiveOnShutdown = true; - public boolean unarchive = true; } diff --git a/common/src/main/java/com/discordsrv/common/discord/api/DiscordAPIImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/DiscordAPIImpl.java index e84ab75e..62529e59 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/DiscordAPIImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/DiscordAPIImpl.java @@ -42,6 +42,7 @@ import com.discordsrv.common.discord.api.entity.channel.DiscordThreadChannelImpl import com.discordsrv.common.discord.api.entity.guild.DiscordGuildImpl; import com.discordsrv.common.discord.api.entity.guild.DiscordRoleImpl; import com.discordsrv.common.function.CheckedSupplier; +import com.discordsrv.common.function.OrDefault; import com.discordsrv.common.future.util.CompletableFutureUtil; import com.github.benmanes.caffeine.cache.AsyncCacheLoader; import com.github.benmanes.caffeine.cache.AsyncLoadingCache; @@ -98,9 +99,9 @@ public class DiscordAPIImpl implements DiscordAPI { * @param config the config that specified the threads * @return the list of active threads */ - public List findThreads(IChannelConfig config) { + public List findThreads(OrDefault config, IChannelConfig channelConfig) { List channels = new ArrayList<>(); - findOrCreateThreads(config, channels::add, null); + findOrCreateThreads(config, channelConfig, channels::add, null, false); return channels; } @@ -111,11 +112,13 @@ public class DiscordAPIImpl implements DiscordAPI { * @param futures a possibly null list of {@link CompletableFuture} for tasks that need to be completed to get all threads */ public void findOrCreateThreads( - IChannelConfig config, + OrDefault config, + IChannelConfig channelConfig, Consumer channelConsumer, - @Nullable List> futures + @Nullable List> futures, + boolean log ) { - List threads = config.threads(); + List threads = channelConfig.threads(); if (threads == null) { return; } @@ -124,10 +127,8 @@ public class DiscordAPIImpl implements DiscordAPI { long channelId = threadConfig.channelId; DiscordTextChannel channel = getTextChannelById(channelId).orElse(null); if (channel == null) { - if (channelId > 0) { - discordSRV.logger().error("Unable to find channel with ID " - + Long.toUnsignedString(channelId) - + ", unable to forward message to Discord"); + if (channelId > 0 && log) { + discordSRV.logger().error("Unable to find channel with ID " + Long.toUnsignedString(channelId)); } continue; } @@ -136,7 +137,7 @@ public class DiscordAPIImpl implements DiscordAPI { DiscordThreadChannel thread = findThread(threadConfig, channel.getActiveThreads()); if (thread != null) { ThreadChannel jdaChannel = thread.getAsJDAThreadChannel(); - if (!jdaChannel.isLocked() && !jdaChannel.isArchived()) { + if (!jdaChannel.isArchived()) { channelConsumer.accept(thread); continue; } @@ -154,7 +155,7 @@ public class DiscordAPIImpl implements DiscordAPI { unarchiveOrCreateThread(threadConfig, channel, thread, future); } else { // Find or create the thread - future = findOrCreateThread(threadConfig, channel); + future = findOrCreateThread(config, threadConfig, channel); } futures.add(future.handle((threadChannel, t) -> { @@ -182,16 +183,20 @@ public class DiscordAPIImpl implements DiscordAPI { return null; } - private CompletableFuture findOrCreateThread(ThreadConfig config, DiscordTextChannel textChannel) { - if (!config.unarchive) { - return textChannel.createThread(config.threadName, config.privateThread); + private CompletableFuture findOrCreateThread( + OrDefault config, + ThreadConfig threadConfig, + DiscordTextChannel textChannel + ) { + if (!config.map(cfg -> cfg.shutdownBehaviour).map(cfg -> cfg.threads).get(cfg -> cfg.unarchive, true)) { + return textChannel.createThread(threadConfig.threadName, threadConfig.privateThread); } CompletableFuture completableFuture = new CompletableFuture<>(); lookupThreads( textChannel, - config.privateThread, - lookup -> findOrCreateThread(config, textChannel, lookup, completableFuture), + threadConfig.privateThread, + lookup -> findOrCreateThread(threadConfig, textChannel, lookup, completableFuture), (thread, throwable) -> { if (throwable != null) { completableFuture.completeExceptionally(throwable); @@ -244,10 +249,10 @@ public class DiscordAPIImpl implements DiscordAPI { ThreadChannel channel = thread.getAsJDAThreadChannel(); if (channel.isLocked() || channel.isArchived()) { try { - channel.getManager().setArchived(false).queue( - v -> future.complete(thread), - future::completeExceptionally - ); + channel.getManager() + .setArchived(false) + .reason("DiscordSRV Auto Unarchive") + .queue(v -> future.complete(thread), future::completeExceptionally); } catch (Throwable t) { future.completeExceptionally(t); } diff --git a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java index 615d2272..f7897e8c 100644 --- a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java +++ b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java @@ -253,7 +253,7 @@ public class JDAConnectionManager implements DiscordConnectionManager { // Our own (named) threads jdaBuilder.setCallbackPool(discordSRV.scheduler().forkJoinPool()); jdaBuilder.setGatewayPool(gatewayPool); - jdaBuilder.setRateLimitPool(rateLimitPool); + jdaBuilder.setRateLimitPool(rateLimitPool, true); OkHttpClient.Builder httpBuilder = IOUtil.newHttpClientBuilder(); // These 3 are 10 seconds by default @@ -304,17 +304,20 @@ public class JDAConnectionManager implements DiscordConnectionManager { instance.shutdown(); try { + discordSRV.logger().info("Waiting up to " + TimeUnit.MILLISECONDS.toSeconds(timeoutMillis) + " seconds for JDA to shutdown..."); discordSRV.scheduler().run(() -> { try { - while (instance != null && instance.getStatus() != JDA.Status.SHUTDOWN) { + while (instance != null && !rateLimitPool.isShutdown()) { Thread.sleep(50); } } catch (InterruptedException ignored) {} }).get(timeoutMillis, TimeUnit.MILLISECONDS); instance = null; shutdownExecutors(); + discordSRV.logger().info("JDA shutdown completed."); } catch (TimeoutException | ExecutionException e) { try { + discordSRV.logger().info("JDA failed to shutdown within the timeout, cancelling any remaining requests"); shutdownNow(); } catch (Throwable t) { if (e instanceof ExecutionException) { @@ -332,13 +335,14 @@ public class JDAConnectionManager implements DiscordConnectionManager { instance = null; } shutdownExecutors(); + discordSRV.logger().info("JDA shutdown completed."); } private void shutdownExecutors() { if (gatewayPool != null) { gatewayPool.shutdownNow(); } - if (rateLimitPool != null) { + if (rateLimitPool != null && !rateLimitPool.isShutdown()) { rateLimitPool.shutdownNow(); } } diff --git a/common/src/main/java/com/discordsrv/common/logging/impl/DependencyLoggingHandler.java b/common/src/main/java/com/discordsrv/common/logging/impl/DependencyLoggingHandler.java index 93c47ac9..27e94db6 100644 --- a/common/src/main/java/com/discordsrv/common/logging/impl/DependencyLoggingHandler.java +++ b/common/src/main/java/com/discordsrv/common/logging/impl/DependencyLoggingHandler.java @@ -19,12 +19,13 @@ package com.discordsrv.common.logging.impl; import com.discordsrv.common.DiscordSRV; -import com.discordsrv.common.logging.LogLevel; import com.discordsrv.common.logging.LogAppender; +import com.discordsrv.common.logging.LogLevel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.concurrent.RejectedExecutionException; public class DependencyLoggingHandler implements LogAppender { @@ -89,6 +90,14 @@ public class DependencyLoggingHandler implements LogAppender { } } + if (name.equals("JDA") && message != null + && message.contains("Got an unexpected error. Please redirect the following message to the devs:") + && throwable instanceof RejectedExecutionException + && discordSRV.status().isShutdown()) { + // Might happen if the server shuts down while JDA is starting + return; + } + discordSRV.logger().log(null, logLevel, "[" + name + "]" + (message != null ? " " + message : ""), throwable); } } diff --git a/common/src/main/java/com/discordsrv/common/logging/impl/DiscordSRVLogger.java b/common/src/main/java/com/discordsrv/common/logging/impl/DiscordSRVLogger.java index 5e42d442..26569764 100644 --- a/common/src/main/java/com/discordsrv/common/logging/impl/DiscordSRVLogger.java +++ b/common/src/main/java/com/discordsrv/common/logging/impl/DiscordSRVLogger.java @@ -162,8 +162,13 @@ public class DiscordSRVLogger implements Logger { Files.write(path, line.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND); } catch (Throwable e) { - // Prevent infinite loop - discordSRV.platformLogger().error("Failed to write to debug log", e); + try { + // Prevent infinite loop + if (discordSRV.status() == DiscordSRV.Status.SHUTDOWN) { + return; + } + discordSRV.platformLogger().error("Failed to write to debug log", e); + } catch (Throwable ignored) {} } } diff --git a/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java b/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java index 65ed54c8..0157a1fc 100644 --- a/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java +++ b/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java @@ -104,11 +104,11 @@ public class DiscordMessageMirroringModule extends AbstractModule { } } - discordSRV.discordAPI().findOrCreateThreads(iChannelConfig, threadChannel -> { + discordSRV.discordAPI().findOrCreateThreads(channelConfig, iChannelConfig, threadChannel -> { if (threadChannel.getId() != channel.getId()) { mirrorChannels.add(Pair.of(threadChannel, config)); } - }, futures); + }, futures, false); } CompletableFutureUtil.combine(futures).whenComplete((v, t) -> { diff --git a/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java b/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java index 52dfd696..00c96f48 100644 --- a/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java +++ b/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java @@ -78,24 +78,24 @@ public abstract class AbstractGameMessageModule extend private CompletableFuture forwardToChannel( @Nullable AbstractGameMessageReceiveEvent event, @Nullable DiscordSRVPlayer player, - @NotNull OrDefault channelConfig + @NotNull OrDefault config ) { - OrDefault config = mapConfig(channelConfig); - if (!config.get(IMessageConfig::enabled, true)) { + OrDefault moduleConfig = mapConfig(config); + if (!moduleConfig.get(IMessageConfig::enabled, true)) { return null; } - IChannelConfig iChannelConfig = channelConfig.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null); - if (iChannelConfig == null) { + IChannelConfig channelConfig = config.get(c -> c instanceof IChannelConfig ? (IChannelConfig) c : null); + if (channelConfig == null) { return null; } List messageChannels = new CopyOnWriteArrayList<>(); List> futures = new ArrayList<>(); - List channelIds = iChannelConfig.channelIds(); + List channelIds = channelConfig.channelIds(); if (channelIds != null) { - for (Long channelId : iChannelConfig.channelIds()) { + for (Long channelId : channelConfig.channelIds()) { DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null); if (textChannel != null) { messageChannels.add(textChannel); @@ -107,24 +107,24 @@ public abstract class AbstractGameMessageModule extend } } - discordSRV.discordAPI().findOrCreateThreads(iChannelConfig, messageChannels::add, futures); + discordSRV.discordAPI().findOrCreateThreads(config, channelConfig, messageChannels::add, futures, true); - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).whenComplete((v, t1) -> { - SendableDiscordMessage.Builder format = config.get(IMessageConfig::format); + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenCompose((v) -> { + SendableDiscordMessage.Builder format = moduleConfig.get(IMessageConfig::format); if (format == null) { - return; + return CompletableFuture.completedFuture(null); } Component component = event != null ? ComponentUtil.fromAPI(event.getMessage()) : null; - String message = component != null ? convertMessage(config, component) : null; + String message = component != null ? convertMessage(moduleConfig, component) : null; Map, DiscordMessageChannel> messageFutures; messageFutures = sendMessageToChannels( - config, format, messageChannels, message, + moduleConfig, format, messageChannels, message, // Context channelConfig, player ); - CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0])) + return CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0])) .whenComplete((vo, t2) -> { Set messages = new LinkedHashSet<>(); for (Map.Entry, DiscordMessageChannel> entry : messageFutures.entrySet()) { diff --git a/common/src/main/java/com/discordsrv/common/module/ModuleManager.java b/common/src/main/java/com/discordsrv/common/module/ModuleManager.java index ab9da7c7..9debee0b 100644 --- a/common/src/main/java/com/discordsrv/common/module/ModuleManager.java +++ b/common/src/main/java/com/discordsrv/common/module/ModuleManager.java @@ -142,9 +142,9 @@ public class ModuleManager { @Subscribe(priority = EventPriority.EARLY) public void onShuttingDown(DiscordSRVShuttingDownEvent event) { - for (Module module : modules) { - unregister(module); - } + modules.stream() + .sorted((m1, m2) -> Integer.compare(m2.shutdownOrder(), m1.shutdownOrder())) + .forEachOrdered(Module::disable); } public void reload() {