Complete message command parser and listener
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
The message command listener is now completed and the bot now also supports message-based commands with multiple aliases.
This commit is contained in:
parent
501b1bc71c
commit
a9790b3525
@ -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.
|
||||
*
|
||||
|
@ -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());
|
||||
|
@ -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<String> 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<MessageCommand> 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();
|
||||
|
||||
}
|
||||
}
|
@ -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<String> 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();
|
||||
}
|
||||
|
||||
}
|
@ -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<LinkedList<String>, MessageCommand> registeredCommands =
|
||||
new TreeMap<LinkedList<String>, 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<String> aliases : registeredCommands.keySet())
|
||||
{
|
||||
for(String currentAlias : aliases)
|
||||
{
|
||||
if(label.equals(currentAlias))
|
||||
{ return registeredCommands.get(aliases); }
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public LinkedList<MessageCommand> 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
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,8 @@ import java.util.TreeMap;
|
||||
public class SlashCommandListener extends ListenerAdapter
|
||||
{
|
||||
|
||||
TreeMap<String, SlashCommand> registeredCommands = new TreeMap<>();
|
||||
// map storing command label and command object alphabetically.
|
||||
private final TreeMap<String, SlashCommand> 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<SlashCommand> getRegisteredCommands()
|
||||
{ return new LinkedList<>(registeredCommands.values()); }
|
||||
|
@ -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<String> 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);
|
||||
}
|
||||
|
@ -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<LinkedList<String>> {
|
||||
|
||||
@Override
|
||||
public int compare(LinkedList<String> linkedList, LinkedList<String> t1) {
|
||||
|
||||
if(linkedList.isEmpty()) return 0;
|
||||
if(t1.isEmpty()) return 0;
|
||||
|
||||
return linkedList.get(0).compareTo(t1.get(0));
|
||||
}
|
||||
}
|
@ -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<CommandData> allCommands = new ArrayList<>()
|
||||
{{
|
||||
|
Loading…
Reference in New Issue
Block a user