From 5d4df2c9c987636dd0d153b8bbfbb159b04ad5ca Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Fri, 2 Apr 2021 16:50:07 +0200 Subject: [PATCH] inital commit --- .gitignore | 4 + dbschema | 7 + pom.xml | 49 +++++++ .../recommendationbot/cofig/Config.java | 31 ++++ .../commands/info/HelpCommand.java | 32 ++++ .../commands/info/InfoCommand.java | 29 ++++ .../commands/info/InviteCommand.java | 22 +++ .../commands/info/RebootCommand.java | 22 +++ .../commands/reco/RecommendCommand.java | 23 +++ .../commands/reco/SubCommand.java | 40 +++++ .../recommendationbot/core/Main.java | 63 ++++++++ .../core/command/Command.java | 80 ++++++++++ .../core/command/CommandHandler.java | 138 ++++++++++++++++++ .../core/command/CommandListener.java | 14 ++ .../core/command/CommandParser.java | 29 ++++ .../core/command/MessageSender.java | 128 ++++++++++++++++ .../core/reactions/ReactionAdapter.java | 48 ++++++ .../core/reactions/ReactionEventListener.java | 22 +++ .../core/reactions/ReactionEventManager.java | 34 +++++ .../core/reactions/ReactionListener.java | 9 ++ .../core/sections/ChannelListener.java | 9 ++ .../sections/ChannelMessageEventManager.java | 58 ++++++++ .../core/sections/ChannelMessageListener.java | 14 ++ .../core/sections/Section.java | 74 ++++++++++ .../core/util/MultiPageEmbed.java | 91 ++++++++++++ .../recommendationbot/db/Neo4jConnection.java | 75 ++++++++++ .../listeners/StartUpListener.java | 12 ++ 27 files changed, 1157 insertions(+) create mode 100644 .gitignore create mode 100644 dbschema create mode 100644 pom.xml create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/cofig/Config.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/commands/info/HelpCommand.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InfoCommand.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InviteCommand.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/commands/info/RebootCommand.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/RecommendCommand.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/SubCommand.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/Main.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/command/Command.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandHandler.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandListener.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandParser.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/command/MessageSender.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionAdapter.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventListener.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventManager.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionListener.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelListener.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageEventManager.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageListener.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/sections/Section.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/core/util/MultiPageEmbed.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/db/Neo4jConnection.java create mode 100644 src/main/java/com/github/nilstrieb/recommendationbot/listeners/StartUpListener.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e0699a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +Secrets.java +.idea +*.iml \ No newline at end of file diff --git a/dbschema b/dbschema new file mode 100644 index 0000000..f644232 --- /dev/null +++ b/dbschema @@ -0,0 +1,7 @@ +(:User {id:number}) +(:Thing {type:string, name:string, tags:[string]}) + +type: s(show), g(game), m(movie), o(other) + +(:User) - [:SUBS_TO] -> (:User) +(:User) - [:LIKES] -> (:Thing) \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..36d74f4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.example + relationship-bot + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + + + + + + 16 + 16 + + + + + net.dv8tion + JDA + LATEST + + + org.neo4j.driver + neo4j-java-driver + 4.2.0 + + + + + + jcenter + jcenter-bintray + https://jcenter.bintray.com + + + + \ No newline at end of file diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/cofig/Config.java b/src/main/java/com/github/nilstrieb/recommendationbot/cofig/Config.java new file mode 100644 index 0000000..9aec626 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/cofig/Config.java @@ -0,0 +1,31 @@ +package com.github.nilstrieb.recommendationbot.cofig; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; + +import java.awt.*; + +public class Config { + + public static final String PREFIX = "r-"; + public static final Color DEFAULT_COLOR = new Color(40, 38, 38); + public static final String VERSION = "0.1.0"; + public static long thisId = 0; + private static JDA jda; + + private static final String GUILD_COUNT_FILE_NAME = "guild_count.txt"; + + private static int guildCount; + + public static void setJda(JDA jda) { + Config.jda = jda; + } + + public static EmbedBuilder getDefaultEmbed() { + + EmbedBuilder builder = new EmbedBuilder(); + builder.setColor(Config.DEFAULT_COLOR). + setThumbnail(jda.getSelfUser().getAvatarUrl()); + return builder; + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/HelpCommand.java b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/HelpCommand.java new file mode 100644 index 0000000..7171ba4 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/HelpCommand.java @@ -0,0 +1,32 @@ +package com.github.nilstrieb.recommendationbot.commands.info; + +import com.github.nilstrieb.recommendationbot.core.command.Command; +import com.github.nilstrieb.recommendationbot.core.command.CommandHandler; +import net.dv8tion.jda.api.entities.MessageEmbed; + + +public class HelpCommand extends Command { + public HelpCommand() { + super("help"); + setDescription("Shows this message"); + setExampleUsage("help invite"); + setArguments("(command name)"); + } + + @Override + public void called(String args) { + if (args.length() == 0) { + MessageEmbed[] helpList = CommandHandler.getHelpList(); + if (helpList.length == 1) { + reply(helpList[0]); + } else { + reply(helpList); + } + } else { + MessageEmbed help = CommandHandler.getCommandHelp(args); + if (help != null) { + reply(help); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InfoCommand.java b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InfoCommand.java new file mode 100644 index 0000000..cfd4207 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InfoCommand.java @@ -0,0 +1,29 @@ +package com.github.nilstrieb.recommendationbot.commands.info; + + +import com.github.nilstrieb.recommendationbot.cofig.Config; +import com.github.nilstrieb.recommendationbot.core.command.Command; +import net.dv8tion.jda.api.entities.MessageEmbed; + +import java.text.NumberFormat; +import java.util.Locale; + +public class InfoCommand extends Command { + + public InfoCommand() { + super("info"); + setDescription("Info about me!"); + } + + @Override + public void called(String args) { + NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH); + + + MessageEmbed embed = Config.getDefaultEmbed() + .setTitle("Info about me") + .setDescription("Get recommendations for things from you friends and people who like the same things!") + .setFooter("Version v" + Config.VERSION).build(); + reply(embed); + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InviteCommand.java b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InviteCommand.java new file mode 100644 index 0000000..9d0d592 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/InviteCommand.java @@ -0,0 +1,22 @@ +package com.github.nilstrieb.recommendationbot.commands.info; + +import com.github.nilstrieb.recommendationbot.cofig.Config; +import com.github.nilstrieb.recommendationbot.core.command.Command; +import net.dv8tion.jda.api.entities.MessageEmbed; + +public class InviteCommand extends Command { + + public InviteCommand() { + super("invite"); + setDescription("Get the invite link for this bot"); + } + + @Override + public void called(String args) { + + MessageEmbed builder = Config.getDefaultEmbed() + .setTitle("Invite me!") + .setDescription("[Click here](https://www.youtube.com/watch?v=dQw4w9WgXcQ)").build(); + reply(builder); + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/RebootCommand.java b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/RebootCommand.java new file mode 100644 index 0000000..919ed5e --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/commands/info/RebootCommand.java @@ -0,0 +1,22 @@ +package com.github.nilstrieb.recommendationbot.commands.info; + + +import com.github.nilstrieb.recommendationbot.core.command.Command; + +public class RebootCommand extends Command { + public RebootCommand() { + super("reboot"); + setHidden(); + } + + @Override + public void called(String args) { + long id = event.getMember().getUser().getIdLong(); + if (id == 414755070161453076L) { + event.getTextChannel().sendMessage("Rebooting!").queue(); + System.exit(409); + } else { + event.getTextChannel().sendMessage("Sorry, you don't seem to have sufficient Permissions for this!").queue(); + } + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/RecommendCommand.java b/src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/RecommendCommand.java new file mode 100644 index 0000000..f926aee --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/RecommendCommand.java @@ -0,0 +1,23 @@ +package com.github.nilstrieb.recommendationbot.commands.reco; + +import com.github.nilstrieb.recommendationbot.core.command.Command; +import com.github.nilstrieb.recommendationbot.db.Neo4jConnection; + +public class RecommendCommand extends Command { + + public RecommendCommand() { + super("recommend"); + setAlias("reco"); + setAlias("r"); + setDescription("Recommend something!"); + setArguments(""); + setExampleUsage("recommend hunter x hunter"); + } + + @Override + public void called(String args) { + String uid = event.getAuthor().getId(); + + Neo4jConnection.getInstance().createRecommendation(uid, args); + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/SubCommand.java b/src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/SubCommand.java new file mode 100644 index 0000000..5eb485d --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/commands/reco/SubCommand.java @@ -0,0 +1,40 @@ +package com.github.nilstrieb.recommendationbot.commands.reco; + +import com.github.nilstrieb.recommendationbot.core.command.Command; +import com.github.nilstrieb.recommendationbot.db.Neo4jConnection; +import net.dv8tion.jda.api.entities.Member; + +import java.util.List; + +public class SubCommand extends Command { + + + public SubCommand() { + super("subscribe"); + setAlias("sub"); + setAlias("s"); + + } + + @Override + public void called(String args) { + String uid = event.getAuthor().getId(); + + List mentions = event.getMessage().getMentionedMembers(); + + if (mentions.size() < 1) { + reply("Mention at least one member"); + return; + } + + Neo4jConnection connection = Neo4jConnection.getInstance(); + String target = mentions.get(0).getId(); + + if (connection.isSubbed(uid, target)) { + reply("You are already subscribed to the user"); + return; + } + + connection.createSub(uid, target); + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/core/Main.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/Main.java new file mode 100644 index 0000000..78ef7ff --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/Main.java @@ -0,0 +1,63 @@ +package com.github.nilstrieb.recommendationbot.core; + +import com.github.nilstrieb.recommendationbot.cofig.Config; +import com.github.nilstrieb.recommendationbot.cofig.Secrets; +import com.github.nilstrieb.recommendationbot.commands.info.HelpCommand; +import com.github.nilstrieb.recommendationbot.commands.info.InfoCommand; +import com.github.nilstrieb.recommendationbot.commands.info.InviteCommand; +import com.github.nilstrieb.recommendationbot.commands.info.RebootCommand; +import com.github.nilstrieb.recommendationbot.commands.reco.RecommendCommand; +import com.github.nilstrieb.recommendationbot.commands.reco.SubCommand; +import com.github.nilstrieb.recommendationbot.core.command.CommandListener; +import com.github.nilstrieb.recommendationbot.core.reactions.ReactionEventListener; +import com.github.nilstrieb.recommendationbot.core.sections.ChannelMessageListener; +import com.github.nilstrieb.recommendationbot.db.Neo4jConnection; +import com.github.nilstrieb.recommendationbot.listeners.StartUpListener; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.OnlineStatus; +import net.dv8tion.jda.api.entities.Activity; +import net.dv8tion.jda.api.requests.GatewayIntent; +import net.dv8tion.jda.api.utils.Compression; +import net.dv8tion.jda.api.utils.MemberCachePolicy; +import net.dv8tion.jda.api.utils.cache.CacheFlag; + +import javax.security.auth.login.LoginException; + +public class Main { + + public static void main(String[] args) throws LoginException { + JDABuilder builder = + JDABuilder.createDefault(Secrets.BOT_TOKEN); + + builder.setCompression(Compression.ZLIB); + builder.setStatus(OnlineStatus.ONLINE); + builder.setAutoReconnect(true); + + builder.setActivity(Activity.listening(Config.PREFIX + "help")); + builder.setDisabledIntents(GatewayIntent.GUILD_PRESENCES, GatewayIntent.GUILD_MEMBERS); + builder.setMemberCachePolicy(MemberCachePolicy.NONE); + builder.disableCache(CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS, CacheFlag.ROLE_TAGS, CacheFlag.MEMBER_OVERRIDES, CacheFlag.EMOTE, CacheFlag.VOICE_STATE); + builder.addEventListeners( + new StartUpListener(), + new ChannelMessageListener(), + new CommandListener(), + new ReactionEventListener() + ); + + + JDA jda = builder.build(); + Config.setJda(jda); + setupCommands(); + Neo4jConnection.create("bolt://localhost:7687", "neo4j", Secrets.NEO4J_PW); + } + + private static void setupCommands() { + new HelpCommand(); + new InviteCommand(); + new InfoCommand(); + new RebootCommand(); + new RecommendCommand(); + new SubCommand(); + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/core/command/Command.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/Command.java new file mode 100644 index 0000000..9b57d08 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/Command.java @@ -0,0 +1,80 @@ +package com.github.nilstrieb.recommendationbot.core.command; + +/** + * The base command class. Every command should extend this class. + */ +public abstract class Command extends MessageSender { + private String name; + private String description; + private String exampleUsage; + private String arguments; + private String detailDescription; + + //Array of all command aliases + private String[] aliases; + //whether the command should be displayed on the help page + //note: this does not make the command restricted in any way + private boolean hidden; + + /** + * Create a new command + * @param name name + */ + public Command(String name) { + this.name = name; + this.description = ""; + this.exampleUsage = ""; + this.arguments = ""; + this.detailDescription = ""; + CommandHandler.addCommand(name, this); + } + + protected void setAlias(String alias) { + CommandHandler.addAlias(alias, this); + } + + protected void setDescription(String description) { + this.description = description; + } + + protected void setExampleUsage(String exampleUsage) { + this.exampleUsage = exampleUsage; + } + + protected void setArguments(String arguments) { + this.arguments = arguments; + } + + protected void setDetailDescription(String detailDescription) { + this.detailDescription = detailDescription; + } + + protected void setHidden() { + this.hidden = true; + CommandHandler.hide(this); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getExampleUsage() { + return exampleUsage; + } + + public String getArguments() { + return arguments; + } + + public String getDetailDescription() { + return detailDescription; + } + + public boolean isHidden() { + return hidden; + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandHandler.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandHandler.java new file mode 100644 index 0000000..d2216c2 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandHandler.java @@ -0,0 +1,138 @@ +package com.github.nilstrieb.recommendationbot.core.command; + +import com.github.nilstrieb.recommendationbot.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.*; + +/** + * 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 HashSet uniqueVisibleCommands = new HashSet<>(); + + 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 + */ + static void addCommand(String key, Command cmd) { + commands.put(key, cmd); + + if (!cmd.isHidden()) { + uniqueVisibleCommands.add(cmd); + } + } + + /** + * Add an alias for a command + * + * @param alias The alias + * @param command The command object + */ + static void addAlias(String alias, Command command) { + commands.put(alias, command); + } + + /** + * Hide a command + * + * @param command The command to be hidden + */ + static void hide(Command command) { + uniqueVisibleCommands.remove(command); + } + + /** + * 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]); + } + } + } + + /** + * Get a list of all commands, returns many pages if there are lots of commands + * + * @return The pages + */ + public static MessageEmbed[] getHelpList() { + + List embeds = new ArrayList<>(); + + EmbedBuilder builder = Config.getDefaultEmbed() + .setTitle("Touka's Commands"); + + int length = 0; + for (Command cmd : uniqueVisibleCommands) { + if (length == MAX_HELP_PAGE_LENGTH) { + embeds.add(builder.build()); + builder = Config.getDefaultEmbed() + .setTitle("Touka's Commands"); + } + + builder.addField(Config.PREFIX + cmd.getName(), "`" + cmd.getDescription() + "`", false); + length++; + } + if (builder.getFields().size() != 0) { + embeds.add(builder.build()); + } + + MessageEmbed[] array = new MessageEmbed[embeds.size()]; + for (int i = 0; i < embeds.size(); i++) { + array[i] = embeds.get(i); + } + return array; + } + + + /** + * 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("Touka help for: " + Config.PREFIX + cmd.getName()) + .addField("Name", cmd.getName(), true); + + + if (!cmd.getDescription().equals("")) { + builder.addField("Description:", cmd.getDescription(), true); + } + if (cmd.getExampleUsage().equals("")) { + builder.addField("Example usage", "`" + Config.PREFIX + cmd.getName() + "`", true); + } else { + builder.addField("Example usage", "`" + Config.PREFIX + cmd.getExampleUsage() + "`", true); + } + if (!cmd.getArguments().equals("")) { + builder.addField("Arguments", "`" + cmd.getArguments() + "`", true); + } + if (!cmd.getDetailDescription().equals("")) { + builder.addField("Details:", "`" + cmd.getDetailDescription() + "`", true); + } + return builder.build(); + } + + return null; + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandListener.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandListener.java new file mode 100644 index 0000000..2c05119 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandListener.java @@ -0,0 +1,14 @@ +package com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/command/CommandParser.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandParser.java new file mode 100644 index 0000000..363afe9 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/CommandParser.java @@ -0,0 +1,29 @@ +package com.github.nilstrieb.recommendationbot.core.command; + + +import com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/command/MessageSender.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/MessageSender.java new file mode 100644 index 0000000..22fb1e0 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/command/MessageSender.java @@ -0,0 +1,128 @@ +package com.github.nilstrieb.recommendationbot.core.command; + + +import com.github.nilstrieb.recommendationbot.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()) { + return; + } + + if (embeds.length == 1) { + reply(embeds[0]); + } else { + 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 timeout The timeout in milliseconds + * @param embeds The embeds + */ + protected void reply(long timeout, MessageEmbed... embeds) { + if (!embeds[0].isEmpty()) { + new MultiPageEmbed(event, timeout, 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/com/github/nilstrieb/recommendationbot/core/reactions/ReactionAdapter.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionAdapter.java new file mode 100644 index 0000000..9647317 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionAdapter.java @@ -0,0 +1,48 @@ +package com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventListener.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventListener.java new file mode 100644 index 0000000..60de1f8 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventListener.java @@ -0,0 +1,22 @@ +package com.github.nilstrieb.recommendationbot.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 || !event.getUser().isBot()) { + ReactionEventManager.onReactionAdd(event); + } + } + + @Override + public void onMessageReactionRemove(@NotNull MessageReactionRemoveEvent event) { + if (event.getUser() == null || !event.getUser().isBot()) { + ReactionEventManager.onReactionRemove(event); + } + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventManager.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventManager.java new file mode 100644 index 0000000..4517c8d --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionEventManager.java @@ -0,0 +1,34 @@ +package com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/reactions/ReactionListener.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionListener.java new file mode 100644 index 0000000..4cf9168 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/reactions/ReactionListener.java @@ -0,0 +1,9 @@ +package com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/sections/ChannelListener.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelListener.java new file mode 100644 index 0000000..ee322dc --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelListener.java @@ -0,0 +1,9 @@ +package com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageEventManager.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageEventManager.java new file mode 100644 index 0000000..4cf26ac --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageEventManager.java @@ -0,0 +1,58 @@ +package com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageListener.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageListener.java new file mode 100644 index 0000000..d622956 --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/ChannelMessageListener.java @@ -0,0 +1,14 @@ +package com.github.nilstrieb.recommendationbot.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/com/github/nilstrieb/recommendationbot/core/sections/Section.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/Section.java new file mode 100644 index 0000000..993bc3a --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/sections/Section.java @@ -0,0 +1,74 @@ +package com.github.nilstrieb.recommendationbot.core.sections; + +import com.github.nilstrieb.recommendationbot.core.command.MessageSender; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * 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; + } + + protected void setSectionTimeout(long timeout) { + //SECTION TIMEOUT + //is safe because listeners can be removed multiple times + final Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + dispose(); + t.cancel(); + } + }, timeout); + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/core/util/MultiPageEmbed.java b/src/main/java/com/github/nilstrieb/recommendationbot/core/util/MultiPageEmbed.java new file mode 100644 index 0000000..aff62cf --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/core/util/MultiPageEmbed.java @@ -0,0 +1,91 @@ +package com.github.nilstrieb.recommendationbot.core.util; + +import com.github.nilstrieb.recommendationbot.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; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * 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()); + }); + } + + public MultiPageEmbed(MessageReceivedEvent event, long timeout, MessageEmbed[] pages) { + this(event, PREVIOUS_PAGE_DEFAULT_REACTION, NEXT_PAGE_DEFAULT_REACTION, pages); + + //MPE TIMEOUT + //is safe because listeners can be removed multiple times + final Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + dispose(); + t.cancel(); + } + }, timeout); + } + + @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(); + } + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/db/Neo4jConnection.java b/src/main/java/com/github/nilstrieb/recommendationbot/db/Neo4jConnection.java new file mode 100644 index 0000000..8711bba --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/db/Neo4jConnection.java @@ -0,0 +1,75 @@ +package com.github.nilstrieb.recommendationbot.db; + +import org.neo4j.driver.*; + +import static org.neo4j.driver.Values.parameters; + +public class Neo4jConnection implements AutoCloseable { + + private static Neo4jConnection connection; + + private final Driver driver; + + public static void create(String uri, String user, String password) { + connection = new Neo4jConnection(uri, user, password); + } + + private Neo4jConnection(String uri, String user, String password) { + driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password)); + } + + @Override + public void close() throws Exception { + driver.close(); + } + + public void createRecommendation(String uid, String name) { + try (Session session = driver.session()) { + session.writeTransaction(tx -> { + tx.run(""" + MERGE (u:User {id:$uid}) + MERGE (t:Thing {name:$name}) + CREATE (u) - [:LIKES] -> (t) + """, + parameters("uid", uid, "name", name)); + return 0; + }); + } + System.out.println("inserted :LIKES"); + } + + public void createSub(String uid, String targetId) { + try (Session session = driver.session()) { + session.writeTransaction(tx -> { + tx.run(""" + MERGE (u:User {id:$uid}) + MERGE (t:User {id:$target}) + CREATE (u) - [:SUBS_TO] -> (t) + """, + parameters("uid", uid, "target", targetId)); + return 0; + }); + } + System.out.println("inserted :SUBS_TO"); + } + + public boolean isSubbed(String uid, String targetId) { + return false; + } + + public void printGreeting(String message) { + try (Session session = driver.session()) { + String greeting = session.writeTransaction(tx -> { + Result result = tx.run("CREATE (a:Greeting) " + + "SET a.message = $message " + + "RETURN a.message + ', from node ' + id(a)", + parameters("message", message)); + return result.single().get(0).asString(); + }); + } + } + + public static Neo4jConnection getInstance() { + return connection; + } +} diff --git a/src/main/java/com/github/nilstrieb/recommendationbot/listeners/StartUpListener.java b/src/main/java/com/github/nilstrieb/recommendationbot/listeners/StartUpListener.java new file mode 100644 index 0000000..597b64a --- /dev/null +++ b/src/main/java/com/github/nilstrieb/recommendationbot/listeners/StartUpListener.java @@ -0,0 +1,12 @@ +package com.github.nilstrieb.recommendationbot.listeners; + +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] Bot started"); + } +}