Skip to content

Commit 4861c53

Browse files
authored
Merge pull request #123 from Matyrobbrt/command-clearing
Add command clearing to the shutdown command
2 parents 4480316 + 9cdf808 commit 4861c53

File tree

11 files changed

+240
-33
lines changed

11 files changed

+240
-33
lines changed

dependencies.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ ext {
1212
jdaChewtilsVersion = "2.0-interactions-SNAPSHOT"
1313
guavaVersion = "31.0.1-jre"
1414
gsonVersion = "2.8.9"
15-
logbackClassicVersion = "1.2.10"
15+
logbackClassicVersion = "1.2.5"
1616
tomlVersion = "3.6.5"
1717
sqliteJdbcVersion = "3.36.0.3"
1818
linkieCoreVersion = "1.0.92"

src/main/java/com/mcmoddev/mmdbot/modules/commands/bot/management/CmdShutdown.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,17 @@
2323
import com.jagrosh.jdautilities.command.SlashCommand;
2424
import com.mcmoddev.mmdbot.MMDBot;
2525
import com.jagrosh.jdautilities.command.SlashCommandEvent;
26+
import com.mcmoddev.mmdbot.utilities.Utils;
27+
import net.dv8tion.jda.api.interactions.InteractionHook;
28+
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
29+
import net.dv8tion.jda.api.interactions.commands.OptionType;
30+
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
2631

32+
import java.util.List;
2733
import java.util.Timer;
2834
import java.util.TimerTask;
35+
import java.util.concurrent.ExecutionException;
36+
import java.util.concurrent.atomic.AtomicReference;
2937

3038
/**
3139
* Shut down the bot and the JDA instance gracefully.
@@ -44,6 +52,9 @@ public CmdShutdown() {
4452
help = "Shuts the bot down without restarting it. (Only usable by KiriCattus)";
4553
category = new Category("Management");
4654
ownerCommand = true;
55+
guildOnly = false;
56+
options = List.of(new OptionData(OptionType.BOOLEAN, "clear_global", "If the shutdown should clear global commands."),
57+
new OptionData(OptionType.BOOLEAN, "clear_guild", "If the shutdown should clear guild commands."));
4758
}
4859

4960
/**
@@ -54,8 +65,56 @@ public CmdShutdown() {
5465
@Override
5566
protected void execute(final SlashCommandEvent event) {
5667
event.reply("Shutting down the bot!").queue();
68+
final var clearGlobal = Utils.getArgumentOr(event, "clear_global", OptionMapping::getAsBoolean, false);
69+
final var clearGuild = Utils.getArgumentOr(event, "clear_guild", OptionMapping::getAsBoolean, false);
70+
if (clearGuild && !event.isFromGuild()) {
71+
event.reply("You cannot clear guild commands if you are not in a guild!").queue();
72+
return;
73+
}
74+
if (clearGuild) {
75+
try {
76+
AtomicReference<InteractionHook> msg = new AtomicReference<>(
77+
event.getInteraction().reply("Waiting for command deletion...").submit().get());
78+
MMDBot.LOGGER.warn(
79+
"Deleting the guild commands of the guild with the id {} at the request of {} via Discord!",
80+
event.getGuild().getIdLong(), event.getUser().getName());
81+
new Thread(() -> {
82+
Utils.clearGuildCommands(event.getGuild(), () -> {
83+
msg.get().editOriginal("Shutting down the bot!").queue();
84+
executeShutdown(event);
85+
});
86+
}, "GuildCommandClearing").start();
87+
} catch (InterruptedException | ExecutionException e) {
88+
e.printStackTrace();
89+
event.getInteraction().reply("Error! Shutdown cancelled!").queue();
90+
}
91+
return;
92+
}
93+
if (clearGlobal) {
94+
try {
95+
AtomicReference<InteractionHook> msg = new AtomicReference<>(
96+
event.getInteraction().reply("Waiting for command deletion...").submit().get());
97+
MMDBot.LOGGER.warn(
98+
"Deleting the global commands at the request of {} via Discord!", event.getUser().getName());
99+
new Thread(() -> {
100+
Utils.clearGlobalCommands(() -> {
101+
msg.get().editOriginal("Shutting down the bot!").queue();
102+
executeShutdown(event);
103+
});
104+
}, "GlobalCommandClearing").start();
105+
} catch (InterruptedException | ExecutionException e) {
106+
e.printStackTrace();
107+
event.getInteraction().reply("Error! Shutdown cancelled!").queue();
108+
}
109+
return;
110+
}
111+
event.deferReply().setContent("Shutting down the bot!").mentionRepliedUser(false).queue();
112+
executeShutdown(event);
113+
}
114+
115+
private void executeShutdown(final SlashCommandEvent event) {
57116
//Shut down the JDA instance gracefully.
58-
event.getJDA().shutdown();
117+
MMDBot.getInstance().shutdown();
59118
MMDBot.LOGGER.warn("Shutting down the bot by request of " + event.getUser().getName() + " via Discord!");
60119
new Timer().schedule(new TimerTask() {
61120
@Override

src/main/java/com/mcmoddev/mmdbot/modules/commands/server/tricks/CmdListTricks.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ protected EmbedBuilder getEmbed(int from) {
8888
public class TrickListListener extends ButtonListener {
8989
@Override
9090
public String getButtonID() {
91-
return "tricklist";
91+
return CmdListTricks.this.getName();
9292
}
9393
}
9494

src/main/java/com/mcmoddev/mmdbot/modules/logging/LoggingModule.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@
3232
import com.mcmoddev.mmdbot.modules.logging.users.UserBanned;
3333
import com.mcmoddev.mmdbot.utilities.ThreadedEventListener;
3434
import com.mcmoddev.mmdbot.utilities.Utils;
35+
import com.mcmoddev.mmdbot.utilities.console.ConsoleChannelButtonListener;
3536
import net.dv8tion.jda.api.entities.TextChannel;
37+
import net.dv8tion.jda.api.hooks.EventListener;
3638

39+
import java.util.concurrent.Executor;
3740
import java.util.concurrent.Executors;
3841
import java.util.function.Consumer;
3942

@@ -45,28 +48,35 @@
4548
*/
4649
public class LoggingModule {
4750

51+
private static final Executor THREAD_POOL = Executors.newFixedThreadPool(2, r -> Utils.setThreadDaemon(new Thread(r, "LoggingListener"), true));
52+
4853
/**
4954
* Setup and load the bots logging module.
5055
*/
5156
public static void setupLoggingModule() {
5257
if (MMDBot.getConfig().isEventLoggingModuleEnabled()) {
5358
MMDBot.getInstance()
5459
.addEventListener(
55-
new EventUserJoined(),
56-
new EventUserLeft(),
57-
new EventNicknameChanged(),
58-
new EventRoleAdded(),
59-
new EventRoleRemoved(),
60-
new EventReactionAdded(),
61-
new UserBanned(),
60+
loggingEvent(new EventUserJoined()),
61+
loggingEvent(new EventUserLeft()),
62+
loggingEvent(new EventNicknameChanged()),
63+
loggingEvent(new EventRoleAdded()),
64+
loggingEvent(new EventRoleRemoved()),
65+
loggingEvent(new EventReactionAdded()),
66+
loggingEvent(new UserBanned()),
67+
loggingEvent(new ConsoleChannelButtonListener()),
6268
new ThreadedEventListener(new ScamDetector(), Executors.newSingleThreadExecutor(r -> Utils.setThreadDaemon(new Thread(r, "ScamDetector"), true))),
63-
ThreadChannelCreatorEvents.create());
69+
loggingEvent(new ThreadChannelCreatorEvents()));
6470
MMDBot.LOGGER.warn("Event logging module enabled and loaded.");
6571
} else {
6672
MMDBot.LOGGER.warn("Event logging module disabled via config, Discord event logging won't work right now!");
6773
}
6874
}
6975

76+
private static EventListener loggingEvent(EventListener listener) {
77+
return new ThreadedEventListener(listener, THREAD_POOL);
78+
}
79+
7080
public static void executeInLoggingChannel(LoggingType loggingType, Consumer<TextChannel> channel) {
7181
Utils.getChannelIfPresent(MMDBot.getConfig().getChannel("events." + loggingType.toString()), channel);
7282
}

src/main/java/com/mcmoddev/mmdbot/modules/logging/misc/ThreadChannelCreatorEvents.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,6 @@
3535

3636
public class ThreadChannelCreatorEvents extends ListenerAdapter {
3737

38-
private static final Executor THREAD_POOL = Executors.newSingleThreadExecutor(r -> Utils.setThreadDaemon(new Thread(r, "ThreadCreator"), true));
39-
40-
public static EventListener create() {
41-
return new ThreadedEventListener(new ThreadChannelCreatorEvents(), THREAD_POOL);
42-
}
43-
44-
private ThreadChannelCreatorEvents() {
45-
46-
}
47-
4838
@Override
4939
public void onMessageReceived(@NotNull final MessageReceivedEvent event) {
5040
if (!event.isFromGuild() || event.isFromThread() || event.isWebhookMessage() || event.getAuthor().isBot() || event.getAuthor().isSystem()) {

src/main/java/com/mcmoddev/mmdbot/utilities/ThreadedEventListener.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,53 @@
2121
package com.mcmoddev.mmdbot.utilities;
2222

2323
import com.mcmoddev.mmdbot.MMDBot;
24+
import com.mcmoddev.mmdbot.utilities.console.MMDMarkers;
25+
import net.dv8tion.jda.api.EmbedBuilder;
2426
import net.dv8tion.jda.api.events.GenericEvent;
2527
import net.dv8tion.jda.api.hooks.EventListener;
28+
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
2629

2730
import javax.annotation.Nonnull;
31+
import java.util.Collections;
32+
import java.util.HashMap;
33+
import java.util.Map;
34+
import java.util.UUID;
2835
import java.util.concurrent.Executor;
2936
import java.util.concurrent.Executors;
3037

3138
public record ThreadedEventListener(EventListener listener, Executor threadPool) implements EventListener {
3239

40+
public static final Map<UUID, Exception> CAUGHT_EXCEPTIONS = Collections.synchronizedMap(new HashMap<>() {
41+
@Override
42+
public Exception put(final UUID key, final Exception value) {
43+
if (size() < 100) {
44+
return super.put(key, value);
45+
}
46+
return get(key);
47+
}
48+
});
49+
3350
public ThreadedEventListener(@Nonnull final EventListener listener) {
3451
this(listener, Executors.newSingleThreadExecutor(r -> Utils.setThreadDaemon(new Thread(r), true)));
3552
}
3653

3754
@Override
3855
public void onEvent(GenericEvent event) {
39-
try {
40-
if (listener != null) {
41-
threadPool.execute(() -> listener.onEvent(event));
42-
}
43-
} catch (Exception e) {
44-
MMDBot.LOGGER.error("Error while executing threaded event!", e);
56+
if (listener != null) {
57+
threadPool.execute(() -> {
58+
try {
59+
listener.onEvent(event);
60+
} catch (Exception e) {
61+
final var id= UUID.randomUUID();
62+
// We need the ID argument for console channel logging!
63+
MMDBot.LOGGER.error(MMDMarkers.CAUGHT_THREADED_EVENT_EXCEPTIONS, "Error while executing threaded event! Error: ", id, e);
64+
// Reply to the user in order to inform them
65+
if (event instanceof IReplyCallback replyCallback) {
66+
replyCallback.deferReply(true).addEmbeds(new EmbedBuilder().setTitle("This interaction failed due to an exception.").setDescription(e.toString()).build()).queue();
67+
}
68+
CAUGHT_EXCEPTIONS.put(id, e);
69+
}
70+
});
4571
}
4672
}
4773

src/main/java/com/mcmoddev/mmdbot/utilities/Utils.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.jetbrains.annotations.NotNull;
4545
import org.jetbrains.annotations.Nullable;
4646

47+
import javax.annotation.Nonnull;
4748
import java.time.Instant;
4849
import java.time.OffsetDateTime;
4950
import java.time.ZoneOffset;
@@ -54,8 +55,10 @@
5455
import java.util.Objects;
5556
import java.util.Optional;
5657
import java.util.StringJoiner;
58+
import java.util.concurrent.ExecutionException;
5759
import java.util.concurrent.TimeUnit;
5860
import java.util.function.Consumer;
61+
import java.util.function.Function;
5962
import java.util.function.LongPredicate;
6063
import java.util.stream.Collectors;
6164

@@ -577,6 +580,15 @@ public static String getOrEmpty(SlashCommandEvent event, String name) {
577580
return getOrEmpty(event.getOption(name));
578581
}
579582

583+
public static <T> T getArgumentOr(SlashCommandEvent event, String name, Function<OptionMapping, T> getter, T orElse) {
584+
final var option = event.getOption(name);
585+
if (option != null) {
586+
return getter.apply(option);
587+
} else {
588+
return orElse;
589+
}
590+
}
591+
580592
/**
581593
* Creates a discord link pointing to the specified message
582594
* @param guildId the ID of the guild of the message
@@ -609,4 +621,34 @@ public static void executeInDMs(final long userId, Consumer<PrivateChannel> cons
609621
public static String uppercaseFirstLetter(final String string) {
610622
return string.substring(0, 1).toUpperCase(Locale.ROOT) + string.substring(1);
611623
}
624+
625+
public static void clearGuildCommands(final @Nonnull Guild guild, final Runnable... after) {
626+
guild.retrieveCommands().queue(cmds -> {
627+
for (int i = 0; i < cmds.size(); i++) {
628+
try {
629+
guild.deleteCommandById(cmds.get(i).getIdLong()).submit().get();
630+
} catch (InterruptedException | ExecutionException e) {
631+
MMDBot.LOGGER.error("Error while trying to clear the commands of the guild {}", guild, e);
632+
}
633+
}
634+
for (var toRun : after) {
635+
toRun.run();
636+
}
637+
});
638+
}
639+
640+
public static void clearGlobalCommands(final Runnable... after) {
641+
MMDBot.getInstance().retrieveCommands().queue(cmds -> {
642+
for (int i = 0; i < cmds.size(); i++) {
643+
try {
644+
MMDBot.getInstance().deleteCommandById(cmds.get(i).getIdLong()).submit().get();
645+
} catch (InterruptedException | ExecutionException e) {
646+
MMDBot.LOGGER.error("Error while trying to clear the global commands", e);
647+
}
648+
}
649+
for (var toRun : after) {
650+
toRun.run();
651+
}
652+
});
653+
}
612654
}

src/main/java/com/mcmoddev/mmdbot/utilities/console/ConsoleChannelAppender.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@
2626
import com.mcmoddev.mmdbot.MMDBot;
2727
import com.mcmoddev.mmdbot.core.BotConfig;
2828
import net.dv8tion.jda.api.MessageBuilder;
29+
import net.dv8tion.jda.api.interactions.components.buttons.Button;
30+
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
2931

3032
import java.util.Collections;
33+
import java.util.UUID;
3134

3235
/**
3336
* A custom {@link ch.qos.logback.core.Appender} for logging to a Discord channel.
@@ -86,12 +89,26 @@ protected void append(final ILoggingEvent event) {
8689
if (channel == null) {
8790
return;
8891
}
89-
final var builder = new MessageBuilder();
90-
builder.append(layout != null ? layout.doLayout(event) : event.getFormattedMessage());
91-
if (!allowMentions) {
92-
builder.setAllowedMentions(Collections.emptyList());
92+
if (event.getMarker() != MMDMarkers.CAUGHT_THREADED_EVENT_EXCEPTIONS) {
93+
final var builder = new MessageBuilder();
94+
builder.append(layout != null ? layout.doLayout(event) : event.getFormattedMessage());
95+
if (!allowMentions) {
96+
builder.setAllowedMentions(Collections.emptyList());
97+
}
98+
channel.sendMessage(builder.build()).queue();
99+
} else {
100+
final var builder = new MessageBuilder();
101+
final var id = (UUID) event.getArgumentArray()[0];
102+
final var exception = event.getThrowableProxy();
103+
builder.append(ConsoleChannelLayout.LEVEL_TO_EMOTE.get(event.getLevel()))
104+
.append(" ");
105+
builder.append(event.getFormattedMessage());
106+
builder.append(exception.getMessage());
107+
if (!allowMentions) {
108+
builder.setAllowedMentions(Collections.emptyList());
109+
}
110+
channel.sendMessage(builder.build()).setActionRow(Button.of(ButtonStyle.PRIMARY, "show_stacktrace_" + id, "Show Stacktrace")).queue();
93111
}
94-
channel.sendMessage(builder.build()).queue();
95112
}
96113
}
97114
}

0 commit comments

Comments
 (0)