diff --git a/src/main/java/wtf/beatrice/hidekobot/Cache.java b/src/main/java/wtf/beatrice/hidekobot/Cache.java index 065414e..6caa6ef 100644 --- a/src/main/java/wtf/beatrice/hidekobot/Cache.java +++ b/src/main/java/wtf/beatrice/hidekobot/Cache.java @@ -3,6 +3,7 @@ package wtf.beatrice.hidekobot; import org.jetbrains.annotations.Nullable; import wtf.beatrice.hidekobot.datasource.ConfigurationSource; import wtf.beatrice.hidekobot.datasource.DatabaseSource; +import wtf.beatrice.hidekobot.listeners.MessageCommandListener; import wtf.beatrice.hidekobot.listeners.MessageLogger; import wtf.beatrice.hidekobot.listeners.SlashCommandListener; import wtf.beatrice.hidekobot.util.Logger; @@ -26,7 +27,7 @@ public class Cache // note: discord sets interactions' expiry time to 15 minutes by default, so we can't go higher than that. private final static long expiryTimeSeconds = 15L; - // used to count eg. uptime + // used to count e.g. uptime private static LocalDateTime startupTime; private final static String execPath = System.getProperty("user.dir"); @@ -35,6 +36,7 @@ public class Cache private static final String botName = "HidekoBot"; private static SlashCommandListener slashCommandListener = null; + private static MessageCommandListener messageCommandListener = null; private final static String defaultInviteLink = "https://discord.com/api/oauth2/authorize?client_id=%userid%&scope=bot+applications.commands&permissions=8"; @@ -201,14 +203,18 @@ public class Cache } + //todo javadocs public static void setSlashCommandListener(SlashCommandListener commandListener) - { - - slashCommandListener = commandListener; - } + { slashCommandListener = commandListener; } public static SlashCommandListener getSlashCommandListener() { return slashCommandListener; } + + public static void setMessageCommandListener(MessageCommandListener commandListener) + { messageCommandListener = commandListener; } + + public static MessageCommandListener getMessageCommandListener() { return messageCommandListener; } + /** * Set the bot's startup time. Generally only used at boot time. * diff --git a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java index 013aeef..58b964a 100644 --- a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java +++ b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java @@ -6,11 +6,13 @@ import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.requests.GatewayIntent; import sun.misc.Signal; +import wtf.beatrice.hidekobot.commands.message.CommandsCommand; +import wtf.beatrice.hidekobot.commands.message.HelloCommand; import wtf.beatrice.hidekobot.commands.slash.*; import wtf.beatrice.hidekobot.datasource.ConfigurationSource; import wtf.beatrice.hidekobot.datasource.DatabaseSource; import wtf.beatrice.hidekobot.listeners.ButtonInteractionListener; -import wtf.beatrice.hidekobot.listeners.MessageListener; +import wtf.beatrice.hidekobot.listeners.MessageCommandListener; import wtf.beatrice.hidekobot.listeners.SlashCommandCompleter; import wtf.beatrice.hidekobot.listeners.SlashCommandListener; import wtf.beatrice.hidekobot.runnables.ExpiredMessageTask; @@ -97,7 +99,7 @@ public class HidekoBot } - // register commands + // register slash commands SlashCommandListener slashCommandListener = new SlashCommandListener(); slashCommandListener.registerCommand(new AvatarCommand()); slashCommandListener.registerCommand(new BotInfoCommand()); @@ -110,8 +112,14 @@ public class HidekoBot slashCommandListener.registerCommand(new SayCommand()); Cache.setSlashCommandListener(slashCommandListener); + // register message commands + MessageCommandListener messageCommandListener = new MessageCommandListener(); + messageCommandListener.registerCommand(new HelloCommand()); + messageCommandListener.registerCommand(new CommandsCommand()); + Cache.setMessageCommandListener(messageCommandListener); + // register listeners - jda.addEventListener(new MessageListener()); + jda.addEventListener(messageCommandListener); jda.addEventListener(slashCommandListener); jda.addEventListener(new SlashCommandCompleter()); jda.addEventListener(new ButtonInteractionListener()); diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/CommandsCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/CommandsCommand.java new file mode 100644 index 0000000..c96b3c8 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/CommandsCommand.java @@ -0,0 +1,43 @@ +package wtf.beatrice.hidekobot.commands.message; + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import wtf.beatrice.hidekobot.Cache; +import wtf.beatrice.hidekobot.objects.MessageCommand; + +import java.util.Collections; +import java.util.LinkedList; + +public class CommandsCommand implements MessageCommand +{ + + @Override + public LinkedList getCommandLabels() { + return new LinkedList<>(Collections.singletonList("commands")); + } + + @Override + public boolean passRawArgs() { + return false; + } + + @Override + public void runCommand(MessageReceivedEvent event, String label, String[] args) { + + StringBuilder commandsList = new StringBuilder(); + commandsList.append("Recognized message commands: "); + + LinkedList messageCommands = Cache.getMessageCommandListener().getRegisteredCommands(); + for(int i = 0; i < messageCommands.size(); i++) + { + commandsList.append("`") + .append(messageCommands.get(i).getCommandLabels().get(0)) + .append("`"); + if(i+1 != messageCommands.size()) + { commandsList.append(", "); } + + } + + event.getMessage().reply(commandsList.toString()).queue(); + + } +} diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/HelloCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/HelloCommand.java new file mode 100644 index 0000000..0c0ea61 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/HelloCommand.java @@ -0,0 +1,29 @@ +package wtf.beatrice.hidekobot.commands.message; + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import wtf.beatrice.hidekobot.objects.MessageCommand; + +import java.util.Arrays; +import java.util.LinkedList; + +public class HelloCommand implements MessageCommand +{ + + @Override + public LinkedList getCommandLabels() { + return new LinkedList<>(Arrays.asList("hi", "hello", "heya")); + } + + @Override + public boolean passRawArgs() { + return false; + } + + @Override + public void runCommand(MessageReceivedEvent event, String label, String[] args) + { + String senderId = event.getMessage().getAuthor().getId(); + event.getMessage().reply("Hi, <@" + senderId + ">! :sparkles:").queue(); + } + +} diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/MessageCommandListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/MessageCommandListener.java new file mode 100644 index 0000000..576f483 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/MessageCommandListener.java @@ -0,0 +1,104 @@ +package wtf.beatrice.hidekobot.listeners; + +import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; +import wtf.beatrice.hidekobot.objects.MessageCommand; +import wtf.beatrice.hidekobot.objects.MessageCommandAliasesComparator; +import wtf.beatrice.hidekobot.util.Logger; + +import java.util.*; + +public class MessageCommandListener extends ListenerAdapter +{ + + // map storing command labels and command object alphabetically. + private final TreeMap, MessageCommand> registeredCommands = + new TreeMap, MessageCommand>(new MessageCommandAliasesComparator()); + + private final String commandRegex = "(?i)^(hideko|hde)\\b"; + // (?i) -> case insensitive flag + // ^ -> start of string (not in middle of a sentence) + // \b -> the word has to end here + // .* -> there can be anything else after this word + + + public void registerCommand(MessageCommand command) + { + registeredCommands.put(command.getCommandLabels(), command); + } + + public MessageCommand getRegisteredCommand(String label) + { + for(LinkedList aliases : registeredCommands.keySet()) + { + for(String currentAlias : aliases) + { + if(label.equals(currentAlias)) + { return registeredCommands.get(aliases); } + } + } + + return null; + } + + + public LinkedList getRegisteredCommands() + { return new LinkedList<>(registeredCommands.values()); } + + + private final Logger logger = new Logger(MessageCommandListener.class); + + @Override + public void onMessageReceived(@NotNull MessageReceivedEvent event) + { + String eventMessage = event.getMessage().getContentDisplay(); + + if(!eventMessage.toLowerCase().matches(commandRegex + ".*")) + return; + + MessageChannel channel = event.getChannel(); + // generate args from the string + String argsString = eventMessage.replaceAll(commandRegex + "\\s*", ""); + + + // if no args were specified apart from the bot prefix + // note: we can't check argsRaw's size because String.split returns an array of size 1 if no match is found, + // and that element is the whole string passed as a single argument, which would be empty in this case + // (or contain text in other cases like "string split ," if the passed text doesn't contain any comma -> + // it will be the whole text as a single element. + if(argsString.isEmpty()) + { + event.getMessage().reply("Hello there! ✨").queue(); + return; + } + + // split all passed arguments + String[] argsRaw = argsString.split("\\s+"); + + // extract the command that the user is trying to run + String commandLabel = argsRaw[0]; + MessageCommand commandObject = getRegisteredCommand(commandLabel); + + if(commandObject != null) + { + String[] commandArgs; + if(commandObject.passRawArgs()) + { + // remove first argument, which is the command label + argsString = argsString.replaceAll("^[\\S]+\\s+", ""); + // pass all other arguments as a single argument as the first array element + commandArgs = new String[]{argsString}; + } + else + { + // copy all split arguments to the array, except from the command label + commandArgs = Arrays.copyOfRange(argsRaw, 1, argsRaw.length); + } + commandObject.runCommand(event, commandLabel, commandArgs); + } else { + event.getMessage().reply("Unrecognized command: `" + commandLabel + "`!").queue(); // todo prettier + } + } +} diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/MessageListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/MessageListener.java deleted file mode 100644 index 38c041c..0000000 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/MessageListener.java +++ /dev/null @@ -1,64 +0,0 @@ -package wtf.beatrice.hidekobot.listeners; - -import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; -import net.dv8tion.jda.api.events.message.MessageReceivedEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; -import org.jetbrains.annotations.NotNull; -import wtf.beatrice.hidekobot.Cache; -import wtf.beatrice.hidekobot.util.Logger; - -public class MessageListener extends ListenerAdapter -{ - - private final String commandRegex = "(?i)^(hideko|hde)\\b"; - // (?i) -> case insensitive flag - // ^ -> start of string (not in middle of a sentence) - // \b -> the word has to end here - // .* -> there can be anything else after this word - - private final Logger logger = new Logger(MessageListener.class); - - @Override - public void onMessageReceived(@NotNull MessageReceivedEvent event) - { - String eventMessage = event.getMessage().getContentDisplay(); - - if(!eventMessage.toLowerCase().matches(commandRegex + ".*")) - return; - - MessageChannel channel = event.getChannel(); - // generate args from the string - String argsString = eventMessage.replaceAll(commandRegex + "\\s*", ""); - String[] args = argsString.split("\\s+"); - - event.getMessage().reply("Hi").queue(); - - - if(eventMessage.equalsIgnoreCase("hideko")) - { - channel.sendMessage("Hello there! ✨").queue(); - return; - } - - if(eventMessage.equalsIgnoreCase("ping")) - { - channel.sendMessage("Pong!").queue(); - return; - } - - if(eventMessage.equalsIgnoreCase("hideko verbose")) - { - boolean verbose = Cache.isVerbose(); - - String msg = verbose ? "off" : "on"; - msg = "Turning verbosity " + msg + "!"; - - Cache.setVerbose(!verbose); - - channel.sendMessage(msg).queue(); - logger.log(msg); - - return; - } - } -} diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java index 0d6920f..0941ac0 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java @@ -11,7 +11,8 @@ import java.util.TreeMap; public class SlashCommandListener extends ListenerAdapter { - TreeMap registeredCommands = new TreeMap<>(); + // map storing command label and command object alphabetically. + private final TreeMap registeredCommands = new TreeMap<>(); public void registerCommand(SlashCommand command) { @@ -20,9 +21,7 @@ public class SlashCommandListener extends ListenerAdapter } public SlashCommand getRegisteredCommand(String label) - { - return registeredCommands.get(label); - } + { return registeredCommands.get(label); } public LinkedList getRegisteredCommands() { return new LinkedList<>(registeredCommands.values()); } diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/MessageCommand.java b/src/main/java/wtf/beatrice/hidekobot/objects/MessageCommand.java index 9deb7d2..efddef3 100644 --- a/src/main/java/wtf/beatrice/hidekobot/objects/MessageCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/objects/MessageCommand.java @@ -2,24 +2,43 @@ package wtf.beatrice.hidekobot.objects; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import java.util.LinkedList; + public interface MessageCommand { /** - * Get the command's label, which is used when determining if this is the correct command or not. + * Get the command's label(s), which are used when determining if this is the correct command or not. + * The first label in the collection is considered the main command name. All other labels are considered + * command aliases. * * @return the command label. */ - String getCommandName(); + LinkedList getCommandLabels(); + + /** + * Say if this command does its own text parsing, and tell the message listener if it should automatically + * split all arguments in separate entries of an array, or pass everything as the first entry of that array. + * + * This is better instead of getting the message contents from the event, because the message listener will + * still strip the bot prefix and command name from the args, but leave the rest untouched. + * + * @return the boolean being true if no parsing should be made by the command handler. + */ + boolean passRawArgs(); /** * Run the command logic by parsing the event and replying accordingly. * + * * @param event the received message event. It should not be used for parsing message contents data as * the arguments already account for it in a better way. + * + * @param label the command label that was used, taken from all available command aliases. + * * @param args a pre-formatted list of arguments, excluding the bot prefix and the command name. * This is useful because command logic won't have to change in case the bot prefix is changed, * removed, or we switch to another method of triggering commands (ping, trigger words, ...). */ - void runCommand(MessageReceivedEvent event, String[] args); + void runCommand(MessageReceivedEvent event, String label, String[] args); } diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/MessageCommandAliasesComparator.java b/src/main/java/wtf/beatrice/hidekobot/objects/MessageCommandAliasesComparator.java new file mode 100644 index 0000000..4624ec7 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/objects/MessageCommandAliasesComparator.java @@ -0,0 +1,19 @@ +package wtf.beatrice.hidekobot.objects; + +import java.util.Comparator; +import java.util.LinkedList; + +/** + * This class gets two linked lists, and compares their first value alphabetically. + */ +public class MessageCommandAliasesComparator implements Comparator> { + + @Override + public int compare(LinkedList linkedList, LinkedList t1) { + + if(linkedList.isEmpty()) return 0; + if(t1.isEmpty()) return 0; + + return linkedList.get(0).compareTo(t1.get(0)); + } +} diff --git a/src/main/java/wtf/beatrice/hidekobot/util/SlashCommandUtil.java b/src/main/java/wtf/beatrice/hidekobot/util/SlashCommandUtil.java index 2efcb27..4f64e06 100644 --- a/src/main/java/wtf/beatrice/hidekobot/util/SlashCommandUtil.java +++ b/src/main/java/wtf/beatrice/hidekobot/util/SlashCommandUtil.java @@ -8,7 +8,7 @@ import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.Commands; import wtf.beatrice.hidekobot.HidekoBot; -import wtf.beatrice.hidekobot.listeners.MessageListener; +import wtf.beatrice.hidekobot.listeners.MessageCommandListener; import java.util.ArrayList; import java.util.List; @@ -16,7 +16,7 @@ import java.util.List; public class SlashCommandUtil { - private static final Logger logger = new Logger(MessageListener.class); + private static final Logger logger = new Logger(MessageCommandListener.class); static List allCommands = new ArrayList<>() {{