Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ public static void runBot(Config config) {
.build();

jda.awaitReady();

BotCore core = new BotCore(jda, database, config);
CommandReloading.reloadCommands(jda, core);
core.scheduleRoutines(jda);

jda.addEventListener(core);

logger.info("Bot is ready");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,24 @@
* <p>
* This is used for separate interactors with the same name, by command type (and possibly more in
* the future). Our system doesn't allow multiple interactors with the same name, while having a
* slash-command and a message-context-command with the same name can be really useful.
* slash-command, and a message-context-command with the same name can be useful.
*/
public enum UserInteractorPrefix {
/*
* Implementations that are none of the following have no dedicated prefix
*/
// Implementations that are none of the following have no dedicated prefix.

/**
* Prefix for slash commands.
*/
SLASH_COMMAND(SlashCommand.class, "s-"),
/**
* Prefix for message context commands.
*/
MESSAGE_CONTEXT_COMMAND(MessageContextCommand.class, "mc-"),
/**
* Prefix for user context commands.
*/
USER_CONTEXT_COMMAND(UserContextCommand.class, "uc-");


private final Class<? extends UserInteractor> classType;
private final String prefix;

Expand All @@ -36,6 +42,16 @@ public String getPrefix() {
return prefix;
}

/**
* Returns the name, attached with the prefix in front of it.
*
* @param name the name
* @return the name, with the prefix in front of it.
*/
public String getPrefixedName(String name) {
return prefix + name;
}

/**
* The class type that should receive the prefix
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Channel;
import net.dv8tion.jda.api.events.interaction.command.MessageContextInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.UserContextInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.SelectMenuInteractionEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.components.ComponentInteraction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.togetherjava.tjbot.CommandReloading;
import org.togetherjava.tjbot.commands.*;
import org.togetherjava.tjbot.commands.componentids.ComponentId;
import org.togetherjava.tjbot.commands.componentids.ComponentIdParser;
Expand All @@ -38,7 +40,6 @@
* events. It forwards events to their corresponding commands and does the heavy lifting on all sort
* of event parsing.
* <p>
* <p>
* 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.
Expand Down Expand Up @@ -112,25 +113,16 @@ public BotCore(JDA jda, Database database, Config config) {
if (logger.isInfoEnabled()) {
logger.info("Available user interactors: {}", interactors);
}


CommandReloading.reloadCommands(jda, this);
scheduleRoutines(jda);
}

/**
* Returns a predicate which validates the given interactor
* Returns a predicate, which validates the given interactor
*
* @return A predicate which validates the given interactor
* @return A predicate, which validates the given interactor
*/
private static Predicate<UserInteractor> validateInteractorPredicate() {
return interactor -> {
String name = interactor.getName();

if (name == null) {
logger.error("An interactor's name shouldn't be null! {}", interactor);
return false;
}
String name = Objects.requireNonNull(interactor.getName());

for (UserInteractorPrefix value : UserInteractorPrefix.values()) {
String prefix = value.getPrefix();
Expand All @@ -142,11 +134,6 @@ private static Predicate<UserInteractor> validateInteractorPredicate() {
}
}

if (name.startsWith("s-") || name.startsWith("mc-") || name.startsWith("uc-")) {
throw new IllegalArgumentException(
"The interactor's name cannot start with any of the prefixes.");
}

return true;
};
}
Expand Down Expand Up @@ -245,9 +232,13 @@ private Stream<MessageReceiver> getMessageReceiversSubscribedTo(Channel channel)

@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
logger.debug("Received slash command '{}' (#{}) on guild '{}'", event.getName(),
event.getId(), event.getGuild());
COMMAND_SERVICE.execute(() -> requireSlashCommand(event.getName()).onSlashCommand(event));
String name = event.getName();

logger.debug("Received slash command '{}' (#{}) on guild '{}'", name, event.getId(),
event.getGuild());
COMMAND_SERVICE.execute(() -> requireUserInteractor(
UserInteractorPrefix.SLASH_COMMAND.getPrefixedName(name), SlashCommand.class)
.onSlashCommand(event));
}

@Override
Expand All @@ -266,6 +257,29 @@ public void onSelectMenuInteraction(SelectMenuInteractionEvent event) {
.execute(() -> forwardComponentCommand(event, UserInteractor::onSelectionMenu));
}

@Override
public void onMessageContextInteraction(@NotNull final MessageContextInteractionEvent event) {
String name = event.getName();

logger.debug("Received message context command '{}' (#{}) on guild '{}'", name,
event.getId(), event.getGuild());
COMMAND_SERVICE.execute(() -> requireUserInteractor(
UserInteractorPrefix.MESSAGE_CONTEXT_COMMAND.getPrefixedName(name),
MessageContextCommand.class).onMessageContext(event));
}

@Override
public void onUserContextInteraction(@NotNull final UserContextInteractionEvent event) {
String name = event.getName();

logger.debug("Received user context command '{}' (#{}) on guild '{}'", name, event.getId(),
event.getGuild());
COMMAND_SERVICE.execute(() -> requireUserInteractor(
UserInteractorPrefix.USER_CONTEXT_COMMAND.getPrefixedName(name),
UserContextCommand.class).onUserContext(event));
}


/**
* Forwards the given component event to the associated user interactor.
* <p>
Expand Down Expand Up @@ -306,33 +320,34 @@ private <T extends ComponentInteraction> void forwardComponentCommand(T event,
}
ComponentId componentId = componentIdOpt.orElseThrow();

UserInteractor interactor = requireUserInteractor(componentId.userInteractorName());
UserInteractor interactor =
requireUserInteractor(componentId.userInteractorName(), UserInteractor.class);
logger.trace("Routing a component event with id '{}' back to user interactor '{}'",
event.getComponentId(), interactor.getName());
interactorArgumentConsumer.accept(interactor, event, componentId.elements());
}

/**
* Gets the given slash command by its name and requires that it exists.
*
* @param name the name of the command to get
* @return the command with the given name
* @throws NullPointerException if the command with the given name was not registered
*/
private SlashCommand requireSlashCommand(String name) {
return getInteractor(name, SlashCommand.class).orElseThrow(
() -> new NullPointerException("There is no slash command with name " + name));
}

/**
* Gets the given user interactor by its name and requires that it exists.
* Gets the given user interactor by its full name, requires it exists and is of the given type.
*
* @param name the name of the user interactor to get
* @param fullName the full name of the interactor, including the prefix
* @param typeToken a token of the type to expect
* @return the user interactor with the given name
* @throws NoSuchElementException if the user interactor with the given name was not registered
* @param <T> the type to expect the user interactor to be of
*/
private UserInteractor requireUserInteractor(final String name) {
return getInteractor(name).orElseThrow();
private <T extends UserInteractor> T requireUserInteractor(String fullName,
Class<T> typeToken) {
UserInteractor userInteractor = getInteractor(fullName).orElseThrow(
() -> new IllegalArgumentException("There is no interactor with name " + fullName));

if (!typeToken.isInstance(userInteractor)) {
throw new IllegalArgumentException(
"The interactor %s is not of the expected type %s, but instead %s".formatted(
fullName, typeToken.getSimpleName(),
fullName.getClass().getSimpleName()));
}

return typeToken.cast(userInteractor);
}


Expand Down