From 459d231eae3e7c779791199dc47d4450142be431 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Tue, 16 Feb 2021 11:07:26 +0100 Subject: [PATCH] core --- .gitignore | 5 + KilluaCore.iml | 31 +++++ README.md | 42 ++++++ pom.xml | 63 +++++++++ src/main/java/cofig/Config.java | 34 +++++ src/main/java/commands/info/HelpCommand.java | 28 ++++ .../java/commands/info/InviteCommand.java | 26 ++++ src/main/java/core/Main.java | 40 ++++++ src/main/java/core/command/Command.java | 87 ++++++++++++ .../java/core/command/CommandHandler.java | 131 ++++++++++++++++++ .../java/core/command/CommandListener.java | 14 ++ src/main/java/core/command/CommandParser.java | 28 ++++ src/main/java/core/command/MessageSender.java | 105 ++++++++++++++ .../java/core/reactions/ReactionAdapter.java | 48 +++++++ .../core/reactions/ReactionEventListener.java | 30 ++++ .../core/reactions/ReactionEventManager.java | 34 +++++ .../java/core/reactions/ReactionListener.java | 9 ++ .../java/core/sections/ChannelListener.java | 9 ++ .../sections/ChannelMessageEventManager.java | 59 ++++++++ .../core/sections/ChannelMessageListener.java | 14 ++ src/main/java/core/sections/Section.java | 56 ++++++++ src/main/java/core/util/MultiPageEmbed.java | 75 ++++++++++ src/main/java/listener/StartUpListener.java | 13 ++ trivia_questions.json | 121 ++++++++++++++++ 24 files changed, 1102 insertions(+) create mode 100644 .gitignore create mode 100644 KilluaCore.iml create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/cofig/Config.java create mode 100644 src/main/java/commands/info/HelpCommand.java create mode 100644 src/main/java/commands/info/InviteCommand.java create mode 100644 src/main/java/core/Main.java create mode 100644 src/main/java/core/command/Command.java create mode 100644 src/main/java/core/command/CommandHandler.java create mode 100644 src/main/java/core/command/CommandListener.java create mode 100644 src/main/java/core/command/CommandParser.java create mode 100644 src/main/java/core/command/MessageSender.java create mode 100644 src/main/java/core/reactions/ReactionAdapter.java create mode 100644 src/main/java/core/reactions/ReactionEventListener.java create mode 100644 src/main/java/core/reactions/ReactionEventManager.java create mode 100644 src/main/java/core/reactions/ReactionListener.java create mode 100644 src/main/java/core/sections/ChannelListener.java create mode 100644 src/main/java/core/sections/ChannelMessageEventManager.java create mode 100644 src/main/java/core/sections/ChannelMessageListener.java create mode 100644 src/main/java/core/sections/Section.java create mode 100644 src/main/java/core/util/MultiPageEmbed.java create mode 100644 src/main/java/listener/StartUpListener.java create mode 100644 trivia_questions.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a34764 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target +.idea +*.class +Secrets.java +src\main\java\com\github\nilstrieb\cofig/Secrets.java \ No newline at end of file diff --git a/KilluaCore.iml b/KilluaCore.iml new file mode 100644 index 0000000..7ca8479 --- /dev/null +++ b/KilluaCore.iml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d85e050 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# KilluaCore + +The core systems of the KilluaBot. Can be used to create any bot with JDA. + +## What is handled in KilluaCore? + +### Commands + +You don't have to care about the command handling at all. You can just create a command in the commands package and then +add it in the `Main` class in the `addCommands()` method by just instantiating it. + +To create a new command, just add a new class that extends `Command`. You are forced to override a method that is called +when the command is entered. You can do all handling there. Sending a method to the text channel the command invocation +came from can be done with `reply("text");` (the method just calls `event.getTextChannel().sendMessage("text").queue();` +if you're familiar with JDA). + +The event is stored as a field and can be accessed with `event`. + +### MultiPageEmbed + +You can create embeds that consist of multiple pages and can be navigated with reactions. Creating one is very easy: +First, you create an Array of `MessageEmbed`, one for each page. Then you call `reply(array);` with the array as a +parameter. A message will be sent containing the pages. You can also customize the reactions. + +### Sections + +If you need the user to give input for mulitple messages, you can use a `Section`. For this, just create a new class (I +recommend it being a subclass of the command class) that extends `Section`. You then get a method `called(String text)` +that gets called everytime the user replies to the section. A section can be closed with `dispose()`. + +### Help Command + +A fully workng help command is generated automatically by the `CommandHandler`. You can hide commands from it by setting +the boolean `hidden` in the superconstructor to true. + +## Examples + +For examples, visit the [KilluaBot](https://github.com/Nilstrieb/KilluaBot) repo on Github. + +## How to use this + +You can just download the code here and copy it into your own bot. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f311a4d --- /dev/null +++ b/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + groupId + artifactId + 1.0.0 + + + 14 + 14 + + + + + net.dv8tion + JDA + 4.2.0_168 + + + com.google.code.gson + gson + 2.8.6 + + + + + + jcenter + jcenter-bintray + https://jcenter.bintray.com + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 14 + + + + maven-assembly-plugin + + + + MAIN_CLASS + + + + jar-with-dependencies + + + + + + + \ No newline at end of file diff --git a/src/main/java/cofig/Config.java b/src/main/java/cofig/Config.java new file mode 100644 index 0000000..568307d --- /dev/null +++ b/src/main/java/cofig/Config.java @@ -0,0 +1,34 @@ +package cofig; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.User; + +import java.awt.*; + +public class Config { + + public static final String PREFIX = "!"; //TODO change prefix + public static final Color DEFAULT_COLOR = new Color(255, 255, 255); //TODO change color + public static final long THIS_ID = 801015254023798825L; + private static JDA jda; + + public static void setJda(JDA jda) { + Config.jda = jda; + } + + public static EmbedBuilder getDefaultEmbed() { + //TODO change how the default embed looks + //you can for example use the image of the bot as the thumbnail or do something completely different + User thisBot = jda.getUserById(THIS_ID); + if (thisBot == null) { + thisBot = jda.retrieveUserById(THIS_ID).complete(); + } + + EmbedBuilder builder = new EmbedBuilder(); + builder.setColor(Config.DEFAULT_COLOR). + setThumbnail(thisBot.getAvatarUrl()) + .setFooter("bot"); + return builder; + } +} diff --git a/src/main/java/commands/info/HelpCommand.java b/src/main/java/commands/info/HelpCommand.java new file mode 100644 index 0000000..231f847 --- /dev/null +++ b/src/main/java/commands/info/HelpCommand.java @@ -0,0 +1,28 @@ +package commands.info; + +import core.command.Command; +import core.command.CommandHandler; +import net.dv8tion.jda.api.entities.MessageEmbed; + +//todo the help command is generated automatically. If you want to change the look of it go to the CommandHandler +public class HelpCommand extends Command { + public HelpCommand() { + super("help", "Shows this message", "help invite", "(command name)"); + } + + @Override + public void called(String args) { + if(args.length() == 0) { + if (CommandHandler.commandAmount() > CommandHandler.MAX_HELP_PAGE_LENGTH) { + reply(CommandHandler.getHelpLists()); + } else { + reply(CommandHandler.getHelpList().build()); + } + } else { + MessageEmbed help = CommandHandler.getCommandHelp(args.split(" ")[0]); + if (help != null) { + reply(help); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/commands/info/InviteCommand.java b/src/main/java/commands/info/InviteCommand.java new file mode 100644 index 0000000..3bb93f4 --- /dev/null +++ b/src/main/java/commands/info/InviteCommand.java @@ -0,0 +1,26 @@ +package commands.info; + +import cofig.Config; +import core.command.Command; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.User; + +//TODO use invite command or change or delete it +public class InviteCommand extends Command { + private static final String INVITE_LINK = + "()"; + + public InviteCommand() { + super("invite", "Get the invite link for this bot"); + } + + @Override + public void called(String args) { + + EmbedBuilder builder = Config.getDefaultEmbed() + .setTitle("Invite Killua to your server!") + .addField("Invite link", "[Invite]" + INVITE_LINK, true) + .setFooter("This bot was made by myself"); + reply(builder.build()); + } +} diff --git a/src/main/java/core/Main.java b/src/main/java/core/Main.java new file mode 100644 index 0000000..3154fc0 --- /dev/null +++ b/src/main/java/core/Main.java @@ -0,0 +1,40 @@ +package core; + +import cofig.Config; +import commands.info.HelpCommand; +import commands.info.InviteCommand; +import core.command.CommandListener; +import core.reactions.ReactionEventListener; +import core.sections.ChannelMessageListener; +import listener.StartUpListener; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.entities.Activity; +import net.dv8tion.jda.api.utils.Compression; + +import javax.security.auth.login.LoginException; + +public class Main { + + public static void main(String[] args) throws LoginException { + JDABuilder builder = JDABuilder.createDefault("THE TOKEN, get it from somewhere safe"); + builder.setCompression(Compression.ZLIB); + builder.setActivity(Activity.watching("over Gon")); + + builder.addEventListeners( + new StartUpListener(), + new ChannelMessageListener(), + new CommandListener(), + new ReactionEventListener() + ); + + JDA jda = builder.build(); + setupCommands(); + Config.setJda(jda); + } + + private static void setupCommands() { + new HelpCommand(); + new InviteCommand(); + } +} diff --git a/src/main/java/core/command/Command.java b/src/main/java/core/command/Command.java new file mode 100644 index 0000000..7de5e21 --- /dev/null +++ b/src/main/java/core/command/Command.java @@ -0,0 +1,87 @@ +package core.command; + +import cofig.Config; + +/** + * The base command class. Every command should extend this class. + */ +public abstract class Command extends MessageSender{ + private final String name; + private final String description; + private final String exampleUsage; + private final String arguments; + private final String detailDescription; + private final CommandParser parser = new CommandParser(); + + + /** + * New command + * @param name command name + * @param description quick description + * @param exampleUsage example usage without the prefix + * @param arguments all arguments (() -> optional. <> -> required + * @param detailDescription a detailed description + */ + public Command(String name, String description, String exampleUsage, String arguments, String detailDescription) { + this.name = name; + this.description = description; + this.exampleUsage = exampleUsage; + this.arguments = arguments; + this.detailDescription = detailDescription; + CommandHandler.addCommand(name, this, false); + } + + /** + * New command + * @param name command name + * @param description quick description + * @param exampleUsage example usage without the prefix + * @param arguments all arguments (() -> optional. <> -> required + */ + public Command(String name, String description, String exampleUsage, String arguments){ + this(name, description, exampleUsage, arguments, ""); + } + + /** + * New command + * @param name command name + * @param description quick description + */ + public Command(String name, String description) { + this(name, description, name, "", ""); + } + + /** + * Hidden command + * @param name name + * @param hidden should always be true + */ + public Command(String name, boolean hidden) { + this.name = name; + this.description = ""; + this.exampleUsage = ""; + this.arguments = ""; + this.detailDescription = ""; + CommandHandler.addCommand(name, this, hidden); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getExampleUsage() { + return Config.PREFIX + exampleUsage; + } + + public String getArguments() { + return arguments; + } + + public String getDetailDescription() { + return detailDescription; + } +} diff --git a/src/main/java/core/command/CommandHandler.java b/src/main/java/core/command/CommandHandler.java new file mode 100644 index 0000000..d6ddcfd --- /dev/null +++ b/src/main/java/core/command/CommandHandler.java @@ -0,0 +1,131 @@ +package core.command; + +import cofig.Config; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +import java.util.HashMap; +import java.util.Map; + +/** + * The CommandHandler handles everything about the commands + */ +public class CommandHandler { + + public static final int MAX_HELP_PAGE_LENGTH = 10; + + private static final Map commands = new HashMap<>(); + private static final Map hiddenCommands = new HashMap<>(); + + private static final CommandParser parser = new CommandParser(); + + /** + * Add a new command to the handler. This is normally done by the{@code Command}. + * + * @param key The key (command name) + * @param cmd The command object + * @param hidden Whether the command should be shown on the help page + */ + public static void addCommand(String key, Command cmd, boolean hidden) { + if (hidden) { + hiddenCommands.put(key, cmd); + } else { + commands.put(key, cmd); + } + } + + public static void addCommand(String key, Command cmd) { + commands.put(key, cmd); + } + + /** + * This method is called by the{@code CommandListener} + * + * @param event The {@code MessageReceivedEvent} + */ + static void call(MessageReceivedEvent event) { + if (event.getMessage().getContentRaw().toLowerCase().startsWith(Config.PREFIX)) { + String[] split = parser.splitOffCommandName(event.getMessage().getContentRaw()); + String command = split[0]; + if (commands.containsKey(command)) { + commands.get(command).onMessageReceived(event, split[1]); + } else if (hiddenCommands.containsKey(command)) { + hiddenCommands.get(command).onMessageReceived(event, split[1]); + } + } + } + + public static int commandAmount() { + return commands.size(); + } + + /** + * Get the list of all commands, on one page. + * May lead to problems if there are too many commands. + * @return The page + */ + public static EmbedBuilder getHelpList() { + EmbedBuilder builder = Config.getDefaultEmbed(); + builder.setTitle("Killua help"); + for (Command s : commands.values()) { + builder.addField(s.getName(), s.getDescription(), false); + } + return builder; + } + + /** + * Get a list of all commands, spread over different pages + * @return The pages + */ + public static MessageEmbed[] getHelpLists() { + + int length = commands.size(); + int pages = length / MAX_HELP_PAGE_LENGTH + 1; + EmbedBuilder[] builders = new EmbedBuilder[pages]; + + + int i = 0, j = 0; + EmbedBuilder builder = null; + for (Command value : commands.values()) { + if (i % MAX_HELP_PAGE_LENGTH == 0) { + builder = Config.getDefaultEmbed(); + builder.setTitle("Killua help"); + builders[j] = builder; + j++; + } + builder.addField(value.getName(), value.getDescription(), false); + + i++; + } + + MessageEmbed[] messageEmbeds = new MessageEmbed[pages]; + for (i = 0; i < builders.length; i++) { + messageEmbeds[i] = builders[i].build(); + } + return messageEmbeds; + } + + /** + * Returns the help page for a single command + * @param command The command name + * @return The help page + */ + public static MessageEmbed getCommandHelp(String command) { + Command cmd = commands.get(command); + if (cmd != null) { + EmbedBuilder builder = Config.getDefaultEmbed() + .setTitle("Killua help: " + cmd.getName()) + .addField("Name", cmd.getName(), true) + .addField("Description:", cmd.getDetailDescription(), true) + .addField("Example usage", "`" + cmd.getExampleUsage() + "`", true); + + if (!cmd.getArguments().equals("")) { + builder.addField("Arguments", "`" + cmd.getArguments() + "`", true); + } + return builder.build(); + } else { + return null; + } + } +} diff --git a/src/main/java/core/command/CommandListener.java b/src/main/java/core/command/CommandListener.java new file mode 100644 index 0000000..d992d7b --- /dev/null +++ b/src/main/java/core/command/CommandListener.java @@ -0,0 +1,14 @@ +package core.command; + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; + +public class CommandListener extends ListenerAdapter { + @Override + public void onMessageReceived(@NotNull MessageReceivedEvent event) { + if (!event.getAuthor().isBot()) { + CommandHandler.call(event); + } + } +} diff --git a/src/main/java/core/command/CommandParser.java b/src/main/java/core/command/CommandParser.java new file mode 100644 index 0000000..77d3a5f --- /dev/null +++ b/src/main/java/core/command/CommandParser.java @@ -0,0 +1,28 @@ +package core.command; + +import cofig.Config; + +/** + * A parser for parsing commands + */ +public class CommandParser { + + public static String[] splitArgs(String args){ + return args.split(" "); + } + + /** + * Split the full message into command name + args + * @param contentRaw The full message (including prefix!) + * @return a String array where [0] is the command name and [1] are the args (the text after the name + an optional space) + */ + public String[] splitOffCommandName(String contentRaw) { + String[] returns = new String[2]; + String beheaded = contentRaw.substring(Config.PREFIX.length()); + String[] split = beheaded.split(" "); + returns[0] = split[0]; + String commandRemoved = beheaded.replaceAll(split[0] + " ?(.*)", "$1"); + returns[1] = commandRemoved; + return returns; + } +} diff --git a/src/main/java/core/command/MessageSender.java b/src/main/java/core/command/MessageSender.java new file mode 100644 index 0000000..e8c3c6e --- /dev/null +++ b/src/main/java/core/command/MessageSender.java @@ -0,0 +1,105 @@ +package core.command; + +import core.util.MultiPageEmbed; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +import java.util.concurrent.TimeUnit; + +/** + * An abstract class for classes that interact with the chat. + * Removes all the boilerplate code associated with sending messages over JDA + */ +public abstract class MessageSender { + + protected MessageReceivedEvent event; + + /** + * Called by the specific handler. + * + * @param event The MessageReceivedEvent + * @param args The arguments + */ + public void onMessageReceived(MessageReceivedEvent event, String args) { + this.event = event; + called(args); + } + + + /** + * This method gets called by this object after the MessageReceivedEvent has been saved. + * + * @param args The arguments/text of the sent message + */ + public abstract void called(String args); + + /** + * Send a simple String message to the chat the original message came from. + * Equivalent to {@code event.getTextChannel().sendMessage(message).queue();} + * Will not send empty Strings. + * + * @param message The message + */ + protected void reply(String message) { + if (!message.isEmpty()) { + event.getTextChannel().sendMessage(message).queue(); + } + } + + /** + * Send a simple embed message to the chat original message came from. + * Equivalent to {@code event.getTextChannel().sendMessage(embed).queue();} + * Will not send empty Embeds + * + * @param embed The embed + */ + protected void reply(MessageEmbed embed) { + if (!embed.isEmpty()) { + event.getTextChannel().sendMessage(embed).queue(); + } + } + + /** + * Send multiple embeds in one message message as a {@code MultiPageEmbed} to the chat original message came from. + * Equivalent to {@code new MultiPageEmbed(event, embeds);} + * Will not send empty Embeds + * + * @param embeds The embeds + */ + protected void reply(MessageEmbed... embeds) { + if (!embeds[0].isEmpty()) { + new MultiPageEmbed(event, embeds); + } + } + + /** + * Send multiple embeds in one message message as a {@code MultiPageEmbed} to the chat original message came from. + * Equivalent to {@code new MultiPageEmbed(event, embeds);} + * Will not send empty Embeds + * + * @param embeds The embeds + * @param emote1 The emote for scrolling to the left + * @param emote2 The emote for scrolling to the right + */ + protected void reply(String emote1, String emote2, MessageEmbed... embeds) { + if (!embeds[0].isEmpty()) { + new MultiPageEmbed(event, emote1, emote2, embeds); + } + } + + /** + * Delete the original message after a specific delay in s. + * + * @param delay The delay in s + */ + protected void deleteMsg(long delay) { + event.getMessage().delete().queueAfter(delay, TimeUnit.SECONDS); + } + + /** + * Delete the original message after a specific delay in ms. + */ + protected void deleteMsg() { + event.getMessage().delete().queue(); + } +} diff --git a/src/main/java/core/reactions/ReactionAdapter.java b/src/main/java/core/reactions/ReactionAdapter.java new file mode 100644 index 0000000..54cce79 --- /dev/null +++ b/src/main/java/core/reactions/ReactionAdapter.java @@ -0,0 +1,48 @@ +package core.reactions; + +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent; + +/** + * The adapter for the ReactionListener + */ +public abstract class ReactionAdapter implements ReactionListener { + + private long message; + + /** + * This method has to be called with the message ID + * @param message The message ID + */ + protected void create(long message) { + this.message = message; + ReactionEventManager.addMessage(message, this); + } + + /** + * This method can be called to remove the Listener class from the Handlerr + */ + protected void dispose() { + ReactionEventManager.removeMessage(message); + } + + /** + * This method gets called from the Handler after a reaction is added to this message + * @param event The event + */ + @Override + public void onReactionAdded(MessageReactionAddEvent event) { + } + + /** + * This method gets called from the Handler after a reaction is removed to this message + * @param event The event + */ + @Override + public void onReactionRemoved(MessageReactionRemoveEvent event) { + } + + public long getMessage() { + return message; + } +} diff --git a/src/main/java/core/reactions/ReactionEventListener.java b/src/main/java/core/reactions/ReactionEventListener.java new file mode 100644 index 0000000..be12718 --- /dev/null +++ b/src/main/java/core/reactions/ReactionEventListener.java @@ -0,0 +1,30 @@ +package core.reactions; + +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; + +public class ReactionEventListener extends ListenerAdapter { + @Override + public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) { + if(event.getUser() == null){ + System.err.println("[ReactionEventListener] ADD Reaction User is null. Message: " + event.getMessageId() + " emote:" + event.getReactionEmote()); + } else { + if (!event.getUser().isBot()) { + ReactionEventManager.onReactionAdd(event); + } + } + } + + @Override + public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) { + if(event.getUser() == null){ + System.err.println("[ReactionEventListener] REMOVE Reaction User is null. Message: " + event.getMessageId() + " emote:" + event.getReactionEmote()); + } else { + if (!event.getUser().isBot()) { + ReactionEventManager.onReactionRemove(event); + } + } + } +} diff --git a/src/main/java/core/reactions/ReactionEventManager.java b/src/main/java/core/reactions/ReactionEventManager.java new file mode 100644 index 0000000..f5ef3f7 --- /dev/null +++ b/src/main/java/core/reactions/ReactionEventManager.java @@ -0,0 +1,34 @@ +package core.reactions; + +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent; + +import java.util.HashMap; + +public class ReactionEventManager { + private static final HashMap currentReactions = new HashMap<>(); + + public static void addMessage(long message, ReactionListener listener){ + currentReactions.put(message, listener); + } + + public static void removeMessage(Long message){ + currentReactions.remove(message); + } + + public static void onReactionAdd(MessageReactionAddEvent event){ + long message = event.getMessageIdLong(); + ReactionListener listener = currentReactions.get(message); + if (listener != null) { + listener.onReactionAdded(event); + } + } + + public static void onReactionRemove(MessageReactionRemoveEvent event){ + long message = event.getMessageIdLong(); + ReactionListener listener = currentReactions.get(message); + if (listener != null) { + listener.onReactionRemoved(event); + } + } +} \ No newline at end of file diff --git a/src/main/java/core/reactions/ReactionListener.java b/src/main/java/core/reactions/ReactionListener.java new file mode 100644 index 0000000..73f2a52 --- /dev/null +++ b/src/main/java/core/reactions/ReactionListener.java @@ -0,0 +1,9 @@ +package core.reactions; + +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; +import net.dv8tion.jda.api.events.message.react.MessageReactionRemoveEvent; + +public interface ReactionListener { + void onReactionAdded(MessageReactionAddEvent event); + void onReactionRemoved(MessageReactionRemoveEvent event); +} diff --git a/src/main/java/core/sections/ChannelListener.java b/src/main/java/core/sections/ChannelListener.java new file mode 100644 index 0000000..44d3a1f --- /dev/null +++ b/src/main/java/core/sections/ChannelListener.java @@ -0,0 +1,9 @@ +package core.sections; + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +public interface ChannelListener { + void messageReceived(MessageReceivedEvent event); + long getUserID(); + long getChannelID(); +} diff --git a/src/main/java/core/sections/ChannelMessageEventManager.java b/src/main/java/core/sections/ChannelMessageEventManager.java new file mode 100644 index 0000000..71bae43 --- /dev/null +++ b/src/main/java/core/sections/ChannelMessageEventManager.java @@ -0,0 +1,59 @@ +package core.sections; + + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +import java.util.*; + +public class ChannelMessageEventManager { + private static final HashMap> listeners = new HashMap<>(); + private static final List removeBuffer = new ArrayList<>(); + private static final Set removedChannels = new HashSet<>(); + + public static void addListener(ChannelListener listener, long channel) { + if (!listeners.containsKey(channel)) { + listeners.put(channel, new ArrayList<>()); + } + listeners.get(channel).add(listener); + } + + public static void removeListener(ChannelListener listener) { + if (listeners.containsKey(listener.getChannelID())) { + removeBuffer.add(listener); + } + } + + public static void onMessageReceived(MessageReceivedEvent event) { + long id = event.getTextChannel().getIdLong(); + //if the message is relevant + if (listeners.containsKey(id)) { + //notify all listeners + List list = listeners.get(id); + for (ChannelListener channelListener : list) { + if (channelListener.getUserID() == 0) { + channelListener.messageReceived(event); + } else if (channelListener.getUserID() == event.getAuthor().getIdLong()) { + channelListener.messageReceived(event); + } + } + + //remove the listeners that got removed during the calling + for (ChannelListener channelListener : removeBuffer) { + listeners.get(channelListener.getChannelID()).remove(channelListener); + removedChannels.add(channelListener.getChannelID()); + } + + //remove the channels if all listeners for that channel have been removed + for (Long removedChannel : removedChannels) { + list = listeners.get(removedChannel); + if (list.isEmpty()) { + listeners.remove(removedChannel); + } + } + + //clear the buffers + removedChannels.clear(); + removeBuffer.clear(); + } + } +} diff --git a/src/main/java/core/sections/ChannelMessageListener.java b/src/main/java/core/sections/ChannelMessageListener.java new file mode 100644 index 0000000..001002c --- /dev/null +++ b/src/main/java/core/sections/ChannelMessageListener.java @@ -0,0 +1,14 @@ +package core.sections; + +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; + +public class ChannelMessageListener extends ListenerAdapter { + @Override + public void onMessageReceived(@NotNull MessageReceivedEvent event) { + if (!event.getAuthor().isBot() && event.isFromGuild()) { + ChannelMessageEventManager.onMessageReceived(event); + } + } +} diff --git a/src/main/java/core/sections/Section.java b/src/main/java/core/sections/Section.java new file mode 100644 index 0000000..7ce2e71 --- /dev/null +++ b/src/main/java/core/sections/Section.java @@ -0,0 +1,56 @@ +package core.sections; + +import core.command.MessageSender; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +/** + * The section class can be extended to create sections where the user is asked to write multiple messages + */ +public abstract class Section extends MessageSender implements ChannelListener{ + private final long textChannelID; + private final long userID; + + /** + * Create a new section for a specific user + * @param textChannelID The channel ID + * @param userID The user ID + */ + public Section(long textChannelID, long userID) { + this.textChannelID = textChannelID; + this.userID = userID; + + ChannelMessageEventManager.addListener(this, textChannelID); + } + + /** + * Create a new section for all users in a channel + * @param textChannelID The channel ID + */ + public Section(long textChannelID) { + this.textChannelID = textChannelID; + this.userID = 0; + } + + @Override + public void messageReceived(MessageReceivedEvent event) { + this.event = event; + called(event.getMessage().getContentRaw()); + } + + /** + * End the section. + */ + protected void dispose(){ + ChannelMessageEventManager.removeListener(this); + } + + @Override + public long getUserID() { + return userID; + } + + @Override + public long getChannelID() { + return textChannelID; + } +} diff --git a/src/main/java/core/util/MultiPageEmbed.java b/src/main/java/core/util/MultiPageEmbed.java new file mode 100644 index 0000000..5d1b4f9 --- /dev/null +++ b/src/main/java/core/util/MultiPageEmbed.java @@ -0,0 +1,75 @@ +package core.util; + +import core.reactions.ReactionAdapter; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; + +/** + * This class sends a message to the channel in the event that contains multiple pages that can be navigated using reactinos + */ +public class MultiPageEmbed extends ReactionAdapter { + private static final String NEXT_PAGE_DEFAULT_REACTION = "\u25b6\ufe0f"; + private static final String PREVIOUS_PAGE_DEFAULT_REACTION = "\u25c0\ufe0f"; + + private Message message; + private final MessageEmbed[] pages; + private int currentState; + private final String prevReaction; + private final String nextReaction; + + + /** + * Create a new MultiPageEmbed with the default emotes + * + * @param event The event + * @param pages The pages + */ + public MultiPageEmbed(MessageReceivedEvent event, MessageEmbed... pages) { + this(event, PREVIOUS_PAGE_DEFAULT_REACTION, NEXT_PAGE_DEFAULT_REACTION, pages); + } + + /** + * Create a new MultiPageEmbed with custom emotes + * + * @param event The event + * @param pages The pages + */ + public MultiPageEmbed(MessageReceivedEvent event, String prevReaction, String nextReaction, MessageEmbed[] pages) { + this.prevReaction = prevReaction; + this.nextReaction = nextReaction; + this.pages = pages; + event.getTextChannel().sendMessage(pages[0]).queue(message1 -> { + message = message1; + message.addReaction(prevReaction).queue(); + message.addReaction(nextReaction).queue(); + create(message1.getIdLong()); + }); + } + + @Override + public void onReactionAdded(MessageReactionAddEvent event) { + String name = event.getReaction().getReactionEmote().getName(); + if (name.equals(nextReaction)) { + if (currentState + 1 < pages.length) { + currentState++; + if (!pages[currentState].isEmpty()) { + message.editMessage(pages[currentState]).queue(); + } + } + } else if (name.equals(prevReaction)) { + if (currentState > 0) { + currentState--; + if (!pages[currentState].isEmpty()) { + message.editMessage(pages[currentState]).queue(); + } + } + } + if (event.getUser() != null) { + event.getReaction().removeReaction(event.getUser()).queue(); + } else { + System.err.println("[MultiPageEmbed] Reaction user was null"); + } + } +} diff --git a/src/main/java/listener/StartUpListener.java b/src/main/java/listener/StartUpListener.java new file mode 100644 index 0000000..6c2cb2c --- /dev/null +++ b/src/main/java/listener/StartUpListener.java @@ -0,0 +1,13 @@ +package listener; + +import net.dv8tion.jda.api.events.ReadyEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.NotNull; + +public class StartUpListener extends ListenerAdapter { + + @Override + public void onReady(@NotNull ReadyEvent event) { + System.out.println("[Startup] Killua started"); + } +} diff --git a/trivia_questions.json b/trivia_questions.json new file mode 100644 index 0000000..52542b6 --- /dev/null +++ b/trivia_questions.json @@ -0,0 +1,121 @@ +[ + { + "question": "What\u0027s the name of Gons aunt?", + "answers": [ + "Kite", + "Mito", + "Ging", + "Kurapika" + ], + "correctAnswer": 1, + "arc": 0 + }, + { + "question": "Why does Leorio want money?", + "answers": [ + "To become a doctor", + "To send it to e-girls", + "So that others don\u0027t get it", + "To buy large quantities of drugs" + ], + "correctAnswer": 0, + "arc": 0 + }, + { + "question": "What was Gon\u0027s aim after Killua left him alone playing with the Chairman Netero?", + "answers": [ + "Get the ball", + "Make Netero use his right hand", + "Make Netero use his left leg", + "Let Netero drop the ball" + ], + "correctAnswer": 1, + "arc": 0 + }, + { + "question": "What did Tonpa mix into the drink?", + "answers": [ + "Sleeping pills", + "Headache tabletsschla", + "Laxative", + "Rat poison" + ], + "correctAnswer": 2, + "arc": 0 + }, + { + "question": "How many doors are there at the Testing Gate of the Zoldyck Family?", + "answers": [ + "7", + "5", + "12", + "8" + ], + "correctAnswer": 0, + "arc": 1 + }, + { + "question": "What is Bisky\u0027s true form?", + "answers": [ + "Boy", + "Big Woman", + "Little Girl" + ], + "correctAnswer": 1, + "arc": 4 + }, + { + "question": "How did Gon die on Greed Island?", + "answers": [ + "He didn\u0027t die on Greed Island", + "Hisoka killed him with a card to the neck", + "He lost too muich blood when Genthru blew his arm of", + "He killed himself after Killuas death" + ], + "correctAnswer": 0, + "arc": 4 + }, + { + "question": "Where did Meruem die?", + "answers": [ + "Volcano", + "In the desert with Netero", + "In the basement of the palace", + "In the womb of the queen" + ], + "correctAnswer": 2, + "arc": 5 + }, + { + "question": "Who is Alluka?", + "answers": [ + "Gon\u0027s sister", + "A Zodiac", + "The \u0027dark\u0027 side of Nanika", + "Killua\u0027s sister" + ], + "correctAnswer": 3, + "arc": 6 + }, + { + "question": "What is Genthrus ability called?", + "answers": [ + "Nuke", + "Massive Explosion", + "Little Flower" + ], + "correctAnswer": 2, + "arc": 4 + }, + { + "question": "Who is not a member of the Zoldyck familiy?", + "answers": [ + "Milluki", + "Killua", + "Ollaki", + "Illumi" + ], + "correctAnswer": 2, + "arc": 1 + } +] \ No newline at end of file