mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-01 08:39:31 +01:00
Add channel auto locking, implement thread archiving. More fixes to shutdown not working properly/ errors when the server is stopped before DiscordSRV starts
This commit is contained in:
parent
51fd9ef6d8
commit
bbc722e689
@ -39,6 +39,10 @@ public interface Module {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default int shutdownOrder() {
|
||||||
|
return priority(getClass());
|
||||||
|
}
|
||||||
|
|
||||||
default void enable() {
|
default void enable() {
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
|||||||
import com.discordsrv.api.module.type.Module;
|
import com.discordsrv.api.module.type.Module;
|
||||||
import com.discordsrv.common.api.util.ApiInstanceUtil;
|
import com.discordsrv.common.api.util.ApiInstanceUtil;
|
||||||
import com.discordsrv.common.channel.ChannelConfigHelper;
|
import com.discordsrv.common.channel.ChannelConfigHelper;
|
||||||
|
import com.discordsrv.common.channel.ChannelShutdownBehaviourModule;
|
||||||
import com.discordsrv.common.channel.ChannelUpdaterModule;
|
import com.discordsrv.common.channel.ChannelUpdaterModule;
|
||||||
import com.discordsrv.common.channel.GlobalChannelLookupModule;
|
import com.discordsrv.common.channel.GlobalChannelLookupModule;
|
||||||
import com.discordsrv.common.command.game.GameCommandModule;
|
import com.discordsrv.common.command.game.GameCommandModule;
|
||||||
@ -388,7 +389,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable runnable) {
|
protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable runnable) {
|
||||||
return invoke(() -> {
|
return invokeLifecycle(() -> {
|
||||||
try {
|
try {
|
||||||
lifecycleLock.lock();
|
lifecycleLock.lock();
|
||||||
runnable.run();
|
runnable.run();
|
||||||
@ -398,11 +399,19 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
}, "Failed to enable", true);
|
}, "Failed to enable", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CompletableFuture<Void> invoke(CheckedRunnable runnable, String message, boolean enable) {
|
protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable runnable, String message, boolean enable) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
if (status().isShutdown()) {
|
||||||
|
// Already shutdown/shutting down, don't bother
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
if (status().isShutdown() && t instanceof NoClassDefFoundError) {
|
||||||
|
// Already shutdown, ignore errors for classes that already got unloaded
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (enable) {
|
if (enable) {
|
||||||
setStatus(Status.FAILED_TO_START);
|
setStatus(Status.FAILED_TO_START);
|
||||||
disable();
|
disable();
|
||||||
@ -424,6 +433,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
@Override
|
@Override
|
||||||
public final CompletableFuture<Void> invokeDisable() {
|
public final CompletableFuture<Void> invokeDisable() {
|
||||||
if (enableFuture != null && !enableFuture.isDone()) {
|
if (enableFuture != null && !enableFuture.isDone()) {
|
||||||
|
logger().warning("Start cancelled");
|
||||||
enableFuture.cancel(true);
|
enableFuture.cancel(true);
|
||||||
}
|
}
|
||||||
return CompletableFuture.runAsync(this::disable, scheduler().executorService());
|
return CompletableFuture.runAsync(this::disable, scheduler().executorService());
|
||||||
@ -431,7 +441,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final CompletableFuture<Void> invokeReload(Set<ReloadFlag> flags, boolean silent) {
|
public final CompletableFuture<Void> invokeReload(Set<ReloadFlag> flags, boolean silent) {
|
||||||
return invoke(() -> reload(flags, silent), "Failed to reload", false);
|
return invokeLifecycle(() -> reload(flags, silent), "Failed to reload", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OverridingMethodsMustInvokeSuper
|
@OverridingMethodsMustInvokeSuper
|
||||||
@ -453,6 +463,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
placeholderService().addGlobalContext(new GlobalTextHandlingContext(this));
|
placeholderService().addGlobalContext(new GlobalTextHandlingContext(this));
|
||||||
|
|
||||||
// Modules
|
// Modules
|
||||||
|
registerModule(ChannelShutdownBehaviourModule::new);
|
||||||
registerModule(ChannelUpdaterModule::new);
|
registerModule(ChannelUpdaterModule::new);
|
||||||
registerModule(GameCommandModule::new);
|
registerModule(GameCommandModule::new);
|
||||||
registerModule(GlobalChannelLookupModule::new);
|
registerModule(GlobalChannelLookupModule::new);
|
||||||
@ -588,6 +599,16 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
}
|
}
|
||||||
if (!initial) {
|
if (!initial) {
|
||||||
waitForStatus(Status.CONNECTED, 20, TimeUnit.SECONDS);
|
waitForStatus(Status.CONNECTED, 20, TimeUnit.SECONDS);
|
||||||
|
} else {
|
||||||
|
JDA jda = jda().orElse(null);
|
||||||
|
if (jda != null) {
|
||||||
|
try {
|
||||||
|
jda.awaitReady();
|
||||||
|
} catch (IllegalStateException ignored) {
|
||||||
|
// JDA shutdown -> don't continue
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
throw e.getCause();
|
throw e.getCause();
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<DiscordSRV> {
|
||||||
|
|
||||||
|
public ChannelShutdownBehaviourModule(DiscordSRV discordSRV) {
|
||||||
|
super(discordSRV);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int shutdownOrder() {
|
||||||
|
return Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
doForAllChannels((config, channelConfig) -> {
|
||||||
|
OrDefault<ShutdownBehaviourConfig> shutdownConfig = config.map(cfg -> cfg.shutdownBehaviour);
|
||||||
|
OrDefault<ShutdownBehaviourConfig.Channels> channels = shutdownConfig.map(cfg -> cfg.channels);
|
||||||
|
OrDefault<ShutdownBehaviourConfig.Threads> 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<ShutdownBehaviourConfig> shutdownConfig = config.map(cfg -> cfg.shutdownBehaviour);
|
||||||
|
OrDefault<ShutdownBehaviourConfig.Channels> channels = shutdownConfig.map(cfg -> cfg.channels);
|
||||||
|
OrDefault<ShutdownBehaviourConfig.Threads> 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<ShutdownBehaviourConfig.Channels> shutdownConfig,
|
||||||
|
boolean state
|
||||||
|
) {
|
||||||
|
JDA jda = discordSRV.jda().orElse(null);
|
||||||
|
if (jda == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean everyone = shutdownConfig.get(cfg -> cfg.everyone, false);
|
||||||
|
List<Long> roleIds = shutdownConfig.get(cfg -> cfg.roleIds, Collections.emptyList());
|
||||||
|
if (!everyone && roleIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Permission> 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<Permission> 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<OrDefault<BaseChannelConfig>, IChannelConfig> channelConsumer) {
|
||||||
|
for (OrDefault<BaseChannelConfig> config : discordSRV.channelConfig().getAllChannels()) {
|
||||||
|
IChannelConfig channelConfig = config.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null);
|
||||||
|
if (channelConfig == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelConsumer.accept(config, channelConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<Long> 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -31,14 +31,20 @@ public class BaseChannelConfig {
|
|||||||
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
|
public DiscordToMinecraftChatConfig discordToMinecraft = new DiscordToMinecraftChatConfig();
|
||||||
public JoinMessageConfig joinMessages = new JoinMessageConfig();
|
public JoinMessageConfig joinMessages = new JoinMessageConfig();
|
||||||
public LeaveMessageConfig leaveMessages = new LeaveMessageConfig();
|
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();
|
public StartMessageConfig startMessage = new StartMessageConfig();
|
||||||
|
@Order(20)
|
||||||
public StopMessageConfig stopMessage = new StopMessageConfig();
|
public StopMessageConfig stopMessage = new StopMessageConfig();
|
||||||
|
|
||||||
@Order(10)
|
@Order(30)
|
||||||
@Comment("Settings for synchronizing messages between the defined Discord channels and threads")
|
@Comment("Settings for synchronizing messages between the defined Discord channels and threads")
|
||||||
public MirroringConfig mirroring = new MirroringConfig();
|
public MirroringConfig mirroring = new MirroringConfig();
|
||||||
|
|
||||||
@Untranslated(Untranslated.Type.VALUE)
|
|
||||||
@Order(50)
|
@Order(50)
|
||||||
public String avatarUrlProvider = "https://heads.discordsrv.com/head.png?texture=%texture%&uuid=%uuid%&name=%username%&overlay";
|
public ShutdownBehaviourConfig shutdownBehaviour = new ShutdownBehaviourConfig();
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,4 @@ public class ThreadConfig {
|
|||||||
public String threadName = "Minecraft Server chat bridge";
|
public String threadName = "Minecraft Server chat bridge";
|
||||||
public boolean privateThread = false;
|
public boolean privateThread = false;
|
||||||
|
|
||||||
public boolean archiveOnShutdown = true;
|
|
||||||
public boolean unarchive = true;
|
|
||||||
}
|
}
|
||||||
|
@ -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.DiscordGuildImpl;
|
||||||
import com.discordsrv.common.discord.api.entity.guild.DiscordRoleImpl;
|
import com.discordsrv.common.discord.api.entity.guild.DiscordRoleImpl;
|
||||||
import com.discordsrv.common.function.CheckedSupplier;
|
import com.discordsrv.common.function.CheckedSupplier;
|
||||||
|
import com.discordsrv.common.function.OrDefault;
|
||||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||||
import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
|
import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
|
||||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||||
@ -98,9 +99,9 @@ public class DiscordAPIImpl implements DiscordAPI {
|
|||||||
* @param config the config that specified the threads
|
* @param config the config that specified the threads
|
||||||
* @return the list of active threads
|
* @return the list of active threads
|
||||||
*/
|
*/
|
||||||
public List<DiscordThreadChannel> findThreads(IChannelConfig config) {
|
public List<DiscordThreadChannel> findThreads(OrDefault<BaseChannelConfig> config, IChannelConfig channelConfig) {
|
||||||
List<DiscordThreadChannel> channels = new ArrayList<>();
|
List<DiscordThreadChannel> channels = new ArrayList<>();
|
||||||
findOrCreateThreads(config, channels::add, null);
|
findOrCreateThreads(config, channelConfig, channels::add, null, false);
|
||||||
return channels;
|
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
|
* @param futures a possibly null list of {@link CompletableFuture} for tasks that need to be completed to get all threads
|
||||||
*/
|
*/
|
||||||
public void findOrCreateThreads(
|
public void findOrCreateThreads(
|
||||||
IChannelConfig config,
|
OrDefault<BaseChannelConfig> config,
|
||||||
|
IChannelConfig channelConfig,
|
||||||
Consumer<DiscordThreadChannel> channelConsumer,
|
Consumer<DiscordThreadChannel> channelConsumer,
|
||||||
@Nullable List<CompletableFuture<DiscordThreadChannel>> futures
|
@Nullable List<CompletableFuture<DiscordThreadChannel>> futures,
|
||||||
|
boolean log
|
||||||
) {
|
) {
|
||||||
List<ThreadConfig> threads = config.threads();
|
List<ThreadConfig> threads = channelConfig.threads();
|
||||||
if (threads == null) {
|
if (threads == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -124,10 +127,8 @@ public class DiscordAPIImpl implements DiscordAPI {
|
|||||||
long channelId = threadConfig.channelId;
|
long channelId = threadConfig.channelId;
|
||||||
DiscordTextChannel channel = getTextChannelById(channelId).orElse(null);
|
DiscordTextChannel channel = getTextChannelById(channelId).orElse(null);
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
if (channelId > 0) {
|
if (channelId > 0 && log) {
|
||||||
discordSRV.logger().error("Unable to find channel with ID "
|
discordSRV.logger().error("Unable to find channel with ID " + Long.toUnsignedString(channelId));
|
||||||
+ Long.toUnsignedString(channelId)
|
|
||||||
+ ", unable to forward message to Discord");
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -136,7 +137,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
|||||||
DiscordThreadChannel thread = findThread(threadConfig, channel.getActiveThreads());
|
DiscordThreadChannel thread = findThread(threadConfig, channel.getActiveThreads());
|
||||||
if (thread != null) {
|
if (thread != null) {
|
||||||
ThreadChannel jdaChannel = thread.getAsJDAThreadChannel();
|
ThreadChannel jdaChannel = thread.getAsJDAThreadChannel();
|
||||||
if (!jdaChannel.isLocked() && !jdaChannel.isArchived()) {
|
if (!jdaChannel.isArchived()) {
|
||||||
channelConsumer.accept(thread);
|
channelConsumer.accept(thread);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -154,7 +155,7 @@ public class DiscordAPIImpl implements DiscordAPI {
|
|||||||
unarchiveOrCreateThread(threadConfig, channel, thread, future);
|
unarchiveOrCreateThread(threadConfig, channel, thread, future);
|
||||||
} else {
|
} else {
|
||||||
// Find or create the thread
|
// Find or create the thread
|
||||||
future = findOrCreateThread(threadConfig, channel);
|
future = findOrCreateThread(config, threadConfig, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
futures.add(future.handle((threadChannel, t) -> {
|
futures.add(future.handle((threadChannel, t) -> {
|
||||||
@ -182,16 +183,20 @@ public class DiscordAPIImpl implements DiscordAPI {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<DiscordThreadChannel> findOrCreateThread(ThreadConfig config, DiscordTextChannel textChannel) {
|
private CompletableFuture<DiscordThreadChannel> findOrCreateThread(
|
||||||
if (!config.unarchive) {
|
OrDefault<BaseChannelConfig> config,
|
||||||
return textChannel.createThread(config.threadName, config.privateThread);
|
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<DiscordThreadChannel> completableFuture = new CompletableFuture<>();
|
CompletableFuture<DiscordThreadChannel> completableFuture = new CompletableFuture<>();
|
||||||
lookupThreads(
|
lookupThreads(
|
||||||
textChannel,
|
textChannel,
|
||||||
config.privateThread,
|
threadConfig.privateThread,
|
||||||
lookup -> findOrCreateThread(config, textChannel, lookup, completableFuture),
|
lookup -> findOrCreateThread(threadConfig, textChannel, lookup, completableFuture),
|
||||||
(thread, throwable) -> {
|
(thread, throwable) -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
completableFuture.completeExceptionally(throwable);
|
completableFuture.completeExceptionally(throwable);
|
||||||
@ -244,10 +249,10 @@ public class DiscordAPIImpl implements DiscordAPI {
|
|||||||
ThreadChannel channel = thread.getAsJDAThreadChannel();
|
ThreadChannel channel = thread.getAsJDAThreadChannel();
|
||||||
if (channel.isLocked() || channel.isArchived()) {
|
if (channel.isLocked() || channel.isArchived()) {
|
||||||
try {
|
try {
|
||||||
channel.getManager().setArchived(false).queue(
|
channel.getManager()
|
||||||
v -> future.complete(thread),
|
.setArchived(false)
|
||||||
future::completeExceptionally
|
.reason("DiscordSRV Auto Unarchive")
|
||||||
);
|
.queue(v -> future.complete(thread), future::completeExceptionally);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
future.completeExceptionally(t);
|
future.completeExceptionally(t);
|
||||||
}
|
}
|
||||||
|
@ -253,7 +253,7 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
|||||||
// Our own (named) threads
|
// Our own (named) threads
|
||||||
jdaBuilder.setCallbackPool(discordSRV.scheduler().forkJoinPool());
|
jdaBuilder.setCallbackPool(discordSRV.scheduler().forkJoinPool());
|
||||||
jdaBuilder.setGatewayPool(gatewayPool);
|
jdaBuilder.setGatewayPool(gatewayPool);
|
||||||
jdaBuilder.setRateLimitPool(rateLimitPool);
|
jdaBuilder.setRateLimitPool(rateLimitPool, true);
|
||||||
|
|
||||||
OkHttpClient.Builder httpBuilder = IOUtil.newHttpClientBuilder();
|
OkHttpClient.Builder httpBuilder = IOUtil.newHttpClientBuilder();
|
||||||
// These 3 are 10 seconds by default
|
// These 3 are 10 seconds by default
|
||||||
@ -304,17 +304,20 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
|||||||
instance.shutdown();
|
instance.shutdown();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
discordSRV.logger().info("Waiting up to " + TimeUnit.MILLISECONDS.toSeconds(timeoutMillis) + " seconds for JDA to shutdown...");
|
||||||
discordSRV.scheduler().run(() -> {
|
discordSRV.scheduler().run(() -> {
|
||||||
try {
|
try {
|
||||||
while (instance != null && instance.getStatus() != JDA.Status.SHUTDOWN) {
|
while (instance != null && !rateLimitPool.isShutdown()) {
|
||||||
Thread.sleep(50);
|
Thread.sleep(50);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ignored) {}
|
} catch (InterruptedException ignored) {}
|
||||||
}).get(timeoutMillis, TimeUnit.MILLISECONDS);
|
}).get(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||||
instance = null;
|
instance = null;
|
||||||
shutdownExecutors();
|
shutdownExecutors();
|
||||||
|
discordSRV.logger().info("JDA shutdown completed.");
|
||||||
} catch (TimeoutException | ExecutionException e) {
|
} catch (TimeoutException | ExecutionException e) {
|
||||||
try {
|
try {
|
||||||
|
discordSRV.logger().info("JDA failed to shutdown within the timeout, cancelling any remaining requests");
|
||||||
shutdownNow();
|
shutdownNow();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
if (e instanceof ExecutionException) {
|
if (e instanceof ExecutionException) {
|
||||||
@ -332,13 +335,14 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
|||||||
instance = null;
|
instance = null;
|
||||||
}
|
}
|
||||||
shutdownExecutors();
|
shutdownExecutors();
|
||||||
|
discordSRV.logger().info("JDA shutdown completed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdownExecutors() {
|
private void shutdownExecutors() {
|
||||||
if (gatewayPool != null) {
|
if (gatewayPool != null) {
|
||||||
gatewayPool.shutdownNow();
|
gatewayPool.shutdownNow();
|
||||||
}
|
}
|
||||||
if (rateLimitPool != null) {
|
if (rateLimitPool != null && !rateLimitPool.isShutdown()) {
|
||||||
rateLimitPool.shutdownNow();
|
rateLimitPool.shutdownNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,13 @@
|
|||||||
package com.discordsrv.common.logging.impl;
|
package com.discordsrv.common.logging.impl;
|
||||||
|
|
||||||
import com.discordsrv.common.DiscordSRV;
|
import com.discordsrv.common.DiscordSRV;
|
||||||
import com.discordsrv.common.logging.LogLevel;
|
|
||||||
import com.discordsrv.common.logging.LogAppender;
|
import com.discordsrv.common.logging.LogAppender;
|
||||||
|
import com.discordsrv.common.logging.LogLevel;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
public class DependencyLoggingHandler implements LogAppender {
|
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);
|
discordSRV.logger().log(null, logLevel, "[" + name + "]" + (message != null ? " " + message : ""), throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,8 +162,13 @@ public class DiscordSRVLogger implements Logger {
|
|||||||
|
|
||||||
Files.write(path, line.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
|
Files.write(path, line.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// Prevent infinite loop
|
try {
|
||||||
discordSRV.platformLogger().error("Failed to write to debug log", e);
|
// Prevent infinite loop
|
||||||
|
if (discordSRV.status() == DiscordSRV.Status.SHUTDOWN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
discordSRV.platformLogger().error("Failed to write to debug log", e);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +104,11 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
discordSRV.discordAPI().findOrCreateThreads(iChannelConfig, threadChannel -> {
|
discordSRV.discordAPI().findOrCreateThreads(channelConfig, iChannelConfig, threadChannel -> {
|
||||||
if (threadChannel.getId() != channel.getId()) {
|
if (threadChannel.getId() != channel.getId()) {
|
||||||
mirrorChannels.add(Pair.of(threadChannel, config));
|
mirrorChannels.add(Pair.of(threadChannel, config));
|
||||||
}
|
}
|
||||||
}, futures);
|
}, futures, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFutureUtil.combine(futures).whenComplete((v, t) -> {
|
CompletableFutureUtil.combine(futures).whenComplete((v, t) -> {
|
||||||
|
@ -78,24 +78,24 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig> extend
|
|||||||
private CompletableFuture<Void> forwardToChannel(
|
private CompletableFuture<Void> forwardToChannel(
|
||||||
@Nullable AbstractGameMessageReceiveEvent event,
|
@Nullable AbstractGameMessageReceiveEvent event,
|
||||||
@Nullable DiscordSRVPlayer player,
|
@Nullable DiscordSRVPlayer player,
|
||||||
@NotNull OrDefault<BaseChannelConfig> channelConfig
|
@NotNull OrDefault<BaseChannelConfig> config
|
||||||
) {
|
) {
|
||||||
OrDefault<T> config = mapConfig(channelConfig);
|
OrDefault<T> moduleConfig = mapConfig(config);
|
||||||
if (!config.get(IMessageConfig::enabled, true)) {
|
if (!moduleConfig.get(IMessageConfig::enabled, true)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
IChannelConfig iChannelConfig = channelConfig.get(cfg -> cfg instanceof IChannelConfig ? (IChannelConfig) cfg : null);
|
IChannelConfig channelConfig = config.get(c -> c instanceof IChannelConfig ? (IChannelConfig) c : null);
|
||||||
if (iChannelConfig == null) {
|
if (channelConfig == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DiscordMessageChannel> messageChannels = new CopyOnWriteArrayList<>();
|
List<DiscordMessageChannel> messageChannels = new CopyOnWriteArrayList<>();
|
||||||
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
|
List<CompletableFuture<DiscordThreadChannel>> futures = new ArrayList<>();
|
||||||
|
|
||||||
List<Long> channelIds = iChannelConfig.channelIds();
|
List<Long> channelIds = channelConfig.channelIds();
|
||||||
if (channelIds != null) {
|
if (channelIds != null) {
|
||||||
for (Long channelId : iChannelConfig.channelIds()) {
|
for (Long channelId : channelConfig.channelIds()) {
|
||||||
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null);
|
DiscordTextChannel textChannel = discordSRV.discordAPI().getTextChannelById(channelId).orElse(null);
|
||||||
if (textChannel != null) {
|
if (textChannel != null) {
|
||||||
messageChannels.add(textChannel);
|
messageChannels.add(textChannel);
|
||||||
@ -107,24 +107,24 @@ public abstract class AbstractGameMessageModule<T extends IMessageConfig> 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) -> {
|
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenCompose((v) -> {
|
||||||
SendableDiscordMessage.Builder format = config.get(IMessageConfig::format);
|
SendableDiscordMessage.Builder format = moduleConfig.get(IMessageConfig::format);
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
return;
|
return CompletableFuture.completedFuture(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Component component = event != null ? ComponentUtil.fromAPI(event.getMessage()) : 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<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> messageFutures;
|
Map<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> messageFutures;
|
||||||
messageFutures = sendMessageToChannels(
|
messageFutures = sendMessageToChannels(
|
||||||
config, format, messageChannels, message,
|
moduleConfig, format, messageChannels, message,
|
||||||
// Context
|
// Context
|
||||||
channelConfig, player
|
channelConfig, player
|
||||||
);
|
);
|
||||||
|
|
||||||
CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0]))
|
return CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0]))
|
||||||
.whenComplete((vo, t2) -> {
|
.whenComplete((vo, t2) -> {
|
||||||
Set<ReceivedDiscordMessage> messages = new LinkedHashSet<>();
|
Set<ReceivedDiscordMessage> messages = new LinkedHashSet<>();
|
||||||
for (Map.Entry<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> entry : messageFutures.entrySet()) {
|
for (Map.Entry<CompletableFuture<ReceivedDiscordMessage>, DiscordMessageChannel> entry : messageFutures.entrySet()) {
|
||||||
|
@ -142,9 +142,9 @@ public class ModuleManager {
|
|||||||
|
|
||||||
@Subscribe(priority = EventPriority.EARLY)
|
@Subscribe(priority = EventPriority.EARLY)
|
||||||
public void onShuttingDown(DiscordSRVShuttingDownEvent event) {
|
public void onShuttingDown(DiscordSRVShuttingDownEvent event) {
|
||||||
for (Module module : modules) {
|
modules.stream()
|
||||||
unregister(module);
|
.sorted((m1, m2) -> Integer.compare(m2.shutdownOrder(), m1.shutdownOrder()))
|
||||||
}
|
.forEachOrdered(Module::disable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reload() {
|
public void reload() {
|
||||||
|
Loading…
Reference in New Issue
Block a user