diff --git a/ModularBot-Core/build.gradle b/ModularBot-Core/build.gradle index cdbbbc3..28d796b 100644 --- a/ModularBot-Core/build.gradle +++ b/ModularBot-Core/build.gradle @@ -1,7 +1,9 @@ dependencies { compile 'net.dv8tion:JDA:3.6.0_375' - testImplementation project(':modularbot-command') testImplementation project(':modularbot-logger') + testImplementation project(':modularbot-command') testImplementation project(':modularbot-night-config-wrapper') + testImplementation project(':modularbot-nashorn-support') + testImplementation project(':modularbot-nashorn-command-support') } diff --git a/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBot.java b/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBot.java index 6e09585..831105b 100644 --- a/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBot.java +++ b/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBot.java @@ -108,8 +108,10 @@ public void onReady(ReadyEvent event) { * Triggered when a shard is ready. */ private void onReady() { - if (receivedReady.get() == shardsTotal) + if (receivedReady.get() == shardsTotal) { + logger.info("Shards ready !"); moduleManager.finalizeInitialization(this); + } } /** @@ -117,9 +119,17 @@ private void onReady() { */ @Override protected ScheduledExecutorService createExecutor(ThreadFactory threadFactory) { + logger.debug("Creating a new executor."); return Executors.newScheduledThreadPool(corePoolSize, r -> { Thread t = threadFactory.newThread(r); t.setPriority(Thread.NORM_PRIORITY + 1); + + t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.error("Uncaught exception !", e); + } + }); return t; }); } diff --git a/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBotBuilder.java b/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBotBuilder.java index 797a5d8..2360918 100644 --- a/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBotBuilder.java +++ b/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/ModularBotBuilder.java @@ -188,13 +188,29 @@ public ModularBotBuilder autoLoadBaseModules() { // Try night config module try { - Class nightConfigModule = (Class) Class.forName("com.jesus_crie.modularbot_nightconfigwrapper.NightConfigWrapperModule"); + Class nightConfigModule = (Class) Class.forName("com.jesus_crie.modularbot_night_config_wrapper.NightConfigWrapperModule"); moduleManager.registerModules(this, nightConfigModule); } catch (ClassNotFoundException e) { LOG.debug("Failed to autoload night config module."); } - // TODO 16/06/18 renember to complete with new modules + // Try nashorn module + try { + Class nashornModule = (Class) Class.forName("com.jesus_crie.modularbot_nashorn_support.NashornSupportModule"); + moduleManager.registerModules(this, nashornModule); + } catch (ClassNotFoundException e) { + LOG.debug("Failed to autoload nashorn module."); + } + + // Try nashorn command module + try { + Class nashornCommandModule = (Class) Class.forName("com.jesus_crie.modularbot_nashorn_command_support.NashornCommandSupportModule"); + moduleManager.registerModules(this, nashornCommandModule); + } catch (ClassNotFoundException e) { + LOG.debug("Failed to autoload nashorn command module."); + } + + // TODO 16/06/18 remember to complete with new modules return this; } diff --git a/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/module/BaseModule.java b/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/module/BaseModule.java index ade73f0..4add165 100644 --- a/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/module/BaseModule.java +++ b/ModularBot-Core/src/main/java/com/jesus_crie/modularbot/module/BaseModule.java @@ -12,7 +12,7 @@ public abstract class BaseModule implements Lifecycle { */ Lifecycle.State state = State.STOPPED; - protected final ModuleInfo info; + protected ModuleInfo info; /** * Reference to the instance of {@link ModularBot}. diff --git a/ModularBot-Core/src/test/java/com/jesus_crie/modularbot/ModularTestRun.java b/ModularBot-Core/src/test/java/com/jesus_crie/modularbot/ModularTestRun.java index 41751eb..5254816 100644 --- a/ModularBot-Core/src/test/java/com/jesus_crie/modularbot/ModularTestRun.java +++ b/ModularBot-Core/src/test/java/com/jesus_crie/modularbot/ModularTestRun.java @@ -1,6 +1,5 @@ package com.jesus_crie.modularbot; -import com.electronwill.nightconfig.core.file.FileConfig; import com.jesus_crie.modularbot.module.BaseModule; import com.jesus_crie.modularbot_command.AccessLevel; import com.jesus_crie.modularbot_command.Command; @@ -10,14 +9,16 @@ import com.jesus_crie.modularbot_command.annotations.RegisterPattern; import com.jesus_crie.modularbot_command.processing.Option; import com.jesus_crie.modularbot_command.processing.Options; -import com.jesus_crie.modularbot_nightconfigwrapper.NightConfigWrapperModule; +import com.jesus_crie.modularbot_logger.ConsoleLoggerModule; +import com.jesus_crie.modularbot_nashorn_command_support.NashornCommandSupportModule; +import com.jesus_crie.modularbot_nashorn_support.NashornSupportModule; +import com.jesus_crie.modularbot_nashorn_support.module.JavaScriptModule; +import com.jesus_crie.modularbot_night_config_wrapper.NightConfigWrapperModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.security.auth.login.LoginException; -import java.io.File; -import java.util.List; import java.util.Optional; public class ModularTestRun extends BaseModule { @@ -26,30 +27,38 @@ public class ModularTestRun extends BaseModule { public static void main(String[] args) { final ModularBot bot = new ModularBotBuilder(args[0]) - .autoLoadBaseModules() + //.autoLoadBaseModules() + .registerModules( + new ConsoleLoggerModule(), + new CommandModule(), + new NightConfigWrapperModule("./example/config.json"), + new NashornSupportModule("./example/scripts/"), + new NashornCommandSupportModule() + ) .useShutdownNow() .build(); + // ConsoleLoggerModule.MIN_LEVEL = ModularLog.Level.TRACE; + + /// Commands + CommandModule cmd = bot.getModuleManager().getModule(CommandModule.class); //cmd.setCreatorId(182547138729869314L); cmd.registerCommands(new StopCommand()); + /// Config + NightConfigWrapperModule config = bot.getModuleManager().getModule(NightConfigWrapperModule.class); Optional startCount = config.getPrimaryConfig().getOptional("start_count"); long count = startCount.orElse(0L); count++; config.getPrimaryConfig().set("start_count", count); - config.loadConfigGroup("testConfigs", new File("./configs/"), true, "^.+\\.json$"); - //config.addSecondaryConfigToGroup("testConfigs", "./configs/user.json"); - - List userCfgs = config.getConfigGroup("testConfigs"); - LOG.info(String.valueOf(userCfgs)); - for (FileConfig cfg : userCfgs) { - cfg.set("hey", count); - } + /// JS - cmd.addCustomPrefixForGuild(264001800686796800L, "!!"); + NashornSupportModule js = bot.getModuleManager().getModule(NashornSupportModule.class); + JavaScriptModule testModule = js.getModuleByName("test"); + LOG.info("Test module file: " + testModule.getScriptLocation().getName()); try { bot.login(); diff --git a/ModularBot-Logger/src/main/java/com/jesus_crie/modularbot_logger/ConsoleLoggerModule.java b/ModularBot-Logger/src/main/java/com/jesus_crie/modularbot_logger/ConsoleLoggerModule.java index 315de35..b3f764c 100644 --- a/ModularBot-Logger/src/main/java/com/jesus_crie/modularbot_logger/ConsoleLoggerModule.java +++ b/ModularBot-Logger/src/main/java/com/jesus_crie/modularbot_logger/ConsoleLoggerModule.java @@ -61,6 +61,8 @@ public void onLoad(@Nonnull final ModuleManager moduleManager, @Nullable Modular System.out.println(message); } else { System.err.println(message); + if (log.error != null && log.level.getLevel() == ModularLog.Level.ERROR.getLevel()) + log.error.printStackTrace(); } } }); diff --git a/ModularBot-NashornCommandSupport/build.gradle b/ModularBot-NashornCommandSupport/build.gradle new file mode 100644 index 0000000..35cd6a9 --- /dev/null +++ b/ModularBot-NashornCommandSupport/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile project(':modularbot-core') + compile project(':modularbot-command') + compile project(':modularbot-nashorn-support') +} diff --git a/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/JavaScriptCommand.java b/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/JavaScriptCommand.java new file mode 100644 index 0000000..1d889a3 --- /dev/null +++ b/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/JavaScriptCommand.java @@ -0,0 +1,41 @@ +package com.jesus_crie.modularbot_nashorn_command_support; + +import com.jesus_crie.modularbot_command.AccessLevel; +import com.jesus_crie.modularbot_command.processing.CommandPattern; +import com.jesus_crie.modularbot_command.processing.Option; +import jdk.nashorn.api.scripting.ScriptObjectMirror; + +/** + * A class that represent a command that can be extended in JS and wrapped into a {@link JavaScriptCommandWrapper JavaScriptCommandWrapper} + * to be registered in the command module. + */ +public class JavaScriptCommand { + + public String[] aliases; + public String description = "No description."; + public String shortDescription = "No description."; + + public AccessLevel accessLevel = AccessLevel.EVERYONE; + public Option[] options = new Option[0]; + + public CommandPattern[] patterns = new CommandPattern[0]; + + /** + * Create a {@link JavaScriptCommand JavaScriptCommand} with the given JS object. + * This method must be called from a JS script. + * + * @param mirror The JS object provided. + * @return A new instance of {@link JavaScriptCommand JavaScriptCommand} that holds the information provided. + */ + public static JavaScriptCommand from(ScriptObjectMirror mirror) { + final JavaScriptCommand command = new JavaScriptCommand(); + command.aliases = ((ScriptObjectMirror) mirror.getMember("aliases")).to(String[].class); + command.description = (String) mirror.getMember("description"); + command.shortDescription = (String) mirror.getMember("shortDescription"); + command.accessLevel = (AccessLevel) mirror.getMember("accessLevel"); + command.options = ((ScriptObjectMirror) mirror.getMember("options")).to(Option[].class); + command.patterns = ((ScriptObjectMirror) mirror.getMember("patterns")).to(CommandPattern[].class); + + return command; + } +} diff --git a/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/JavaScriptCommandWrapper.java b/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/JavaScriptCommandWrapper.java new file mode 100644 index 0000000..2a7043d --- /dev/null +++ b/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/JavaScriptCommandWrapper.java @@ -0,0 +1,24 @@ +package com.jesus_crie.modularbot_nashorn_command_support; + +import com.jesus_crie.modularbot_command.Command; + +import javax.annotation.Nonnull; +import java.util.Collections; + +/** + * Class that map the content of a {@link JavaScriptCommand JavaScriptCommand} into a real {@link Command Command} + * usable by the command module. + *

+ * The command can't be modified after being wrapped. + */ +public class JavaScriptCommandWrapper extends Command { + + public JavaScriptCommandWrapper(@Nonnull final JavaScriptCommand command) { + super(command.aliases[0], command.accessLevel, command.shortDescription, command.description); + + aliases.clear(); + Collections.addAll(aliases, command.aliases); + Collections.addAll(options, command.options); + Collections.addAll(patterns, command.patterns); + } +} diff --git a/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/NashornCommandSupportModule.java b/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/NashornCommandSupportModule.java new file mode 100644 index 0000000..794f976 --- /dev/null +++ b/ModularBot-NashornCommandSupport/src/main/java/com/jesus_crie/modularbot_nashorn_command_support/NashornCommandSupportModule.java @@ -0,0 +1,51 @@ +package com.jesus_crie.modularbot_nashorn_command_support; + +import com.jesus_crie.modularbot.ModularBotBuilder; +import com.jesus_crie.modularbot.module.BaseModule; +import com.jesus_crie.modularbot.module.ModuleManager; +import com.jesus_crie.modularbot_command.CommandModule; +import com.jesus_crie.modularbot_nashorn_support.NashornSupportModule; +import com.jesus_crie.modularbot_nashorn_support.module.JavaScriptModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.script.ScriptException; + +public class NashornCommandSupportModule extends BaseModule { + + private static final Logger LOG = LoggerFactory.getLogger("NashornCommandSupportModule"); + + private static final ModuleInfo INFO = new ModuleInfo("JS Nashorn Command Support", "Jesus-Crie", + "https://github.com/JesusCrie/ModularBot", "1.0", 1); + + public NashornCommandSupportModule() { + super(INFO); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onLoad(@Nonnull ModuleManager moduleManager, @Nonnull ModularBotBuilder builder) { + final NashornSupportModule nashornModule = moduleManager.getModule(NashornSupportModule.class); + final CommandModule commandModule = moduleManager.getModule(CommandModule.class); + + for (JavaScriptModule module : nashornModule.getModules()) { + try { + final JavaScriptCommand[] commands = (JavaScriptCommand[]) module.getEngine().invokeFunction("getCommands"); + + if (commands == null) + continue; + + for (JavaScriptCommand command : commands) { + final JavaScriptCommandWrapper wrapper = new JavaScriptCommandWrapper(command); + commandModule.registerCommands(wrapper); + } + + } catch (ScriptException | ClassCastException e) { + LOG.error("Failed to load commands from JS module: " + module.getJsModule().getInfo().getName(), e); + } catch (NoSuchMethodException ignore) { + // The module have no command to register. + } + } + } +} diff --git a/ModularBot-NashornSupport/build.gradle b/ModularBot-NashornSupport/build.gradle new file mode 100644 index 0000000..b6a98ae --- /dev/null +++ b/ModularBot-NashornSupport/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(':modularbot-core') +} diff --git a/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/NashornSupportModule.java b/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/NashornSupportModule.java new file mode 100644 index 0000000..c01a9b7 --- /dev/null +++ b/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/NashornSupportModule.java @@ -0,0 +1,247 @@ +package com.jesus_crie.modularbot_nashorn_support; + +import com.jesus_crie.modularbot.ModularBot; +import com.jesus_crie.modularbot.ModularBotBuilder; +import com.jesus_crie.modularbot.module.BaseModule; +import com.jesus_crie.modularbot.module.Lifecycle; +import com.jesus_crie.modularbot.module.ModuleManager; +import com.jesus_crie.modularbot_nashorn_support.module.BaseJavaScriptModule; +import com.jesus_crie.modularbot_nashorn_support.module.JavaScriptModule; +import jdk.nashorn.api.scripting.NashornScriptEngine; +import jdk.nashorn.api.scripting.NashornScriptEngineFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.script.ScriptException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class NashornSupportModule extends BaseModule { + + private static final Logger LOG = LoggerFactory.getLogger("NashornSupportModule"); + + private static final ModuleInfo INFO = new ModuleInfo("JS Nashorn Support", "Jesus-Crie", + "https://github.com/JesusCrie/ModularBot", "1.0", 1); + + private String SCRIPT_HEADER; + + private NashornScriptEngineFactory engineFactory = new NashornScriptEngineFactory(); + private final File scriptDirectory; + private Map modules = Collections.emptyMap(); + + public NashornSupportModule() { + this("./scripts/"); + } + + public NashornSupportModule(@Nonnull final String path) { + this(new File(path)); + } + + public NashornSupportModule(@Nonnull final File directory) { + super(INFO); + scriptDirectory = directory; + + if (!directory.exists()) { + if (!directory.mkdirs()) + LOG.warn("Can't create the script directory !"); + } else if (!directory.isDirectory()) + throw new IllegalArgumentException("The provided location isn't a directory !"); + + // Load the header + + final File header = new File(scriptDirectory + "/_script_header.js"); + + if (header.exists()) { + try { + SCRIPT_HEADER = String.join("\n", + Files.readAllLines(Paths.get(header.toURI()), StandardCharsets.UTF_8)); + return; + } catch (IOException e) { + LOG.error("Failed to load custom script header ! Loading the default one..."); + } + } + + LOG.debug("No header found, using the default one."); + try { + SCRIPT_HEADER = String.join("\n", + Files.readAllLines( + Paths.get(NashornSupportModule.class.getResource("/script_header.js").toURI()), + StandardCharsets.UTF_8)); + + } catch (URISyntaxException | IOException e) { // Should not happen + LOG.error("Failed to load script_header.js from the JAR ! No header will be used, expect some errors !"); + SCRIPT_HEADER = ""; + } + } + + /** + * Load the scripts from the script directory. + * Skip the header file. + * For each script, create a new engine, evaluate the header and the script. + * Then wrap them in a {@link JavaScriptModule JavaScriptModule} and register it. + * The name of the register is the name of the file without the extension. + * + * @param directory The directory that contains the entry points of the modules to load. + * @see #registerScript(String, JavaScriptModule) + */ + private void loadScripts(@Nonnull final File directory) { + final File[] scripts = directory.listFiles((dir, name) -> !name.equals("_script_header.js") && name.endsWith(".js") && name.length() > 3); + + if (scripts == null) + return; + + for (File file : scripts) { + if (file.isDirectory()) continue; + + try { + final NashornScriptEngine engine = (NashornScriptEngine) engineFactory.getScriptEngine(); + engine.eval(SCRIPT_HEADER); + engine.eval(new FileReader(file)); + + final JavaScriptModule module = new JavaScriptModule(engine, file); + registerScript(file.getName().substring(0, file.getName().length() - 3), module); + + } catch (FileNotFoundException ignore) { // Can't happen + } catch (ScriptException e) { + LOG.error("Failed to load script: " + file, e); + } + } + } + + /** + * Register a {@link JavaScriptModule JavaScriptModule} by an arbitrary name. + * If the name is already taken, add a suffix with the index of the duplicate. + * + * @param name The arbitrary name of the module. + * @param module The wrapper that holds the module. + * @see #getModuleByName(String) + */ + public void registerScript(@Nonnull final String name, @Nonnull final JavaScriptModule module) { + if (modules.size() == 0) + modules = new HashMap<>(); + + modules.compute(name, (key, old) -> { + if (old == null) return module; + + // Skip the already used suffixes. + int i = 1; + while (modules.containsKey(name + "-" + i)) { + i++; + } + + modules.put(name + "-" + i, module); + return old; + }); + } + + /** + * Get a module by its arbitrary name. Usually the name of the file without the extension. + * + * @param name The name of the module to get. + * @return The module or {@code null} if no modules exist with this name. + * @see #registerScript(String, JavaScriptModule) + */ + @Nullable + public JavaScriptModule getModuleByName(@Nonnull final String name) { + return modules.get(name); + } + + /** + * Get a view of the JS modules currently registered. + * + * @return An immutable view of the JS modules currently registered. + */ + @Nonnull + public Collection getModules() { + return modules.values(); + } + + private void dispatchToModules(Consumer action) { + modules.values().forEach(module -> action.accept(module.getJsModule())); + } + + /** + * {@inheritDoc} + */ + @Override + public void onLoad(@Nonnull ModuleManager moduleManager, @Nonnull ModularBotBuilder builder) { + loadScripts(scriptDirectory); + + dispatchToModules(m -> m.onLoad(moduleManager, builder)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onInitialization() { + if (modules.size() > 0) + modules = Collections.unmodifiableMap(modules); + + dispatchToModules(Lifecycle::onInitialization); + + LOG.info(modules.size() + " JS modules initialized !"); + } + + /** + * {@inheritDoc} + */ + @Override + public void onPostInitialization() { + dispatchToModules(Lifecycle::onPostInitialization); + } + + /** + * {@inheritDoc} + */ + @Override + public void onPrepareShards() { + dispatchToModules(Lifecycle::onPrepareShards); + } + + /** + * {@inheritDoc} + */ + @Override + public void onShardsCreated() { + dispatchToModules(Lifecycle::onShardsCreated); + } + + /** + * {@inheritDoc} + */ + @Override + public void onShardsReady(@Nonnull ModularBot bot) { + super.onShardsReady(bot); + dispatchToModules(m -> m.onShardsReadyDelegate(bot)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onShutdownShards() { + dispatchToModules(Lifecycle::onShutdownShards); + } + + /** + * {@inheritDoc} + */ + @Override + public void onUnload() { + dispatchToModules(Lifecycle::onUnload); + } +} diff --git a/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/module/BaseJavaScriptModule.java b/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/module/BaseJavaScriptModule.java new file mode 100644 index 0000000..5f856c2 --- /dev/null +++ b/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/module/BaseJavaScriptModule.java @@ -0,0 +1,32 @@ +package com.jesus_crie.modularbot_nashorn_support.module; + +import com.jesus_crie.modularbot.ModularBot; +import com.jesus_crie.modularbot.module.BaseModule; + +import javax.annotation.Nonnull; + +/** + * Module that provide an empty constructor. This class is supposed to be extended by a script. + */ +public abstract class BaseJavaScriptModule extends BaseModule { + + /** + * Empty {@link com.jesus_crie.modularbot.module.BaseModule.ModuleInfo ModuleInfo}. + */ + private static final ModuleInfo NULL = new ModuleInfo("", "", "", "1.0", 0); + + public BaseJavaScriptModule() { + super(NULL); + } + + /** + * This method is triggered instead of {@link #onShardsReady(ModularBot)} for this type of module to avoid having + * to call the super in the JS script. + * + * @param bot The associated instance of {@link ModularBot ModularBot}; + */ + public final void onShardsReadyDelegate(@Nonnull ModularBot bot) { + this.bot = bot; + onShardsReady(bot); + } +} diff --git a/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/module/JavaScriptModule.java b/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/module/JavaScriptModule.java new file mode 100644 index 0000000..ad6b95a --- /dev/null +++ b/ModularBot-NashornSupport/src/main/java/com/jesus_crie/modularbot_nashorn_support/module/JavaScriptModule.java @@ -0,0 +1,60 @@ +package com.jesus_crie.modularbot_nashorn_support.module; + +import jdk.nashorn.api.scripting.NashornScriptEngine; + +import javax.annotation.Nonnull; +import javax.script.ScriptException; +import java.io.File; + +/** + * Holds the required background for a JS module to be executed. + */ +public class JavaScriptModule { + + private final NashornScriptEngine engine; + + private final File scriptLocation; + private final BaseJavaScriptModule jsModule; + + /** + * Query the JS module from the {@link NashornScriptEngine NashornScriptEngine} that contains its code. + * + * @param engine The script engine that runs the code of the module. + * @param scriptLocation The location of the entry point of the module. + * @throws ScriptException + */ + public JavaScriptModule(@Nonnull final NashornScriptEngine engine, @Nonnull final File scriptLocation) throws ScriptException { + this.engine = engine; + this.scriptLocation = scriptLocation; + + try { + jsModule = (BaseJavaScriptModule) engine.invokeFunction("getModule"); + } catch (NoSuchMethodException e) { + throw new ScriptException(e); + } + } + + /** + * @return The {@link NashornScriptEngine NashornScriptEngine} that runs the module. + */ + @Nonnull + public NashornScriptEngine getEngine() { + return engine; + } + + /** + * @return The location of the entry point of the module. + */ + @Nonnull + public File getScriptLocation() { + return scriptLocation; + } + + /** + * @return Get the JS module provided by the script. + */ + @Nonnull + public BaseJavaScriptModule getJsModule() { + return jsModule; + } +} diff --git a/ModularBot-NashornSupport/src/main/resources/script_header.js b/ModularBot-NashornSupport/src/main/resources/script_header.js new file mode 100644 index 0000000..2ca6005 --- /dev/null +++ b/ModularBot-NashornSupport/src/main/resources/script_header.js @@ -0,0 +1,4 @@ +var BaseJavaScriptModule = Java.type("com.jesus_crie.modularbot_nashorn_support.module.BaseJavaScriptModule"); +var ModuleInfo = Java.type("com.jesus_crie.modularbot.module.BaseModule.ModuleInfo"); + +var baseImports = new JavaImporter(java.lang, java.util, java.io, org.slf4j, com.jesus_crie.modularbot); \ No newline at end of file diff --git a/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_nightconfigwrapper/NightConfigWrapperModule.java b/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_night_config_wrapper/NightConfigWrapperModule.java similarity index 98% rename from ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_nightconfigwrapper/NightConfigWrapperModule.java rename to ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_night_config_wrapper/NightConfigWrapperModule.java index 0ece6e0..35d6759 100644 --- a/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_nightconfigwrapper/NightConfigWrapperModule.java +++ b/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_night_config_wrapper/NightConfigWrapperModule.java @@ -1,4 +1,4 @@ -package com.jesus_crie.modularbot_nightconfigwrapper; +package com.jesus_crie.modularbot_night_config_wrapper; import com.electronwill.nightconfig.core.Config; import com.electronwill.nightconfig.core.file.FileConfig; @@ -6,7 +6,7 @@ import com.jesus_crie.modularbot.ModularBotBuilder; import com.jesus_crie.modularbot.module.BaseModule; import com.jesus_crie.modularbot.module.ModuleManager; -import com.jesus_crie.modularbot_nightconfigwrapper.exception.DirectoryAccessDeniedException; +import com.jesus_crie.modularbot_night_config_wrapper.exception.DirectoryAccessDeniedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,9 +45,9 @@ public NightConfigWrapperModule() { .concurrent(); } - public NightConfigWrapperModule(@Nonnull final String configPath) { + public NightConfigWrapperModule(@Nonnull final String primaryConfigPath) { super(INFO); - primaryConfigBuilder = FileConfig.builder(configPath); + primaryConfigBuilder = FileConfig.builder(primaryConfigPath); } @Override diff --git a/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_nightconfigwrapper/exception/DirectoryAccessDeniedException.java b/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_night_config_wrapper/exception/DirectoryAccessDeniedException.java similarity index 69% rename from ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_nightconfigwrapper/exception/DirectoryAccessDeniedException.java rename to ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_night_config_wrapper/exception/DirectoryAccessDeniedException.java index 8d94308..a4acc4a 100644 --- a/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_nightconfigwrapper/exception/DirectoryAccessDeniedException.java +++ b/ModularBot-NightConfigWrapper/src/main/java/com/jesus_crie/modularbot_night_config_wrapper/exception/DirectoryAccessDeniedException.java @@ -1,4 +1,4 @@ -package com.jesus_crie.modularbot_nightconfigwrapper.exception; +package com.jesus_crie.modularbot_night_config_wrapper.exception; public class DirectoryAccessDeniedException extends RuntimeException { diff --git a/README.md b/README.md index 0eb6533..f7f990d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,21 @@ won't use. It's a v2 because there's been a long pause since v1 and JDA has changed a lot. +1. [Getting Started](#getting-started) +2. [Modules](#modules) + 1. [Available Modules](#available-modules) + 1. [Base](#base) + 2. [Core](#core*) + 3. [Console Logger](#console-logger*) + 4. [Command](#command*) + 5. [Night Config Wrapper](#night-config-wrapper) + 6. [JS Nashorn Support](#js-nashorn-support) + 7. [JS Nashorn Command Support](#js-nashorn-command-support) + 8. [Message Decorator](#message-decorator) + 9. [Audio](#audio) + 10. [Eval](#eval) + 2. [Your Custom Module](#your-custom-module) + ## Getting Started You can download each modules with gradle from maven central. @@ -26,7 +41,7 @@ repositories { } dependencies { - compile 'com.jesus-crie:modularbot-base:2.1.0' + compile 'com.jesus-crie:modularbot-base:2.2.0' } ``` And now you can register commands and start your bot with: @@ -80,7 +95,6 @@ For custom modules, look [here](#your-custom-module). > The modules with a * are included in the [Base](#Base) module #### Base - > *Artifact: `com.jesus-crie:modularbot-base`.* There is no additional code in this module other than the code provided by @@ -89,9 +103,7 @@ the modules [Core](# core*), [Logger](#console-logger*) and [Command](#command* This is basically a shortcut to import these 3 modules in one line. #### Core* - [![Javadocs core](http://www.javadoc.io/badge/com.jesus-crie/modularbot-core.svg?label=javadoc-core)](http://www.javadoc.io/doc/com.jesus-crie/modularbot-core) - > *Artifact: `com.jesus-crie:modularbot-core`.* If you want only the base code without any modules you can use this artifact. @@ -100,9 +112,7 @@ Use it if you want to use another command system or another implementation of SLF4J. It only contains the classes necessary to use JDA and the module manager. #### Console Logger* - [![Javadocs logger](http://www.javadoc.io/badge/com.jesus-crie/modularbot-logger.svg?label=javadoc-logger)](http://www.javadoc.io/doc/com.jesus-crie/modularbot-logger) - > *Artifact: `com.jesus-crie:modularbot-logger`.* Provides an implementation of [SLF4J](https://www.slf4j.org/). @@ -127,9 +137,7 @@ the necessary information about a specific log. You can listen to them by registering a listener using `ModularLogger#addListener`. #### Command* - [![Javadocs command](http://www.javadoc.io/badge/com.jesus-crie/modularbot-command.svg?label=javadoc-command)](http://www.javadoc.io/doc/com.jesus-crie/modularbot-command) - > *Artifact: `com.jesus-crie:modularbot-command`* This module provide a complete command system. Commands that looks like @@ -212,9 +220,7 @@ Finally, you can listen to the success or the failure of a command typed by a user by registering your own `CommandListener` with `CommandModule#addListener`. #### Night Config Wrapper - [![Javadocs config](http://www.javadoc.io/badge/com.jesus-crie/modularbot-night-config-wrapper.svg?label=javadoc-night-config-wrapper)](http://www.javadoc.io/doc/com.jesus-crie/modularbot-night-config-wrapper) - > *Artifact: `com.jesus-crie:modularbot-night-config-wrapper`* This module uses [NightConfig 3.1.1](https://github.com/TheElectronWill/Night-Config) @@ -268,15 +274,133 @@ for the config file located at `./users/michel.json` and loaded by one of the This module is entirely based on [Night Config](https://github.com/TheElectronWill/Night-Config) and I hardly recommend to read its documentation. -#### Audio +#### JS Nashorn support +[![Javadocs nashorn](http://www.javadoc.io/badge/com.jesus-crie/modularbot-nashorn-support.svg?label=javadoc-nashorn-support)](http://www.javadoc.io/doc/com.jesus-crie/modularbot-nashorn-support) +> *Artifact: `com.jesus-crie:modularbot-nashorn-support`* + +This module allows you to load modules in JavaScript using the Nashorn +Script Engine. By default it will look for any `.js` file in the folder +`./scripts/` and try to load them. + +A module in JavaScript looks like this: +```javascript +with (baseImports) { + var LOG = LoggerFactory.getLogger("JS TestModule"); + + var TestModule = Java.extend(BaseJavaScriptModule, { + info: new ModuleInfo("TestModule", "Author", "URL", "1.0", 1), + + onLoad: function(moduleManager, builder) { + LOG.info("Module loaded !"); + }, + onUnload: function () { + LOG.info("Module unloaded !"); + } + }); +} + +function getModule() { + return new TestModule(); +} +``` + +Note that the only requirement is a method called `getModule()` without +arguments that returns a `BaseJsModule` which is a `BaseModule` that allows +an empty constructor and doesn't require a call to the super in +`#onShardsReady()`. + +See [this section](#your-custom-module) for more information about the +custom modules. + +For each script, a header is added that imports some essential classes. +You can found this header [here](./ModularBot-NashornSupport/src/main/resources/script_header.js). +It can be overridden if there is a file called `_script_header.js` in the +scripts folder. + +You can use multiple files for your module if you put them in a subfolder +of `./scripts/` or somewhere else but you need to keep your main file that +contains the `#getModule()` function in the `./scripts/` folder. + +#### JS Nashorn Command Support +[![Javadocs nashorn command](http://www.javadoc.io/badge/com.jesus-crie/modularbot-nashorn-command-support.svg?label=javadoc-nashorn-support)](http://www.javadoc.io/doc/com.jesus-crie/modularbot-nashorn-command-support) +> *Artifact: `com.jesus-crie:modularbot-nashorn-command-support`* + +An extension to the JS module that provide a way to use the command module +in JavaScript. + +This module let you define a `#getCommands()` that returns an array of +`JavaScriptCommand` that will be wrapped into real command objects and +registered. But because of my poor skills in JavaScript you can't use the +annotation system and you need to register your patterns explicitly like +in the example below. Regardless of that, all of the other features are +available. + +```javascript +with (baseImports) { + with (commandImports) { + + /* Module declaration here */ + + function getCommands() { + // Create a typed array + var commands = new JavaScriptCommandArray(1); + commands[0] = testJSCommand; + return commands; + } + + var testJSCommand = JavaScriptCommand.from({ + aliases: ["testjs"], + description: "A demo command in JavaScript", + shortDescription: "A demo command", + accessLevel: AccessLevel.EVERYONE, + options: [Option.FORCE], + + // Create the patterns by hand + patterns: [ + new CommandPattern( + [ + Argument.forString("add"), + Argument.STRING + ], function (event, args, options) { + event.fastReply("You wan to add: " + args[0]); + } + ), + new CommandPattern(function (event, args, options) { + if (options.has("force")) + event.fastReply("Hi, i'm force"); + else event.fastReply("Hi"); + }) + ] + }); + } +} +``` + +Note that this code comes in addition to the module declaration. If a script +doesn't contains a module, its entirely ignored. + +> You can also extends `JavaScriptCommand` but for some reason Nashorn do +not evaluate the arrays correctly and messes up everything, but feel free +to experiment and send me a pull request. + +For convenience you can add these imports to your custom header: +```javascript +var JavaScriptCommandArray = Java.type("com.jesus_crie.modularbot_nashorn_command_support.JavaScriptCommand[]"); + +var commandImports = new JavaImporter(com.jesus_crie.modularbot_nashorn_command_support, + com.jesus_crie.modularbot_command, + com.jesus_crie.modularbot_command.processing); +``` + +#### Message Decorator TODO -#### Eval +#### Audio TODO -#### JS Nashorn support +#### Eval TODO @@ -287,8 +411,8 @@ If you want to create your own module, you can by simply extending > Don't forget to enable it in with `ModularBotBuiler#registerModule()`. You can now implement the methods from `Lifecycle` to allow your module to -be notify when something important happens. Every callback method is -documented in the class. +be notify when something important happens. **Every callback method is +documented in the class.** > **Nothing can prevent a malicious module from stealing your token using reflection. And there are no efficient way to prevent reflection.** diff --git a/build.gradle b/build.gradle index a6d5035..99e6b13 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ allprojects { apply plugin: 'signing' group 'com.jesus-crie' - version '2.1.1' + version '2.2.0' sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -76,7 +76,17 @@ project(':modularbot-command') { project(':modularbot-night-config-wrapper') { ext.name = 'ModularBot - Night Config Wrapper' - project.description = 'A wrapper of https://github.com/TheElectronWill/Night-Config' + project.description = 'A wrapper of https://github.com/TheElectronWill/Night-Config .' +} + +project(':modularbot-nashorn-support') { + ext.name = 'ModularBot - JS Nashorn Support' + project.description = 'Module that can load modules in JavaScript.' +} + +project(':modularbot-nashorn-command-support') { + ext.name = 'ModularBot - JS Nashorn Command Support' + project.description = 'Extension of the JS module that supports the command module.' } subprojects { diff --git a/docs/allclasses-frame.html b/docs/allclasses-frame.html index f9514e1..bc07394 100644 --- a/docs/allclasses-frame.html +++ b/docs/allclasses-frame.html @@ -2,9 +2,9 @@ - + All Classes - + @@ -14,6 +14,7 @@

All Classes