From 41146a74be75fd7770e4087fee5db11fb91fdba1 Mon Sep 17 00:00:00 2001 From: MiniDay <372403923@qq.com> Date: Thu, 8 Aug 2024 07:21:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=81=E8=AE=B8=E4=BD=BF=E7=94=A8Jav?= =?UTF-8?q?aScript=E7=9B=91=E5=90=AC=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 12 +- .../mc/plugin/script/HamsterScriptPlugin.java | 205 +++--------------- .../plugin/script/command/ScriptCommand.java | 119 ++++++++++ .../mc/plugin/script/core/ScriptManager.java | 148 +++++++++++++ .../mc/plugin/script/data/EventCode.java | 33 +++ .../plugin/script/listener/MainListener.java | 33 +++ src/main/resources/config.yml | 3 + src/main/resources/event/example.js | 13 ++ src/main/resources/plugin.yml | 3 + 9 files changed, 379 insertions(+), 190 deletions(-) create mode 100644 src/main/java/cn/hamster3/mc/plugin/script/command/ScriptCommand.java create mode 100644 src/main/java/cn/hamster3/mc/plugin/script/core/ScriptManager.java create mode 100644 src/main/java/cn/hamster3/mc/plugin/script/data/EventCode.java create mode 100644 src/main/java/cn/hamster3/mc/plugin/script/listener/MainListener.java create mode 100644 src/main/resources/event/example.js diff --git a/build.gradle.kts b/build.gradle.kts index 383ca89..ac94b41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,11 +2,10 @@ plugins { id("java") - id("com.github.johnrengelman.shadow") version "8+" } group = "cn.hamster3.mc.plugin" -version = "1.0.6" +version = "1.1.0" description = "为Minecraft服务器导入 Nashorn 引擎来执行 JavaScript 脚本" repositories { @@ -23,9 +22,6 @@ dependencies { annotationProcessor("org.projectlombok:lombok:1.18.30") compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT") - - // https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core - implementation("org.openjdk.nashorn:nashorn-core:15.4") } tasks { @@ -43,9 +39,6 @@ tasks { jar { destinationDirectory = rootProject.layout.buildDirectory } - shadowJar { - destinationDirectory = rootProject.layout.buildDirectory - } withType().configureEach { options.encoding = "UTF-8" } @@ -54,7 +47,4 @@ tasks { from(rootProject.file("LICENSE")) duplicatesStrategy = DuplicatesStrategy.EXCLUDE } - build { - dependsOn(shadowJar) - } } diff --git a/src/main/java/cn/hamster3/mc/plugin/script/HamsterScriptPlugin.java b/src/main/java/cn/hamster3/mc/plugin/script/HamsterScriptPlugin.java index 451272a..67a0b94 100644 --- a/src/main/java/cn/hamster3/mc/plugin/script/HamsterScriptPlugin.java +++ b/src/main/java/cn/hamster3/mc/plugin/script/HamsterScriptPlugin.java @@ -1,208 +1,55 @@ package cn.hamster3.mc.plugin.script; +import cn.hamster3.mc.plugin.script.command.ScriptCommand; +import cn.hamster3.mc.plugin.script.core.ScriptManager; +import cn.hamster3.mc.plugin.script.listener.MainListener; import lombok.Getter; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.RegisteredListener; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.script.Bindings; -import javax.script.Invocable; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; +import java.util.List; -@SuppressWarnings("CallToPrintStackTrace") public class HamsterScriptPlugin extends JavaPlugin { - private static File codeFolder; @Getter - private static ScriptEngine scriptEngine; - @Getter - private static Invocable invocable; + private static HamsterScriptPlugin instance; - private boolean enableEvalCommand; - private Map importClass; + @Override + public void onLoad() { + instance = this; + } @Override public void onEnable() { - reload(); + ScriptManager.init(); + for (HandlerList list : HandlerList.getHandlerLists()) { + list.register(new RegisteredListener( + MainListener.INSTANCE, + (listener, event) -> MainListener.INSTANCE.onEvent(event), + EventPriority.NORMAL, + this, + false + )); + } } - private void reload() { - saveDefaultConfig(); - reloadConfig(); - FileConfiguration config = getConfig(); - enableEvalCommand = config.getBoolean("enable-eval-command", false); - scriptEngine = new ScriptEngineManager(getClassLoader()).getEngineByName("JavaScript"); - invocable = (Invocable) scriptEngine; - - Logger logger = getLogger(); - importClass = new HashMap<>(); - ConfigurationSection importConfig = config.getConfigurationSection("import"); - if (importConfig != null) { - for (String simpleName : importConfig.getKeys(false)) { - String className = importConfig.getString(simpleName); - importClass.put(simpleName, className); - try { - Class clazz = Class.forName(className); - scriptEngine.put(simpleName, clazz); - scriptEngine.eval(String.format("%s = %s.static;", simpleName, simpleName)); - logger.info("已导入 " + className); - } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, "导入 " + className + " 失败:未找到这个类", e); - } catch (Exception e) { - logger.log(Level.WARNING, "导入 " + className + " 失败", e); - } - } - } - - codeFolder = new File(getDataFolder(), "code"); - if (codeFolder.mkdirs()) { - getLogger().info("创建代码文件夹: " + codeFolder.getAbsolutePath()); - try { - Files.copy( - Objects.requireNonNull(getResource("code/example.js")), - new File(codeFolder, "example.js").toPath(), - StandardCopyOption.REPLACE_EXISTING - ); - } catch (IOException ignored) { - } - } + public ClassLoader getPluginClassLoader() { + return getClassLoader(); } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (!sender.hasPermission("hamster.script.admin")) { - sender.sendMessage("§c你没有这个权限"); - return true; - } - if (args.length < 1) { - sender.sendMessage("§c请输入命令"); - return true; - } - switch (args[0]) { - case "run": { - if (args.length < 2) { - sender.sendMessage("§c请输入 JavaScript 文件名称"); - return true; - } - File file = new File(codeFolder, args[1]); - if (!file.exists() && !args[1].endsWith(".js")) { - file = new File(codeFolder, args[1] + ".js"); - } - if (!file.exists()) { - sender.sendMessage("§c未找到 JavaScript 文件: " + file.getAbsolutePath()); - return true; - } - String code; - try { - List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); - code = String.join("\n", lines); - } catch (IOException e) { - sender.sendMessage("§c读取 JavaScript 文件内容 " + file.getAbsolutePath() + " 时出错!"); - e.printStackTrace(); - return true; - } - long start = System.currentTimeMillis(); - try { - Bindings bindings = scriptEngine.createBindings(); - for (String simpleName : importClass.keySet()) { - bindings.put(simpleName, scriptEngine.get(simpleName)); - } - String[] scriptArgs = Arrays.copyOfRange(args, 2, args.length); - bindings.put("sender", sender); - bindings.put("args", scriptArgs); - Object eval = scriptEngine.eval(code, bindings); - long time = System.currentTimeMillis() - start; - sender.sendMessage("§aJavaScript 代码执行完成, 耗时: " + time + " 毫秒"); - sender.sendMessage("§a返回值: §f" + eval); - } catch (Exception e) { - long time = System.currentTimeMillis() - start; - sender.sendMessage("§cJavaScript 代码执行出错, 耗时: " + time + " 毫秒"); - sender.sendMessage("§c异常原因: " + e.getMessage()); - e.printStackTrace(); - } - break; - } - case "eval": { - if (!enableEvalCommand) { - sender.sendMessage("§c当前不允许直接执行 JavaScript"); - return true; - } - if (args.length < 2) { - sender.sendMessage("§c请输入 JavaScript 代码内容"); - return true; - } - StringBuilder builder = new StringBuilder(); - for (int i = 1; i < args.length; i++) { - builder.append(args[i]).append(" "); - } - String code = builder.toString(); - long start = System.currentTimeMillis(); - try { - Bindings bindings = scriptEngine.createBindings(); - for (String simpleName : importClass.keySet()) { - bindings.put(simpleName, scriptEngine.get(simpleName)); - } - bindings.put("sender", sender); - Object eval = scriptEngine.eval(code, bindings); - long time = System.currentTimeMillis() - start; - sender.sendMessage("§aJavaScript 代码执行完成, 耗时: " + time + " 毫秒"); - sender.sendMessage("§a返回值: §f" + eval); - } catch (Exception e) { - long time = System.currentTimeMillis() - start; - sender.sendMessage("§cJavaScript 代码执行出错, 耗时: " + time + " 毫秒"); - sender.sendMessage("§c异常原因: " + e.getMessage()); - e.printStackTrace(); - } - break; - } - case "reload": { - reload(); - sender.sendMessage("§a插件重载完成"); - break; - } - default: { - sender.sendMessage("§c未找到该命令"); - break; - } - } - return true; + return ScriptCommand.INSTANCE.onCommand(sender, command, label, args); } @Nullable @Override public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { - if (args.length == 1) { - List list = new ArrayList<>(); - list.add("run"); - list.add("eval"); - list.add("reload"); - String startWith = args[0].toLowerCase(); - list.removeIf(o -> !o.startsWith(startWith)); - return list; - } - if (args[0].equalsIgnoreCase("run")) { - File[] files = codeFolder.listFiles(); - if (files != null) { - String startWith = args[1].toLowerCase(); - return Arrays.stream(files) - .map(File::getName) - .filter(o -> o.toLowerCase().startsWith(startWith)) - .collect(Collectors.toList()); - } - } - return Collections.emptyList(); + return ScriptCommand.INSTANCE.onTabComplete(sender, command, alias, args); } } diff --git a/src/main/java/cn/hamster3/mc/plugin/script/command/ScriptCommand.java b/src/main/java/cn/hamster3/mc/plugin/script/command/ScriptCommand.java new file mode 100644 index 0000000..833ced7 --- /dev/null +++ b/src/main/java/cn/hamster3/mc/plugin/script/command/ScriptCommand.java @@ -0,0 +1,119 @@ +package cn.hamster3.mc.plugin.script.command; + +import cn.hamster3.mc.plugin.script.HamsterScriptPlugin; +import cn.hamster3.mc.plugin.script.core.ScriptManager; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class ScriptCommand implements TabExecutor { + public static final ScriptCommand INSTANCE = new ScriptCommand(); + + private ScriptCommand() { + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("hamster.script.admin")) { + sender.sendMessage("§c你没有这个权限"); + return true; + } + if (args.length < 1) { + sender.sendMessage("§c请输入命令"); + return true; + } + switch (args[0]) { + case "run": { + if (args.length < 2) { + sender.sendMessage("§c请输入 JavaScript 文件名称"); + return true; + } + File file = new File(ScriptManager.getCodeFolder(), args[1]); + if (!file.exists() && !args[1].endsWith(".js")) { + file = new File(ScriptManager.getCodeFolder(), args[1] + ".js"); + } + if (!file.exists()) { + sender.sendMessage("§c未找到 JavaScript 文件: " + file.getAbsolutePath()); + return true; + } + String code; + try { + List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); + code = String.join("\n", lines); + } catch (IOException e) { + sender.sendMessage("§c读取 JavaScript 文件内容 " + file.getAbsolutePath() + " 时出错!"); + HamsterScriptPlugin.getInstance().getLogger().log(Level.SEVERE, "", e); + return true; + } + String[] scriptArgs = Arrays.copyOfRange(args, 2, args.length); + ScriptManager.eval(sender, code, scriptArgs); + break; + } + case "eval": { + if (!ScriptManager.isEnableEvalCommand()) { + sender.sendMessage("§c当前不允许直接执行 JavaScript"); + return true; + } + if (args.length < 2) { + sender.sendMessage("§c请输入 JavaScript 代码内容"); + return true; + } + StringBuilder builder = new StringBuilder(); + for (int i = 1; i < args.length; i++) { + builder.append(args[i]).append(" "); + } + String code = builder.toString(); + ScriptManager.eval(sender, code); + break; + } + case "reload": { + ScriptManager.init(); + sender.sendMessage("§a插件重载完成"); + break; + } + default: { + sender.sendMessage("§c未找到该命令"); + break; + } + } + return true; + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 1) { + ArrayList list = new ArrayList<>(); + list.add("run"); + list.add("eval"); + list.add("reload"); + String startWith = args[0].toLowerCase(); + list.removeIf(o -> !o.toLowerCase().startsWith(startWith)); + return list; + } + if (args[0].equalsIgnoreCase("run")) { + File[] files = ScriptManager.getCodeFolder().listFiles(); + if (files != null) { + String startWith = args[1].toLowerCase(); + return Arrays.stream(files) + .map(File::getName) + .filter(o -> o.toLowerCase().startsWith(startWith)) + .collect(Collectors.toList()); + } + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/cn/hamster3/mc/plugin/script/core/ScriptManager.java b/src/main/java/cn/hamster3/mc/plugin/script/core/ScriptManager.java new file mode 100644 index 0000000..b868b7b --- /dev/null +++ b/src/main/java/cn/hamster3/mc/plugin/script/core/ScriptManager.java @@ -0,0 +1,148 @@ +package cn.hamster3.mc.plugin.script.core; + +import cn.hamster3.mc.plugin.script.HamsterScriptPlugin; +import cn.hamster3.mc.plugin.script.data.EventCode; +import lombok.Getter; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; + +import javax.script.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ScriptManager { + @Getter + private static File codeFolder; + @Getter + private static ScriptEngine scriptEngine; + @Getter + private static Invocable invocable; + + @Getter + private static boolean enableEvalCommand; + @Getter + private static boolean enableEventCode; + @Getter + private static Map importClass; + + @Getter + private static List eventCodes; + + public static void init() { + HamsterScriptPlugin plugin = HamsterScriptPlugin.getInstance(); + plugin.saveDefaultConfig(); + plugin.reloadConfig(); + + FileConfiguration config = plugin.getConfig(); + + enableEvalCommand = config.getBoolean("enable-eval-command", false); + enableEventCode = config.getBoolean("enable-event-code", false); + scriptEngine = new ScriptEngineManager(plugin.getPluginClassLoader()).getEngineByName("JavaScript"); + invocable = (Invocable) scriptEngine; + + Logger logger = plugin.getLogger(); + importClass = new HashMap<>(); + ConfigurationSection importConfig = config.getConfigurationSection("import"); + if (importConfig != null) { + for (String simpleName : importConfig.getKeys(false)) { + String className = importConfig.getString(simpleName); + importClass.put(simpleName, className); + try { + Class clazz = Class.forName(className); + scriptEngine.put(simpleName, clazz); + scriptEngine.eval(String.format("%s = %s.static;", simpleName, simpleName)); + logger.info("已导入 " + className); + } catch (ClassNotFoundException e) { + logger.log(Level.WARNING, "导入 " + className + " 失败:未找到这个类", e); + } catch (Exception e) { + logger.log(Level.WARNING, "导入 " + className + " 失败", e); + } + } + } + + codeFolder = new File(plugin.getDataFolder(), "code"); + if (codeFolder.mkdirs()) { + plugin.getLogger().info("创建代码文件夹: " + codeFolder.getAbsolutePath()); + try { + Files.copy( + Objects.requireNonNull(plugin.getResource("code/example.js")), + new File(codeFolder, "example.js").toPath() + ); + } catch (IOException e) { + HamsterScriptPlugin.getInstance().getLogger().log(Level.SEVERE, "", e); + } + } + + File eventFolder = new File(plugin.getDataFolder(), "event"); + if (eventFolder.mkdirs()) { + plugin.getLogger().info("创建事件文件夹: " + codeFolder.getAbsolutePath()); + try { + Files.copy( + Objects.requireNonNull(plugin.getResource("event/example.js")), + new File(eventFolder, "example.js").toPath() + ); + } catch (IOException e) { + HamsterScriptPlugin.getInstance().getLogger().log(Level.SEVERE, "", e); + } + } + eventCodes = loadEventCodes(eventFolder); + } + + private static List loadEventCodes(@NotNull File file) { + ArrayList list = new ArrayList<>(); + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File subFile : files) { + List loaded = loadEventCodes(subFile); + list.addAll(loaded); + } + } + return list; + } + try { + EventCode code = new EventCode(file); + list.add(code); + } catch (Exception e) { + HamsterScriptPlugin.getInstance().getLogger().log(Level.WARNING, "加载事件代码 " + file.getAbsolutePath() + " 失败", e); + } + return list; + } + + public static Bindings createBindings() { + Bindings bindings = ScriptManager.getScriptEngine().createBindings(); + for (String simpleName : importClass.keySet()) { + bindings.put(simpleName, ScriptManager.getScriptEngine().get(simpleName)); + } + return bindings; + } + + public static Object eval(@NotNull String code) throws ScriptException { + Bindings bindings = createBindings(); + return ScriptManager.getScriptEngine().eval(code, bindings); + } + + public static void eval(@NotNull CommandSender sender, @NotNull String code, @NotNull String... args) { + long start = System.currentTimeMillis(); + try { + Bindings bindings = createBindings(); + bindings.put("sender", sender); + bindings.put("args", args); + Object eval = ScriptManager.getScriptEngine().eval(code, bindings); + long time = System.currentTimeMillis() - start; + sender.sendMessage("§aJavaScript 代码执行完成, 耗时: " + time + " 毫秒"); + sender.sendMessage("§a返回值: §f" + eval); + } catch (Exception e) { + long time = System.currentTimeMillis() - start; + sender.sendMessage("§cJavaScript 代码执行出错, 耗时: " + time + " 毫秒"); + sender.sendMessage("§c异常原因: " + e.getMessage()); + HamsterScriptPlugin.getInstance().getLogger().log(Level.SEVERE, "", e); + } + } +} diff --git a/src/main/java/cn/hamster3/mc/plugin/script/data/EventCode.java b/src/main/java/cn/hamster3/mc/plugin/script/data/EventCode.java new file mode 100644 index 0000000..bc1f5c5 --- /dev/null +++ b/src/main/java/cn/hamster3/mc/plugin/script/data/EventCode.java @@ -0,0 +1,33 @@ +package cn.hamster3.mc.plugin.script.data; + +import cn.hamster3.mc.plugin.script.core.ScriptManager; +import lombok.Getter; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; + +import javax.script.ScriptException; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +@Getter +public class EventCode { + @NotNull + private final String id; + @NotNull + private final Class clazz; + @NotNull + private final Object object; + + public EventCode(@NotNull File file) throws IOException, ScriptException, NoSuchMethodException, ClassNotFoundException { + String code = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + object = ScriptManager.eval(code); + id = ScriptManager.getInvocable().invokeMethod(object, "getID").toString(); + clazz = Class.forName(ScriptManager.getInvocable().invokeMethod(object, "getEventType").toString()); + } + + public void invoke(@NotNull Event event) throws ScriptException, NoSuchMethodException { + ScriptManager.getInvocable().invokeMethod(object, "invoke", event); + } +} diff --git a/src/main/java/cn/hamster3/mc/plugin/script/listener/MainListener.java b/src/main/java/cn/hamster3/mc/plugin/script/listener/MainListener.java new file mode 100644 index 0000000..d82f52c --- /dev/null +++ b/src/main/java/cn/hamster3/mc/plugin/script/listener/MainListener.java @@ -0,0 +1,33 @@ +package cn.hamster3.mc.plugin.script.listener; + +import cn.hamster3.mc.plugin.script.HamsterScriptPlugin; +import cn.hamster3.mc.plugin.script.core.ScriptManager; +import cn.hamster3.mc.plugin.script.data.EventCode; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.util.logging.Level; + +public class MainListener implements Listener { + public static final MainListener INSTANCE = new MainListener(); + + private MainListener() { + } + + @EventHandler(ignoreCancelled = true) + public void onEvent(Event event) { + if (!ScriptManager.isEnableEventCode()) { + return; + } + for (EventCode code : ScriptManager.getEventCodes()) { + if (code.getClazz().isAssignableFrom(event.getClass())) { + try { + code.invoke(event); + } catch (Exception e) { + HamsterScriptPlugin.getInstance().getLogger().log(Level.WARNING, "处理事件代码 " + code.getId() + " 失败", e); + } + } + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9004113..cc00094 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,6 +1,9 @@ # 是否允许通过指令执行 JavaScript 代码 enable-eval-command: false +# 是否启用事件监听执行代码 +enable-event-code: false + # 导入的 Java 代码 import: Math: java.lang.Math diff --git a/src/main/resources/event/example.js b/src/main/resources/event/example.js new file mode 100644 index 0000000..35060d0 --- /dev/null +++ b/src/main/resources/event/example.js @@ -0,0 +1,13 @@ +(function () { + return { + getID: function () { + return "test"; + }, + getEventType: function () { + return "org.bukkit.event.player.AsyncPlayerChatEvent"; + }, + invoke: function (event) { + print(event.getPlayer().getName() + " says: " + event.getMessage()); + }, + }; +})(); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 310f97d..a3a68e3 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -18,6 +18,9 @@ Plugin: # HamsterScript 需要访问其他插件的类路径 join-classpath: true +libraries: + - org.openjdk.nashorn:nashorn-core:15.4 + commands: hamster-script: aliases: [ hscript, scripts, script ]