diff --git a/application/src/main/java/org/togetherjava/tjbot/Application.java b/application/src/main/java/org/togetherjava/tjbot/Application.java index fb4b93baae..7383bebb8d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/Application.java +++ b/application/src/main/java/org/togetherjava/tjbot/Application.java @@ -5,8 +5,8 @@ import net.dv8tion.jda.api.requests.GatewayIntent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.togetherjava.tjbot.commands.Commands; -import org.togetherjava.tjbot.commands.system.CommandSystem; +import org.togetherjava.tjbot.commands.Features; +import org.togetherjava.tjbot.commands.system.BotCore; import org.togetherjava.tjbot.config.Config; import org.togetherjava.tjbot.db.Database; @@ -22,7 +22,7 @@ * New commands can be created by implementing * {@link net.dv8tion.jda.api.events.interaction.SlashCommandEvent} or extending * {@link org.togetherjava.tjbot.commands.SlashCommandAdapter}. They can then be registered in - * {@link Commands}. + * {@link Features}. */ public enum Application { ; @@ -79,7 +79,7 @@ public static void runBot(String token, Path databasePath) { JDA jda = JDABuilder.createDefault(token) .enableIntents(GatewayIntent.GUILD_MEMBERS) .build(); - jda.addEventListener(new CommandSystem(jda, database)); + jda.addEventListener(new BotCore(jda, database)); jda.awaitReady(); logger.info("Bot is ready"); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java b/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java deleted file mode 100644 index 556ea519d2..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Commands.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.togetherjava.tjbot.commands; - -import net.dv8tion.jda.api.JDA; -import org.jetbrains.annotations.NotNull; -import org.togetherjava.tjbot.commands.basic.PingCommand; -import org.togetherjava.tjbot.commands.basic.VcActivityCommand; -import org.togetherjava.tjbot.commands.free.FreeCommand; -import org.togetherjava.tjbot.commands.mathcommands.TeXCommand; -import org.togetherjava.tjbot.commands.moderation.*; -import org.togetherjava.tjbot.commands.moderation.temp.TemporaryModerationRoutine; -import org.togetherjava.tjbot.commands.tags.TagCommand; -import org.togetherjava.tjbot.commands.tags.TagManageCommand; -import org.togetherjava.tjbot.commands.tags.TagSystem; -import org.togetherjava.tjbot.commands.tags.TagsCommand; -import org.togetherjava.tjbot.db.Database; -import org.togetherjava.tjbot.routines.ModAuditLogRoutine; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Utility class that offers all commands that should be registered by the system. New commands have - * to be added here, where {@link org.togetherjava.tjbot.commands.system.CommandSystem} will then - * pick it up from and register it with the system. - *
- * To add a new slash command, extend the commands returned by - * {@link #createSlashCommands(JDA, Database)}. - */ -public enum Commands { - ; - - /** - * Creates all slash commands that should be registered with this application. - *
- * Calling this method multiple times will result in multiple commands being created, which
- * generally should be avoided.
- *
- * @param jda the JDA instance commands will be registered at
- * @param database the database of the application, which commands can use to persist data
- * @return a collection of all slash commands
- */
- public static @NotNull Collection
+ * All event receivers have to implement this interface. A new receiver can then be registered by
+ * adding it to {@link Features}.
+ *
+ *
+ * After registration, the system will notify a receiver for any incoming Discord event.
+ */
+@FunctionalInterface
+public interface EventReceiver extends EventListener, Feature {
+ // Basically a renaming of JDAs EventListener, plus our Feature marker interface
+}
diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Feature.java b/application/src/main/java/org/togetherjava/tjbot/commands/Feature.java
new file mode 100644
index 0000000000..8b16cca77c
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/Feature.java
@@ -0,0 +1,11 @@
+package org.togetherjava.tjbot.commands;
+
+/**
+ * Interface for features supported by the bots core system.
+ *
+ * New features are added in {@link org.togetherjava.tjbot.commands.Features} and from there picked
+ * up by {@link org.togetherjava.tjbot.commands.system.BotCore}.
+ */
+public interface Feature {
+ // Marker interface
+}
diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java
new file mode 100644
index 0000000000..1a05547d91
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java
@@ -0,0 +1,84 @@
+package org.togetherjava.tjbot.commands;
+
+import net.dv8tion.jda.api.JDA;
+import org.jetbrains.annotations.NotNull;
+import org.togetherjava.tjbot.commands.basic.PingCommand;
+import org.togetherjava.tjbot.commands.basic.VcActivityCommand;
+import org.togetherjava.tjbot.commands.free.FreeCommand;
+import org.togetherjava.tjbot.commands.mathcommands.TeXCommand;
+import org.togetherjava.tjbot.commands.moderation.*;
+import org.togetherjava.tjbot.commands.moderation.temp.TemporaryModerationRoutine;
+import org.togetherjava.tjbot.commands.system.BotCore;
+import org.togetherjava.tjbot.commands.tags.TagCommand;
+import org.togetherjava.tjbot.commands.tags.TagManageCommand;
+import org.togetherjava.tjbot.commands.tags.TagSystem;
+import org.togetherjava.tjbot.commands.tags.TagsCommand;
+import org.togetherjava.tjbot.db.Database;
+import org.togetherjava.tjbot.routines.ModAuditLogRoutine;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Utility class that offers all features that should be registered by the system, such as commands.
+ * New features have to be added here, where {@link BotCore} will then pick it up from and register
+ * it with the system.
+ *
+ * To add a new slash command, extend the commands returned by
+ * {@link #createFeatures(JDA, Database)}.
+ */
+public enum Features {
+ ;
+
+ /**
+ * Creates all features that should be registered with this application.
+ *
+ * Calling this method multiple times will result in multiple features being created, which
+ * generally should be avoided.
+ *
+ * @param jda the JDA instance commands will be registered at
+ * @param database the database of the application, which features can use to persist data
+ * @return a collection of all features
+ */
+ public static @NotNull Collection
+ * All message receivers have to implement this interface. For convenience, there is a
+ * {@link MessageReceiverAdapter} available that implemented most methods already. A new receiver
+ * can then be registered by adding it to {@link Features}.
+ *
+ *
+ * After registration, the system will notify a receiver whenever a new message was sent or an
+ * existing message was updated in any channel matching the {@link #getChannelNamePattern()} the bot
+ * is added to.
+ */
+public interface MessageReceiver extends Feature {
+ /**
+ * Retrieves the pattern matching the names of channels of which this receiver is interested in
+ * receiving sent messages from. Called by the core system once during the startup in order to
+ * register the receiver accordingly.
+ *
+ * Changes on the pattern returned by this method afterwards will not be picked up.
+ *
+ * @return the pattern matching the names of relevant channels
+ */
+ @NotNull
+ Pattern getChannelNamePattern();
+
+ /**
+ * Triggered by the core system whenever a new message was sent and received in a text channel
+ * of a guild the bot has been added to.
+ *
+ * @param event the event that triggered this, containing information about the corresponding
+ * message that was sent and received
+ */
+ void onMessageReceived(@NotNull GuildMessageReceivedEvent event);
+
+ /**
+ * Triggered by the core system whenever an existing message was edited in a text channel of a
+ * guild the bot has been added to.
+ *
+ * @param event the event that triggered this, containing information about the corresponding
+ * message that was edited
+ */
+ void onMessageUpdated(@NotNull GuildMessageUpdateEvent event);
+}
diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java
new file mode 100644
index 0000000000..506d2aa6e7
--- /dev/null
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/MessageReceiverAdapter.java
@@ -0,0 +1,47 @@
+package org.togetherjava.tjbot.commands;
+
+import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
+import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.regex.Pattern;
+
+/**
+ * Adapter implementation of a {@link MessageReceiver}. A new receiver can then be registered by
+ * adding it to {@link Features}.
+ *
+ * {@link #onMessageReceived(GuildMessageReceivedEvent)} and
+ * {@link #onMessageUpdated(GuildMessageUpdateEvent)} can be overridden if desired. The default
+ * implementation is empty, the adapter will not react to such events.
+ */
+public abstract class MessageReceiverAdapter implements MessageReceiver {
+
+ private final Pattern channelNamePattern;
+
+ /**
+ * Creates an instance of a message receiver with the given pattern.
+ *
+ * @param channelNamePattern the pattern matching names of channels interested in, only messages
+ * from matching channels will be received
+ */
+ protected MessageReceiverAdapter(@NotNull Pattern channelNamePattern) {
+ this.channelNamePattern = channelNamePattern;
+ }
+
+ @Override
+ public final @NotNull Pattern getChannelNamePattern() {
+ return channelNamePattern;
+ }
+
+ @SuppressWarnings("NoopMethodInAbstractClass")
+ @Override
+ public void onMessageReceived(@NotNull GuildMessageReceivedEvent event) {
+ // Adapter does not react by default, subclasses may change this behavior
+ }
+
+ @SuppressWarnings("NoopMethodInAbstractClass")
+ @Override
+ public void onMessageUpdated(@NotNull GuildMessageUpdateEvent event) {
+ // Adapter does not react by default, subclasses may change this behavior
+ }
+}
diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java
index c8644c1147..a5a01c20e8 100644
--- a/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/SlashCommand.java
@@ -1,7 +1,6 @@
package org.togetherjava.tjbot.commands;
import net.dv8tion.jda.api.entities.Emoji;
-import net.dv8tion.jda.api.events.ReadyEvent;
import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent;
import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
@@ -21,7 +20,7 @@
*
* All slash commands have to implement this interface. For convenience, there is a
* {@link SlashCommandAdapter} available that implemented most methods already. A new command can
- * then be registered by adding it to {@link Commands}.
+ * then be registered by adding it to {@link Features}.
*
*
* Slash commands can either be visible globally in Discord or just to specific guilds. They can
@@ -36,7 +35,7 @@
*
* Some example commands are available in {@link org.togetherjava.tjbot.commands.basic}.
*/
-public interface SlashCommand {
+public interface SlashCommand extends Feature {
/**
* Gets the name of the command.
@@ -84,8 +83,8 @@ public interface SlashCommand {
*
*
* This method may be called multiple times, implementations must not create new data each time
- * but instead configure it once beforehand. The command system will automatically call this
- * method to register the command to Discord.
+ * but instead configure it once beforehand. The core system will automatically call this method
+ * to register the command to Discord.
*
* @return the command data of this command
*/
@@ -93,29 +92,8 @@ public interface SlashCommand {
CommandData getData();
/**
- * Triggered by the command system after system startup is complete. This can be used for
- * initialisation actions that cannot occur during construction.
- *
- * This method may be called multi-threaded. There is no guarantee as to the order that commands
- * will get called and there is no guarantee which thread they will be called on or even that
- * they will be called by the same thread.
- *
- * There is also no guarantee that slashCommands will be registered on guilds before this is
- * called. Do not use this method to interact with slashCommands.
- *
- * Details are available in the given event and the event also enables implementations to
- * respond to it.
- *
- * This method will be called in a multi-threaded context and the event may not be hold valid
- * forever.
- *
- * @param event the event that triggered this
- */
- void onReady(@NotNull ReadyEvent event);
-
- /**
- * Triggered by the command system when a slash command corresponding to this implementation
- * (based on {@link #getData()}) has been triggered.
+ * Triggered by the core system when a slash command corresponding to this implementation (based
+ * on {@link #getData()}) has been triggered.
*
* This method may be called multi-threaded. In particular, there are no guarantees that it will
* be executed on the same thread repeatedly or on the same thread that other event methods have
@@ -127,7 +105,7 @@ public interface SlashCommand {
* Buttons or menus have to be created with a component ID (see
* {@link ComponentInteraction#getComponentId()},
* {@link net.dv8tion.jda.api.interactions.components.Button#of(ButtonStyle, String, Emoji)}) in
- * a very specific format, otherwise the command system will fail to identify the command that
+ * a very specific format, otherwise the core system will fail to identify the command that
* corresponded to the button or menu click event and is unable to route it back.
*
* The component ID has to be a UUID-string (see {@link java.util.UUID}), which is associated to
@@ -154,7 +132,7 @@ public interface SlashCommand {
void onSlashCommand(@NotNull SlashCommandEvent event);
/**
- * Triggered by the command system when a button corresponding to this implementation (based on
+ * Triggered by the core system when a button corresponding to this implementation (based on
* {@link #getData()}) has been clicked.
*
* This method may be called multi-threaded. In particular, there are no guarantees that it will
@@ -174,7 +152,7 @@ public interface SlashCommand {
void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List
* This method may be called multi-threaded. In particular, there are no guarantees that it will
@@ -194,10 +172,10 @@ public interface SlashCommand {
void onSelectionMenu(@NotNull SelectionMenuEvent event, @NotNull List
* Further, {@link #onButtonClick(ButtonClickEvent, List)} and
* {@link #onSelectionMenu(SelectionMenuEvent, List)} can be overridden if desired. The default
@@ -54,7 +53,7 @@
* }
*
*
- * and registration of an instance of that class in {@link Commands}.
+ * and registration of an instance of that class in {@link Features}.
*/
public abstract class SlashCommandAdapter implements SlashCommand {
private final String name;
@@ -106,12 +105,6 @@ public final void acceptComponentIdGenerator(@NotNull ComponentIdGenerator gener
componentIdGenerator = generator;
}
- @SuppressWarnings("NoopMethodInAbstractClass")
- @Override
- public void onReady(@NotNull ReadyEvent event) {
- // Adapter does not react by default, subclasses may change this behavior
- }
-
@SuppressWarnings("NoopMethodInAbstractClass")
@Override
public void onButtonClick(@NotNull ButtonClickEvent event, @NotNull List
- * Commands are registered in {@link org.togetherjava.tjbot.commands.Commands} and then picked up by
- * the {@link org.togetherjava.tjbot.commands.system.CommandSystem}.
+ * Commands are registered in {@link org.togetherjava.tjbot.commands.Features} and then picked up by
+ * the {@link org.togetherjava.tjbot.commands.system.BotCore}.
*
* Custom slash commands can be created by implementing
* {@link org.togetherjava.tjbot.commands.SlashCommand} or using the adapter
diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/system/CommandSystem.java b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java
similarity index 82%
rename from application/src/main/java/org/togetherjava/tjbot/commands/system/CommandSystem.java
rename to application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java
index 4be65e20ae..5b32efa476 100644
--- a/application/src/main/java/org/togetherjava/tjbot/commands/system/CommandSystem.java
+++ b/application/src/main/java/org/togetherjava/tjbot/commands/system/BotCore.java
@@ -2,12 +2,15 @@
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.AbstractChannel;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.events.ReadyEvent;
import net.dv8tion.jda.api.events.interaction.ButtonClickEvent;
import net.dv8tion.jda.api.events.interaction.SelectionMenuEvent;
import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
+import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
+import net.dv8tion.jda.api.events.message.guild.GuildMessageUpdateEvent;
import net.dv8tion.jda.api.exceptions.ErrorHandler;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.commands.Command;
@@ -16,8 +19,7 @@
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.togetherjava.tjbot.commands.Commands;
-import org.togetherjava.tjbot.commands.SlashCommand;
+import org.togetherjava.tjbot.commands.*;
import org.togetherjava.tjbot.commands.componentids.ComponentId;
import org.togetherjava.tjbot.commands.componentids.ComponentIdParser;
import org.togetherjava.tjbot.commands.componentids.ComponentIdStore;
@@ -29,40 +31,60 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
- * The command system is the core of command handling in this application.
+ * The bot core is the core of command handling in this application.
*
* It knows and manages all commands, registers them towards Discord and is the entry point of all
* events. It forwards events to their corresponding commands and does the heavy lifting on all sort
* of event parsing.
*
*
- * Commands are made available via {@link Commands}, then the system has to be added to JDA as an
+ * Commands are made available via {@link Features}, then the system has to be added to JDA as an
* event listener, using {@link net.dv8tion.jda.api.JDA#addEventListener(Object...)}. Afterwards,
* the system is ready and will correctly forward events to all commands.
*/
-public final class CommandSystem extends ListenerAdapter implements SlashCommandProvider {
- private static final Logger logger = LoggerFactory.getLogger(CommandSystem.class);
+public final class BotCore extends ListenerAdapter implements SlashCommandProvider {
+ private static final Logger logger = LoggerFactory.getLogger(BotCore.class);
private static final String RELOAD_COMMAND = "reload";
private static final ExecutorService COMMAND_SERVICE = Executors.newCachedThreadPool();
private final Map
- * Commands are fetched from {@link Commands}.
+ * Commands are fetched from {@link Features}.
*
* @param jda the JDA instance that this command system will be used with
* @param database the database that commands may use to persist data
*/
@SuppressWarnings("ThisEscapedInObjectConstruction")
- public CommandSystem(@NotNull JDA jda, @NotNull Database database) {
- nameToSlashCommands = Commands.createSlashCommands(jda, database)
- .stream()
+ public BotCore(@NotNull JDA jda, @NotNull Database database) {
+ Collection