Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f35b1e64d | |||
c4c09fcbb5 | |||
cfbed99ece | |||
8c6af8e95e | |||
3dab447178 | |||
fe2e658c50 | |||
41146a74be | |||
f2ed45b524 | |||
db812c13b5 | |||
8d7667f473 | |||
8ea030205e | |||
9900f0a014 | |||
eae09072ad | |||
c628a269f4 |
25
.gitea/workflows/main.yaml
Normal file
25
.gitea/workflows/main.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Publish Project
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: 21
|
||||||
|
distribution: temurin
|
||||||
|
cache: gradle
|
||||||
|
- name: Build Project
|
||||||
|
run: chmod +x gradlew && ./gradlew build --console plain --no-daemon
|
||||||
|
- name: Publish to Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: build/*.jar
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 MiniDay
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
24
README.md
24
README.md
@@ -1,6 +1,7 @@
|
|||||||
# hamster-script
|
# HamsterScript
|
||||||
|
|
||||||
为Minecraft服务器导入 [Nashorn](https://github.com/openjdk/nashorn) 引擎来执行 JavaScript 脚本
|
HamsterScript 通过为 Minecraft 服务器导入 Nashorn 引擎,来允许用户执行 JavaScript 脚本。
|
||||||
|
这个插件主要用于提供调试功能。
|
||||||
|
|
||||||
# 手动构建
|
# 手动构建
|
||||||
|
|
||||||
@@ -9,25 +10,22 @@
|
|||||||
3. 命令行窗口中执行`./gradlew clean build`
|
3. 命令行窗口中执行`./gradlew clean build`
|
||||||
4. 构建成品在 `build` 文件夹
|
4. 构建成品在 `build` 文件夹
|
||||||
|
|
||||||
也可访问我的 [Jenkins网站](https://jenkins.airgame.net/job/opensource/job/hamster-script/) 获取最新版
|
|
||||||
|
|
||||||
# 指令
|
# 指令
|
||||||
|
|
||||||
> JavaScript 代码执行时,执行命令的对象(玩家或控制台)会作为 sender 变量传入
|
> JavaScript 代码执行时,执行命令的对象(玩家或控制台)会作为 sender 变量传入
|
||||||
|
|
||||||
| 指令 | 权限 | 描述 |
|
| 指令 | 权限 | 描述 |
|
||||||
|:-------------------|:---------------------|:------------------------|
|
|:--------------------|:---------------------|:------------------------|
|
||||||
| script eval {脚本内容} | hamster.script.admin | 直接执行 JavaScript 脚本 |
|
| /script eval {脚本内容} | hamster.script.admin | 直接执行 JavaScript 脚本 |
|
||||||
| script run {脚本文件} | hamster.script.admin | 执行文件中的 JavaScript 代码 |
|
| /script run {脚本文件} | hamster.script.admin | 执行文件中的 JavaScript 代码 |
|
||||||
| script reload | hamster.script.admin | 重载插件并重设 JavaScript 引擎环境 |
|
| /script reload | hamster.script.admin | 重载插件并重设 JavaScript 引擎环境 |
|
||||||
|
|
||||||
# 注意事项
|
# 注意事项
|
||||||
|
|
||||||
默认情况下禁止使用 `/script eval {脚本内容}` 指令。
|
默认情况下禁止使用 `/script eval {脚本内容}` 指令。
|
||||||
如有需要,可以在 `config.yml` 中将 `enable-eval-command` 设置为 `true` 并重载插件以启用该子指令。
|
如有需要,可以在 `config.yml` 中将 `enable-eval-command` 设置为 `true` 并重载插件以启用该子指令。
|
||||||
|
|
||||||
# nashorn 学习资料
|
# Nashorn 学习资料
|
||||||
|
|
||||||
- [介绍 Nashorn —— Java 8 JavaScript 引擎](https://mouse0w0.github.io/2018/12/02/Introduction-to-Nashorn/)
|
- [介绍 Nashorn —— Java 8 JavaScript 引擎](https://mouse0w0.github.io/2018/12/02/Introduction-to-Nashorn/)
|
||||||
- [Oracle Nashorn:面向 JVM 的下一代 JavaScript 引擎
|
- [Oracle Nashorn:面向 JVM 的下一代 JavaScript 引擎](https://www.oracle.com/technical-resources/articles/java/jf14-nashorn.html)
|
||||||
](https://www.oracle.com/technical-resources/articles/java/jf14-nashorn.html)
|
|
||||||
|
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("java")
|
id("java")
|
||||||
id("com.github.johnrengelman.shadow") version "8+"
|
}
|
||||||
|
base {
|
||||||
|
archivesName = "HamsterScript"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "cn.hamster3.mc.plugin"
|
group = "cn.hamster3.mc.plugin"
|
||||||
version = "1.0.6"
|
version = "1.1.0"
|
||||||
description = "为Minecraft服务器导入 Nashorn 引擎来执行 JavaScript 脚本"
|
description = "为 Minecraft 服务器导入 Nashorn 引擎来执行 JavaScript 脚本"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
@@ -23,48 +25,27 @@ dependencies {
|
|||||||
annotationProcessor("org.projectlombok:lombok:1.18.30")
|
annotationProcessor("org.projectlombok:lombok:1.18.30")
|
||||||
|
|
||||||
compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT")
|
compileOnly("org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT")
|
||||||
|
}
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core
|
java {
|
||||||
implementation("org.openjdk.nashorn:nashorn-core:15.4")
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
withSourcesJar()
|
|
||||||
}
|
|
||||||
processResources {
|
processResources {
|
||||||
filesMatching("plugin.yml") {
|
filesMatching("plugin.yml") {
|
||||||
expand(project.properties)
|
expand(project.properties)
|
||||||
}
|
}
|
||||||
val map = mutableMapOf<String, String>()
|
|
||||||
map["BUILD_ID"] = System.getenv().getOrDefault("BUILD_ID", "DEV")
|
|
||||||
map["BUILD_NUMBER"] = System.getenv().getOrDefault("BUILD_NUMBER", "DEV")
|
|
||||||
map["BUILD_DISPLAY_NAME"] = System.getenv().getOrDefault("BUILD_DISPLAY_NAME", "DEV")
|
|
||||||
map["JOB_URL"] = System.getenv().getOrDefault("JOB_URL", "DEV")
|
|
||||||
map["BUILD_URL"] = System.getenv().getOrDefault("BUILD_URL", "DEV")
|
|
||||||
map["GIT_COMMIT"] = System.getenv().getOrDefault("GIT_COMMIT", "DEV")
|
|
||||||
filesMatching("jenkins.yml") {
|
|
||||||
expand(map)
|
|
||||||
}
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
jar {
|
withType<JavaCompile> {
|
||||||
destinationDirectory = rootProject.layout.buildDirectory
|
|
||||||
}
|
|
||||||
shadowJar {
|
|
||||||
destinationDirectory = rootProject.layout.buildDirectory
|
|
||||||
}
|
|
||||||
withType<JavaCompile>().configureEach {
|
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
}
|
}
|
||||||
withType<Jar>().configureEach {
|
withType<Jar> {
|
||||||
archiveBaseName = "HamsterScript"
|
|
||||||
from(rootProject.file("LICENSE"))
|
from(rootProject.file("LICENSE"))
|
||||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
destinationDirectory = rootProject.layout.buildDirectory
|
||||||
build {
|
|
||||||
dependsOn(shadowJar)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,2 +1 @@
|
|||||||
rootProject.name = "hamster-script"
|
rootProject.name = "hamster-script"
|
||||||
|
|
||||||
|
@@ -1,205 +1,55 @@
|
|||||||
package cn.hamster3.mc.plugin.script;
|
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 lombok.Getter;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.configuration.ConfigurationSection;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.plugin.RegisteredListener;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.script.Bindings;
|
import java.util.List;
|
||||||
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.stream.Collectors;
|
|
||||||
|
|
||||||
@SuppressWarnings("CallToPrintStackTrace")
|
|
||||||
public class HamsterScriptPlugin extends JavaPlugin {
|
public class HamsterScriptPlugin extends JavaPlugin {
|
||||||
private static File codeFolder;
|
|
||||||
@Getter
|
@Getter
|
||||||
private static ScriptEngine engine;
|
private static HamsterScriptPlugin instance;
|
||||||
@Getter
|
|
||||||
private static Invocable invocable;
|
|
||||||
|
|
||||||
private boolean enableEvalCommand;
|
@Override
|
||||||
private Map<String, String> importClass;
|
public void onLoad() {
|
||||||
|
instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
reload();
|
ScriptManager.init();
|
||||||
codeFolder = new File(getDataFolder(), "code");
|
for (HandlerList list : HandlerList.getHandlerLists()) {
|
||||||
if (codeFolder.mkdirs()) {
|
list.register(new RegisteredListener(
|
||||||
getLogger().info("创建代码文件夹: " + codeFolder.getAbsolutePath());
|
MainListener.INSTANCE,
|
||||||
try {
|
(listener, event) -> MainListener.INSTANCE.onEvent(event),
|
||||||
Files.copy(
|
EventPriority.NORMAL,
|
||||||
Objects.requireNonNull(getResource("code/example.js")),
|
this,
|
||||||
new File(codeFolder, "example.js").toPath(),
|
false
|
||||||
StandardCopyOption.REPLACE_EXISTING
|
));
|
||||||
);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reload() {
|
public ClassLoader getPluginClassLoader() {
|
||||||
saveDefaultConfig();
|
return getClassLoader();
|
||||||
reloadConfig();
|
|
||||||
FileConfiguration config = getConfig();
|
|
||||||
enableEvalCommand = config.getBoolean("enable-eval-command", false);
|
|
||||||
engine = new ScriptEngineManager(getClassLoader()).getEngineByName("JavaScript");
|
|
||||||
invocable = (Invocable) engine;
|
|
||||||
|
|
||||||
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);
|
|
||||||
engine.put(simpleName, clazz);
|
|
||||||
engine.eval(String.format("%s = %s.static;", simpleName, simpleName));
|
|
||||||
getLogger().info("已导入 " + className);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
getLogger().warning("导入 " + className + " 失败:未找到这个类");
|
|
||||||
} catch (Exception e) {
|
|
||||||
getLogger().warning("导入 " + className + " 失败");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
if (!sender.hasPermission("hamster.script.admin")) {
|
return ScriptCommand.INSTANCE.onCommand(sender, command, label, args);
|
||||||
sender.sendMessage("§c你没有这个权限");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (args.length < 1) {
|
|
||||||
sender.sendMessage("§c请输入命令");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
switch (args[0]) {
|
|
||||||
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 = engine.createBindings();
|
|
||||||
for (String simpleName : importClass.keySet()) {
|
|
||||||
bindings.put(simpleName, engine.get(simpleName));
|
|
||||||
}
|
|
||||||
bindings.put("sender", sender);
|
|
||||||
Object eval = engine.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 "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<String> 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 = engine.createBindings();
|
|
||||||
for (String simpleName : importClass.keySet()) {
|
|
||||||
bindings.put(simpleName, engine.get(simpleName));
|
|
||||||
}
|
|
||||||
String[] scriptArgs = Arrays.copyOfRange(args, 2, args.length);
|
|
||||||
bindings.put("sender", sender);
|
|
||||||
bindings.put("args", scriptArgs);
|
|
||||||
Object eval = engine.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
|
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
|
||||||
if (args.length == 1) {
|
return ScriptCommand.INSTANCE.onTabComplete(sender, command, alias, args);
|
||||||
List<String> list = new ArrayList<>();
|
|
||||||
list.add("eval");
|
|
||||||
list.add("run");
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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<String> 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<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||||
|
if (args.length == 1) {
|
||||||
|
ArrayList<String> 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();
|
||||||
|
}
|
||||||
|
}
|
@@ -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<String, String> importClass;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static List<EventCode> 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<EventCode> loadEventCodes(@NotNull File file) {
|
||||||
|
ArrayList<EventCode> list = new ArrayList<>();
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
File[] files = file.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File subFile : files) {
|
||||||
|
List<EventCode> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,19 @@
|
|||||||
# 是否允许通过指令执行 JavaScript 代码
|
# 是否允许通过指令执行 JavaScript 代码
|
||||||
enable-eval-command: false
|
enable-eval-command: false
|
||||||
|
|
||||||
|
# 是否启用事件监听执行代码
|
||||||
|
enable-event-code: false
|
||||||
|
|
||||||
# 导入的 Java 代码
|
# 导入的 Java 代码
|
||||||
import:
|
import:
|
||||||
Math: java.lang.Math
|
Math: java.lang.Math
|
||||||
String: java.lang.String
|
String: java.lang.String
|
||||||
|
|
||||||
UUID: java.util.UUID
|
UUID: java.util.UUID
|
||||||
Base64: java.util.Base64
|
Base64: java.util.Base64
|
||||||
|
ArrayList: java.util.ArrayList
|
||||||
|
HashMap: java.util.HashMap
|
||||||
|
|
||||||
Bukkit: org.bukkit.Bukkit
|
Bukkit: org.bukkit.Bukkit
|
||||||
Material: org.bukkit.Material
|
Material: org.bukkit.Material
|
||||||
ItemStack: org.bukkit.inventory.ItemStack
|
ItemStack: org.bukkit.inventory.ItemStack
|
||||||
|
13
src/main/resources/event/example.js
Normal file
13
src/main/resources/event/example.js
Normal file
@@ -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());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
@@ -2,19 +2,25 @@ name: HamsterScript
|
|||||||
main: cn.hamster3.mc.plugin.script.HamsterScriptPlugin
|
main: cn.hamster3.mc.plugin.script.HamsterScriptPlugin
|
||||||
version: ${version}
|
version: ${version}
|
||||||
api-version: 1.13
|
api-version: 1.13
|
||||||
description: ${description}
|
|
||||||
author: MiniDay
|
|
||||||
|
|
||||||
CHECK_TYPE: GITEA_RELEASES
|
author: MiniDay
|
||||||
GIT_BASE_URL: https://git.airgame.net
|
description: ${description}
|
||||||
GIT_REPO: MiniDay/hamster-script
|
website: https://git.airgame.net/MiniDay/hamster-script/
|
||||||
GIT_TOKEN: a44a69a4d1b8601bf6091403247759cd28764d5e
|
|
||||||
DOWNLOAD_URL: https://jenkins.airgame.net/job/opensource/job/hamster-script/
|
UPDATE_CHECKER:
|
||||||
|
VERSION: ${version}
|
||||||
|
CHECK_TYPE: GITEA_RELEASES
|
||||||
|
GIT_BASE_URL: https://git.airgame.net
|
||||||
|
GIT_REPO: MiniDay/hamster-script
|
||||||
|
DOWNLOAD_URL: https://git.airgame.net/MiniDay/hamster-script/releases
|
||||||
|
|
||||||
Plugin:
|
Plugin:
|
||||||
# HamsterScript 需要访问其他插件的类路径
|
# HamsterScript 需要访问其他插件的类路径
|
||||||
join-classpath: true
|
join-classpath: true
|
||||||
|
|
||||||
|
libraries:
|
||||||
|
- org.openjdk.nashorn:nashorn-core:15.4
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
hamster-script:
|
hamster-script:
|
||||||
aliases: [ hscript, scripts, script ]
|
aliases: [ hscript, scripts, script ]
|
||||||
|
Reference in New Issue
Block a user