From e5fb48cb6a483b0570349955aa205c59b0fa9541 Mon Sep 17 00:00:00 2001 From: Pingex Date: Fri, 8 Apr 2016 22:03:43 +0200 Subject: [PATCH] Modules foundation + loader Also added missing documentation --- config.example.ini | 7 ++- .../net/pingex/discordbot/AbstractModule.java | 32 ++++++++++ .../net/pingex/discordbot/Configuration.java | 2 +- .../net/pingex/discordbot/DiscordBot.java | 58 ++++++++++++++++++- .../pingex/discordbot/HelloWorldModule.java | 28 +++++++++ .../net/pingex/discordbot/IControllable.java | 24 ++++++++ .../discordbot/InvalidModuleException.java | 34 +++++++++++ 7 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 src/main/java/net/pingex/discordbot/AbstractModule.java create mode 100644 src/main/java/net/pingex/discordbot/HelloWorldModule.java create mode 100644 src/main/java/net/pingex/discordbot/IControllable.java create mode 100644 src/main/java/net/pingex/discordbot/InvalidModuleException.java diff --git a/config.example.ini b/config.example.ini index b7f6167..8905b82 100644 --- a/config.example.ini +++ b/config.example.ini @@ -4,4 +4,9 @@ isBot = false email = bot@example.com password = samplePassword -; token = sampleToken \ No newline at end of file +; token = sampleToken + +[plugins] +; Comma-separated list of modules to load +; enable = net.pingex.discordbot.HelloWorldModule, net.pingex.discordbot.AnothaModule, fr.w4rell.discordbot.ThemeSombreModule +enable = net.pingex.discordbot.HelloWorldModule \ No newline at end of file diff --git a/src/main/java/net/pingex/discordbot/AbstractModule.java b/src/main/java/net/pingex/discordbot/AbstractModule.java new file mode 100644 index 0000000..425a793 --- /dev/null +++ b/src/main/java/net/pingex/discordbot/AbstractModule.java @@ -0,0 +1,32 @@ +package net.pingex.discordbot; + +import sx.blah.discord.api.IDiscordClient; +import java.util.ArrayList; +import java.util.logging.Logger; + +/** + * Defines a basic bot module + * @version 0.1-dev + * @author Raphael "Pingex" NAAS + */ +public abstract class AbstractModule +{ + /** + * Contains all `AbstractModule` instances created. + */ + private static final ArrayList REGISTERED = new ArrayList<>(); + + protected Logger logger; + + /** + * Constructor doing all the basic stuff, like registering as a Listener to Discord, getting a logger, etc. + * @param client Discord Client instance used to register self. + */ + public AbstractModule(IDiscordClient client) + { + REGISTERED.add(this); + client.getDispatcher().registerListener(this); + logger = Logger.getLogger(this.getClass().getName()); + logger.info("Loading module " + this.getClass().getName()); + } +} diff --git a/src/main/java/net/pingex/discordbot/Configuration.java b/src/main/java/net/pingex/discordbot/Configuration.java index 8fd33ad..5f8eaf4 100644 --- a/src/main/java/net/pingex/discordbot/Configuration.java +++ b/src/main/java/net/pingex/discordbot/Configuration.java @@ -94,6 +94,6 @@ public final class Configuration */ public static boolean exists(String section, String key) { - return (datastore.get(section, key) != null) ? true : false; + return (datastore.get(section, key) != null); } } diff --git a/src/main/java/net/pingex/discordbot/DiscordBot.java b/src/main/java/net/pingex/discordbot/DiscordBot.java index 6a5fc6c..9e8b2fc 100644 --- a/src/main/java/net/pingex/discordbot/DiscordBot.java +++ b/src/main/java/net/pingex/discordbot/DiscordBot.java @@ -1,12 +1,12 @@ package net.pingex.discordbot; import sx.blah.discord.api.ClientBuilder; -import sx.blah.discord.api.Event; import sx.blah.discord.api.IDiscordClient; import sx.blah.discord.api.IListener; import sx.blah.discord.handle.impl.events.ReadyEvent; import sx.blah.discord.util.DiscordException; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.logging.Logger; /** @@ -17,15 +17,61 @@ import java.util.logging.Logger; public class DiscordBot { private static final Logger LOGGER = Logger.getLogger(DiscordBot.class.getName()); + + /** + * Discord Client instance + */ private IDiscordClient client; - public DiscordBot(IDiscordClient client) + /** + * Constructor of the singleton + * @param client Discord client instance used to Bootstrap the whole program + */ + private DiscordBot(IDiscordClient client) { this.client = client; LOGGER.info("Successfully logged in !"); this.client.getDispatcher().registerListener((IListener) event -> LOGGER.info("Logged in as \"" + event.getClient().getOurUser().getName() + "#" + event.getClient().getOurUser().getDiscriminator() + "\" (#" + event.getClient().getOurUser().getID() + ")")); + + enableModules(); + } + + private void enableModules() + { + LOGGER.info("Enabling modules"); + + if(!Configuration.exists("plugins", "enable") || Configuration.getValue("plugins", "enable").isEmpty()) + { + LOGGER.warning("Key \"plugins/enable\" doesn't exist in Configuration. No module will be enabled."); + return; + } + + for(String i : Configuration.getValue("plugins", "enable").split(",")) + { + i = i.trim(); + try + { + Class clazz = Class.forName(i); + if(!AbstractModule.class.isAssignableFrom(clazz)) + throw new InvalidModuleException(i + " isn't a valid module."); + clazz.getConstructor(IDiscordClient.class).newInstance(client); + } catch (ClassNotFoundException e) + { + LOGGER.warning("Class " + i + " wasn't found, thus won't be loaded."); + } catch (InvalidModuleException e) + { + LOGGER.warning(e.getMessage()); + } catch (NoSuchMethodException e) + { + LOGGER.warning("No valid Constructor for " + i + " was found."); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) + { + LOGGER.warning("An error occured while creating an instance of " + i); + e.printStackTrace(); + } + } } // ==================================================== @@ -43,7 +89,7 @@ public class DiscordBot // LOAD CONFIGURATION try { - Configuration.loadConfiguration("config.ini"); + Configuration.loadConfiguration("config.ini"); // TODO: pass configuration file using commandline argument } catch (IOException e) { LOGGER.severe("Could not load Configuration, reason: " + e.getMessage()); @@ -65,6 +111,12 @@ public class DiscordBot } } + /** + * Discord login subroutine. Called by `main()` once + * @return Instance of DiscordBot if login was successful + * @throws ConfigurationException When token/email/password field in config.ini isn't filled + * @throws DiscordException + */ private static DiscordBot loginToDiscord() throws ConfigurationException, DiscordException { ClientBuilder builder = new ClientBuilder(); diff --git a/src/main/java/net/pingex/discordbot/HelloWorldModule.java b/src/main/java/net/pingex/discordbot/HelloWorldModule.java new file mode 100644 index 0000000..d52492e --- /dev/null +++ b/src/main/java/net/pingex/discordbot/HelloWorldModule.java @@ -0,0 +1,28 @@ +package net.pingex.discordbot; + +import sx.blah.discord.api.EventSubscriber; +import sx.blah.discord.api.IDiscordClient; +import sx.blah.discord.handle.impl.events.ReadyEvent; + +/** + * Very first Discord module, written for testing purposes + * @version 0.1-dev + * @author Raphael "Pingex" NAAS + */ +public class HelloWorldModule extends AbstractModule +{ + /** + * Constructor doing all the basic stuff, like registering as a Listener to Discord, getting a logger, etc. + * @param client Discord Client instance used to register self. + */ + public HelloWorldModule(IDiscordClient client) + { + super(client); + } + + @EventSubscriber + public void onReadyEvent(ReadyEvent event) + { + logger.info("READY"); + } +} diff --git a/src/main/java/net/pingex/discordbot/IControllable.java b/src/main/java/net/pingex/discordbot/IControllable.java new file mode 100644 index 0000000..6dc839e --- /dev/null +++ b/src/main/java/net/pingex/discordbot/IControllable.java @@ -0,0 +1,24 @@ +package net.pingex.discordbot; + +import java.util.Hashtable; + +/** + * Implemented by modules which can be controlled using Discord Commands + * @version 0.1-dev + * @author Raphael "Pingex" NAAS + */ +public interface IControllable +{ + /** + * A list of prefixes the module uses to identify itself. + * @return List of prefixes + */ + String[] getPrefixes(); + + /** + * Get a list of shortened commands. + * Example: command !module:myCommand can be shortened as !myc, !mc, or !myco, so the table would be like "myCommand" => ["myc", "mc", "myco"] + * @return Hastable containing all shortened commands. Key is the real command, Value is a list of eligible short commands. + */ + Hashtable getShorthands(); +} diff --git a/src/main/java/net/pingex/discordbot/InvalidModuleException.java b/src/main/java/net/pingex/discordbot/InvalidModuleException.java new file mode 100644 index 0000000..1642d9d --- /dev/null +++ b/src/main/java/net/pingex/discordbot/InvalidModuleException.java @@ -0,0 +1,34 @@ +package net.pingex.discordbot; + +/** + * Exception thrown when trying to load a module which isn't one. + * @version 0.1-dev + * @author Raphael "Pingex" NAAS + */ +public class InvalidModuleException extends Exception +{ + public InvalidModuleException(String message) + { + super(message); + } + + protected InvalidModuleException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) + { + super(message, cause, enableSuppression, writableStackTrace); + } + + public InvalidModuleException(Throwable cause) + { + super(cause); + } + + public InvalidModuleException(String message, Throwable cause) + { + super(message, cause); + } + + public InvalidModuleException() + { + super(); + } +}