diff --git a/.gitignore b/.gitignore
index 460a283..06f95de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,6 @@ gradle-app.setting
!gradle-wrapper.jar
# Cache of project
-.gradletasknamecache
\ No newline at end of file
+.gradletasknamecache
+
+start.sh
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..e96534f
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index ae29da0..c96b9d7 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -1,7 +1,23 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -22,25 +38,85 @@
-
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
-
+
+
@@ -51,16 +127,34 @@
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -69,13 +163,14 @@
+
+
+
-
-
@@ -109,13 +204,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -148,17 +273,13 @@
-
-
-
-
-
-
+
-
-
-
-
+
+
+
+
+
@@ -166,43 +287,197 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 39de5f0..ed9d538 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,7 +16,10 @@ repositories {
dependencies {
compile 'net.dv8tion:JDA:3.8.3_464'
compile 'com.sedmelluq:lavaplayer:1.3.17'
+
compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.26'
+
+ compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
}
jar {
diff --git a/src/main/java/com/redstoner/redstonerBot/Env.java b/src/main/java/com/redstoner/redstonerBot/Env.java
new file mode 100644
index 0000000..bc59e58
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/Env.java
@@ -0,0 +1,13 @@
+package com.redstoner.redstonerBot;
+
+public class Env {
+ public static final String TOKEN = System.getenv("TOKEN");
+
+ public static final String MYSQL_HOST = System.getenv("MYSQL_HOST");
+ public static final String MYSQL_PORT = System.getenv("MYSQL_PORT");
+
+ public static final String MYSQL_USER = System.getenv("MYSQL_USER");
+ public static final String MYSQL_PASS = System.getenv("MYSQL_PASS");
+
+ public static final String MYSQL_DB = System.getenv("MYSQL_DB");
+}
diff --git a/src/main/java/com/redstoner/redstonerBot/Main.java b/src/main/java/com/redstoner/redstonerBot/Main.java
index 10f73e6..f43d357 100644
--- a/src/main/java/com/redstoner/redstonerBot/Main.java
+++ b/src/main/java/com/redstoner/redstonerBot/Main.java
@@ -1,12 +1,62 @@
package com.redstoner.redstonerBot;
+import com.redstoner.redstonerBot.managers.CommandManager;
+import com.redstoner.redstonerBot.managers.DataManager;
+import com.redstoner.redstonerBot.managers.DiscordManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
public class Main {
- public static final Logger logger = LoggerFactory.getLogger(Main.class);
+ private static final Logger logger = LoggerFactory.getLogger(Main.class);
+
+ private static final List managers = new ArrayList<>();
+ private static final List loadedManagers = new ArrayList<>();
+
+ static {
+ managers.add(new DataManager());
+ managers.add(new DiscordManager());
+ managers.add(new CommandManager());
+ }
public static void main(final String... args) {
logger.info("Starting RedstonerBot...");
+
+ if (!start()) {
+ stop();
+ System.exit(1);
+ }
+ }
+
+ private static boolean start() {
+ loadedManagers.clear();
+
+ for (Manager m : managers) {
+ if (m.start()) {
+ loadedManagers.add(m);
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean stop() {
+ for (Manager m : getReversedManagers()) {
+ if (!m.stop()) return false;
+ }
+
+ return true;
+ }
+
+ private static List getReversedManagers() {
+ List reversedManagers = new ArrayList<>(loadedManagers);
+ Collections.reverse(reversedManagers);
+
+ return reversedManagers;
}
}
diff --git a/src/main/java/com/redstoner/redstonerBot/Manager.java b/src/main/java/com/redstoner/redstonerBot/Manager.java
new file mode 100644
index 0000000..3c77d4c
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/Manager.java
@@ -0,0 +1,6 @@
+package com.redstoner.redstonerBot;
+
+public interface Manager {
+ boolean start();
+ boolean stop();
+}
diff --git a/src/main/java/com/redstoner/redstonerBot/commands/Command.java b/src/main/java/com/redstoner/redstonerBot/commands/Command.java
new file mode 100644
index 0000000..0ec335a
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/commands/Command.java
@@ -0,0 +1,7 @@
+package com.redstoner.redstonerBot.commands;
+
+import net.dv8tion.jda.core.entities.*;
+
+public interface Command {
+ boolean execute(Guild guild, TextChannel channel, Message message, User author, Member self, String command, String[] params);
+}
diff --git a/src/main/java/com/redstoner/redstonerBot/commands/InfoCommand.java b/src/main/java/com/redstoner/redstonerBot/commands/InfoCommand.java
new file mode 100644
index 0000000..11377be
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/commands/InfoCommand.java
@@ -0,0 +1,24 @@
+package com.redstoner.redstonerBot.commands;
+
+import com.redstoner.redstonerBot.managers.DiscordManager;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.entities.*;
+
+import java.awt.*;
+
+public class InfoCommand implements Command {
+ @Override
+ public boolean execute(Guild guild, TextChannel channel, Message message, User author, Member self, String command, String[] params) {
+ EmbedBuilder embed = new EmbedBuilder();
+
+ embed.setTitle("Information");
+ embed.setDescription("This is a Discord bot built by psrcek for the Redstoner Guild.");
+
+ embed.setColor(Color.ORANGE);
+ embed.setFooter("Redstoner bot", "https://cdn.discordapp.com/avatars/576432236702859284/b08d4dc368b2e041ebb3fc208a2e8230.png");
+
+ message.getChannel().sendMessage(embed.build()).queue(DiscordManager::expireMessage);
+
+ return true;
+ }
+}
diff --git a/src/main/java/com/redstoner/redstonerBot/commands/StopCommand.java b/src/main/java/com/redstoner/redstonerBot/commands/StopCommand.java
new file mode 100644
index 0000000..8cb0fdd
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/commands/StopCommand.java
@@ -0,0 +1,25 @@
+package com.redstoner.redstonerBot.commands;
+
+import com.redstoner.redstonerBot.Main;
+import com.redstoner.redstonerBot.managers.DiscordManager;
+import net.dv8tion.jda.core.entities.*;
+
+public class StopCommand implements Command {
+ @Override
+ public boolean execute(Guild guild, TextChannel channel, Message message, User author, Member self, String command, String[] params) {
+ if (!guild.getOwnerId().equals(author.getId())) return false;
+
+ message.getChannel().sendMessage("This bot will stop in 5 seconds, as requested by " + author.getAsTag()).queue(DiscordManager::expireMessage);
+
+ new Thread(() -> {
+ try {
+ Thread.sleep(6000);
+ } catch (InterruptedException ignored) {
+ }
+
+ Main.stop();
+ }).start();
+
+ return true;
+ }
+}
diff --git a/src/main/java/com/redstoner/redstonerBot/listeners/MessageReceived.java b/src/main/java/com/redstoner/redstonerBot/listeners/MessageReceived.java
new file mode 100644
index 0000000..f91761f
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/listeners/MessageReceived.java
@@ -0,0 +1,43 @@
+package com.redstoner.redstonerBot.listeners;
+
+import com.redstoner.redstonerBot.managers.CommandManager;
+import com.redstoner.redstonerBot.managers.DataManager;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.*;
+import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent;
+import net.dv8tion.jda.core.hooks.ListenerAdapter;
+import net.dv8tion.jda.core.utils.Checks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+
+public class MessageReceived extends ListenerAdapter {
+ private static final Logger logger = LoggerFactory.getLogger(MessageReceived.class);
+
+ @Override
+ public final void onGuildMessageReceived(GuildMessageReceivedEvent event) {
+ Checks.notNull(event, "Event");
+
+ final Guild guild = event.getGuild();
+ final TextChannel channel = event.getChannel();
+ final Message message = event.getMessage();
+ final User author = event.getAuthor();
+
+ final Member self = guild.getSelfMember();
+
+ if (!Objects.equals(guild.getId(), DataManager.getConfigValue("guild_id"))) return;
+ if (author.isBot()) return;
+ if (message.isTTS()) return;
+ if (!self.hasPermission(channel, Permission.MESSAGE_WRITE)) return;
+ if (!self.hasPermission(channel, Permission.MESSAGE_EMBED_LINKS)) return;
+
+ String rawMessage = message.getContentRaw();
+
+ logger.info(author.getAsTag() + " -> " + rawMessage);
+
+ if (rawMessage.startsWith(DataManager.getConfigValue("prefix_char"))) {
+ CommandManager.execute(guild, channel, message, author, self);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/redstoner/redstonerBot/managers/CommandManager.java b/src/main/java/com/redstoner/redstonerBot/managers/CommandManager.java
new file mode 100644
index 0000000..81ef3bb
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/managers/CommandManager.java
@@ -0,0 +1,61 @@
+package com.redstoner.redstonerBot.managers;
+
+import com.redstoner.redstonerBot.Manager;
+import com.redstoner.redstonerBot.commands.Command;
+import com.redstoner.redstonerBot.commands.InfoCommand;
+import com.redstoner.redstonerBot.commands.StopCommand;
+import net.dv8tion.jda.core.entities.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CommandManager implements Manager {
+ private static final Logger logger = LoggerFactory.getLogger(CommandManager.class);
+
+ private static Map commands = new HashMap<>();
+
+ @Override
+ public boolean start() {
+ logger.info("Command Manager starting...");
+
+ commands.put("info", new InfoCommand());
+ commands.put("stop", new StopCommand());
+
+
+ logger.info("Command Manager started!");
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ logger.info("Command Manager stopping...");
+
+ commands.clear();
+
+ logger.info("Command Manager stopped!");
+ return true;
+ }
+
+ public static void execute(Guild guild, TextChannel channel, Message message, User author, Member self) {
+ String rawMsg = message.getContentRaw();
+ String[] rawCmd = rawMsg.split(" ");
+ String cmd = rawCmd[0].substring(1);
+ String[] params = Arrays.copyOfRange(rawCmd, 1, rawCmd.length);
+
+ Command command = commands.get(cmd);
+
+ if (command == null) return;
+ logger.info("[" + message.getId() + "] User '" + author.getAsTag() + "' executed command " + cmd + " with parameters " + Arrays.toString(params));
+
+ if (command.execute(guild, channel, message, author, self, cmd, params)) {
+ logger.info("[" + message.getId() + "] Command executed successfully!");
+ } else {
+ logger.error("[" + message.getId() + "] Error while executing command!");
+ }
+
+ message.delete().reason("Redstoner Bot command execution").queue();
+ }
+}
diff --git a/src/main/java/com/redstoner/redstonerBot/managers/DataManager.java b/src/main/java/com/redstoner/redstonerBot/managers/DataManager.java
new file mode 100644
index 0000000..e88a711
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/managers/DataManager.java
@@ -0,0 +1,118 @@
+package com.redstoner.redstonerBot.managers;
+
+import com.redstoner.redstonerBot.Env;
+import com.redstoner.redstonerBot.Manager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DataManager implements Manager {
+ private static final Logger logger = LoggerFactory.getLogger(DataManager.class);
+
+ private static final String[] requiredTables = { "config", "opt_in", "rules", "rule_agree_reactions" };
+
+ private static Map config = new HashMap<>();
+
+ public boolean start() {
+ logger.info("Data Manager starting...");
+
+ if (checkConnection()) {
+ logger.info("Loading config...");
+
+ if (!loadConfig()) {
+ logger.error("Data Manager failed to load config!");
+ return false;
+ }
+
+ logger.info("Data Manager started!");
+ return true;
+ } else {
+ logger.error("Data Manager failed to start!");
+ return false;
+ }
+ }
+
+ public boolean stop() {
+ logger.info("Data Manager stopping...");
+
+ config.clear();
+
+ logger.info("Data Manager stopped!");
+ return true;
+ }
+
+ private static String getConnectionString() {
+ return "jdbc:mysql://"
+ + Env.MYSQL_HOST + ":" + Env.MYSQL_PORT
+ + "/" + Env.MYSQL_DB
+ + "?useUnicode=true&characterEncoding=UTF-8"
+ + "&user=" + Env.MYSQL_USER
+ + "&password=" + Env.MYSQL_PASS;
+ }
+
+ private static Connection getConnection() throws SQLException {
+ return DriverManager.getConnection(getConnectionString());
+ }
+
+ private boolean checkConnection() {
+ try {
+ Class.forName("com.mysql.cj.jdbc.Driver");
+
+ Connection conn = getConnection();
+ ResultSet rs = conn.prepareStatement("show tables").executeQuery();
+
+ List tables = new ArrayList<>();
+
+ while (rs.next()) {
+ tables.add(rs.getString(1));
+ }
+
+ conn.close();
+
+ for (String rt : requiredTables) {
+ if (!tables.contains(rt)) {
+ logger.error("The " + rt + " table is missing from the database!");
+ return false;
+ }
+ }
+
+ return true;
+ } catch (ClassNotFoundException | SQLException e) {
+ logger.error("SQL error:", e);
+ return false;
+ }
+ }
+
+ public static String getConfigValue(String name) {
+ return config.get(name);
+ }
+
+ public boolean loadConfig() {
+ config.clear();
+
+ try {
+ Connection conn = getConnection();
+ ResultSet rs = conn.prepareStatement("SELECT * FROM config").executeQuery();
+
+ while (rs.next()) {
+ String name = rs.getString(2);
+ String value = rs.getString(3);
+
+ config.put(name, value);
+ }
+
+ return true;
+ } catch (SQLException e) {
+ logger.error("SQL error:", e);
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/redstoner/redstonerBot/managers/DiscordManager.java b/src/main/java/com/redstoner/redstonerBot/managers/DiscordManager.java
new file mode 100644
index 0000000..1d737fb
--- /dev/null
+++ b/src/main/java/com/redstoner/redstonerBot/managers/DiscordManager.java
@@ -0,0 +1,69 @@
+package com.redstoner.redstonerBot.managers;
+
+import com.redstoner.redstonerBot.Env;
+import com.redstoner.redstonerBot.Manager;
+import com.redstoner.redstonerBot.listeners.MessageReceived;
+import net.dv8tion.jda.core.AccountType;
+import net.dv8tion.jda.core.JDA;
+import net.dv8tion.jda.core.JDABuilder;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Message;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.login.LoginException;
+import java.util.concurrent.TimeUnit;
+
+public class DiscordManager implements Manager {
+ private static final Logger logger = LoggerFactory.getLogger(DiscordManager.class);
+
+ private JDA jda;
+
+ public boolean start() {
+ logger.info("Discord Manager starting...");
+
+ try {
+ final JDABuilder builder = new JDABuilder(AccountType.BOT);
+
+ builder.setAutoReconnect(true);
+ builder.setAudioEnabled(true);
+
+ builder.setToken(Env.TOKEN);
+
+ builder.addEventListener(new MessageReceived());
+
+ jda = builder.build().awaitReady();
+ } catch (LoginException e) {
+ logger.error("The discord token is invalid!");
+ return false;
+ } catch (Throwable t) {
+ logger.error("JDA setup error:", t);
+ return false;
+ }
+
+ Guild guild = jda.getGuildById(DataManager.getConfigValue("guild_id"));
+
+ String guildName = guild.getName();
+ String infoChannelName = guild.getTextChannelById(DataManager.getConfigValue("info_channel_id")).getName();
+
+ logger.info("Discord Manager started and logged in as '" + jda.getSelfUser().getName() + "'");
+ logger.info("Listening to guild '" + guildName + "'");
+ logger.info("Using channel '" + infoChannelName + "' as the info channel");
+
+ logger.info("Discord Manager started!");
+ return true;
+ }
+
+ public boolean stop() {
+ logger.info("Discord Manager stopping...");
+
+ jda.shutdown();
+
+ logger.info("Discord Manager stopped!");
+ return true;
+ }
+
+ public static void expireMessage(Message message) {
+ message.delete().reason("Redstoner Bot message expiry").queueAfter(5, TimeUnit.SECONDS);
+ }
+}