This commit is contained in:
Nilstrieb 2021-02-16 11:07:26 +01:00
commit 459d231eae
24 changed files with 1102 additions and 0 deletions

View file

@ -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;
}
}

View file

@ -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);
}
}
}
}

View file

@ -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 =
"(<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());
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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<String, Command> commands = new HashMap<>();
private static final Map<String, Command> 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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}
}

View file

@ -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<Long, ReactionListener> 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);
}
}
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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<Long, List<ChannelListener>> listeners = new HashMap<>();
private static final List<ChannelListener> removeBuffer = new ArrayList<>();
private static final Set<Long> 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<ChannelListener> 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();
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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");
}
}
}

View file

@ -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");
}
}