diff --git a/README.md b/README.md index 3599114..0a53932 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,10 @@ 一个为第三方客户端开放GC命令执行接口的插件 ## 服务端安装 + 1. 在 [Release](https://github.com/jie65535/gc-opencommand-plugin/releases) 下载 `jar` 2. 放入 `plugins` 文件夹即可 + > 注意,如果出现以下错误: > ```log > INFO:PluginManager Enabling plugin: opencommand-plugin @@ -17,24 +19,28 @@ > 请使用v1.2.1版本插件,因为该报错表示你的服务端是旧版! ## 控制台连接 + 1. 首次启动时,会在 `plugins` 目录下生成一个 `opencommand-plugin` 目录,打开并编辑 `config.json` 2. 设置 `consoleToken` 的值为你的连接秘钥,建议使用至少32字符的长随机字符串。 3. 重新启动服务端即可生效配置 4. 在客户端中选择控制台身份,并填写你的 `consoleToken` 即可以控制台身份运行指令 ## 构建说明 + 1. 克隆仓库 2. 在目录下新建 `lib` 目录 3. 将 `grasscutter-1.1.x-dev.jar` 放入 `lib` 目录 4. `gradle build` ## 玩家使用流程 + 1. 在客户端中填写服务地址,确认是否支持 2. 填写UID,发送验证码 3. 将游戏内收到的**4位整数验证码**填入客户端校验 4. 享受便利! ## 客户端请求流程 + 1. `ping` 确认是否支持 `opencommand` 插件 2. `sendCode` 向指定玩家发送验证码(1分钟内不允许重发),保存返回的 `token` 3. 使用 `token` 和**4位整数验证码**发送 `verify` 校验 @@ -43,6 +49,7 @@ --- ## `config.json` + ```json { // 控制台连接令牌 @@ -58,114 +65,210 @@ // 多服务器通信密钥 "socketToken": "", // 多服务器Dispatch地址 - "socketHost": "127.0.0.1" + "socketHost": "127.0.0.1", + // 多服务器显示名称 + "socketDisplayName": "" } ``` - ## API `/opencommand/api` + 示例 + ``` https://127.0.0.1/opencommand/api ``` ### Request 请求 + ```java public final class JsonRequest { public String token = ""; public String action = ""; + public String server = ""; public Object data = null; } ``` ### Response 响应 + ```java public final class JsonResponse { public int retcode = 200; - public String message = "success"; + public String message = "Success"; public Object data; } ``` ### Actions 动作 + #### `测试连接` ##### Request -| 请求参数 | 请求数据 | 类型 | -| ------- | --------- | ------ | -| action | `ping` |`String`| +| 请求参数 | 请求数据 | 类型 | +|--------|--------|----------| +| action | `ping` | `String` | ##### Response -| 返回参数 | 返回数据 | 类型 | -| ------- | --------- | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `null` |`null` | +| 返回参数 | 返回数据 | 类型 | +|---------|-----------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `null` | `null` | + + +#### `获取在线玩家` + +##### Request + +| 请求参数 | 请求数据 | 类型 | +|--------|----------|----------| +| action | `online` | `String` | + +##### Response + +| 返回参数 | 返回数据 | 类型 | +|---------|---------------------------------|--------------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `{"count": 0, playerList": []}` | `JsonObject` | #### `发送验证码` ##### Request -| 请求参数 | 请求数据 | 类型 | -| ------- | --------- | ------ | -| action | `sendCode`|`String`| -| data | `uid` |`Int` | +| 请求参数 | 请求数据 | 类型 | +|--------|------------|----------| +| action | `sendCode` | `String` | +| data | `uid` | `Int` | ##### Response -| 返回参数 | 返回数据 | 类型 | -| ------- | --------- | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `token` |`String`| - +| 返回参数 | 返回数据 | 类型 | +|---------|-----------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `token` | `String` | #### `验证验证码` ##### Request -| 请求参数 | 请求数据 | 类型 | -| ------- | --------- | ------ | -| action | `verify` |`String`| -| token | `token` |`String`| -| data | `code` |`Int` | +| 请求参数 | 请求数据 | 类型 | +|--------|----------|----------| +| action | `verify` | `String` | +| token | `token` | `String` | +| data | `code` | `Int` | ##### Response 成功 -| 返回参数 | 返回数据 | 类型 | -| ------- | --------- | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `null` | `null` | +| 返回参数 | 返回数据 | 类型 | +|---------|-----------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `null` | `null` | 失败 -| 返回参数 | 返回数据 | 类型 | -| ------- | -------------------- | ------ | -| retcode | `400` |`String`| -| message | `Verification failed`|`String`| -| data | `null` |`null` | +| 返回参数 | 返回数据 | 类型 | +|---------|-----------------------|----------| +| retcode | `400` | `Int` | +| message | `Verification failed` | `String` | +| data | `null` | `null` | #### `执行命令` ##### Request -| 请求参数 | 请求数据 | 类型 | -| ------- | ----------- | ------ | -| action | `command` |`String`| -| token | `token` |`String`| -| data | `command` |`String`| +| 请求参数 | 请求数据 | 类型 | +|--------|-----------|----------| +| action | `command` | `String` | +| token | `token` | `String` | +| data | `command` | `String` | ##### Response 成功 -| 返回参数 | 返回数据 | 类型 | -| ------- | ---------------- | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `Command return` |`String`| \ No newline at end of file +| 返回参数 | 返回数据 | 类型 | +|---------|------------------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `Command return` | `String` | + +### 执行控制台命令 + +#### `获取运行模式` + +##### Request + +| 请求参数 | 请求数据 | 类型 | +|--------|-----------|----------| +| action | `runmode` | `String` | +| token | `token` | `String` | + +##### Response + +成功 + +| 返回参数 | 返回数据 | 类型 | +|---------|-----------------------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `1 (多服务器) / 0 (单服务器)` | `Int` | + +#### `获取多服务器列表` + +##### Request + +| 请求参数 | 请求数据 | 类型 | +|--------|----------|----------| +| action | `server` | `String` | +| token | `token` | `String` | + +##### Response + +成功 + +| 返回参数 | 返回数据 | 类型 | +|---------|-----------|--------------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `{}` | `JsonObject` | + +```json +{ + "retcode": 200, + "message": "success", + "data": { + // 服务器 UUID + "13d82d0d-c7d9-47dd-830c-76588006ef6e": "2.8.0 服务器", + "e6b83224-a761-4023-be57-e054c5bb823a": "2.8.0 开发服务器" + } +} +``` + +#### `执行命令` + +##### Request + +| 请求参数 | 请求数据 | 类型 | +|--------|-----------|----------| +| action | `command` | `String` | +| token | `token` | `String` | +| server | `UUID` | `String` | +| data | `command` | `String` | + +##### Response + +成功 + +| 返回参数 | 返回数据 | 类型 | +|---------|------------------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `Command return` | `String` | \ No newline at end of file diff --git a/README_en-US.md b/README_en-US.md index a1369db..afda1ab 100644 --- a/README_en-US.md +++ b/README_en-US.md @@ -60,6 +60,7 @@ https://127.0.0.1/opencommand/api public final class JsonRequest { public String token = ""; public String action = ""; + public Seting server = ""; public Object data = null; } ``` @@ -68,7 +69,7 @@ public final class JsonRequest { ```java public final class JsonResponse { public int retcode = 200; - public String message = "success"; + public String message = "Success"; public Object data; } ``` @@ -78,79 +79,168 @@ public final class JsonResponse { ##### Request -| Request | Request data | type | -| ------- | -------------- | ------ | -| action | `ping` |`String`| +| Request | Request data | type | +|---------|--------------|----------| +| action | `ping` | `String` | ##### Response -| Response | Response data | type | -| -------- | --------------- | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `null` |`null` | +| Response | Response data | type | +|----------|---------------|----------| +| retcode | `200` | `String` | +| message | `Success` | `String` | +| data | `null` | `null` | + +#### `Get online players` + +##### Request + +| Request | Request data | type | +|---------|--------------|----------| +| action | `online` | `String` | + +##### Response + +| Response | Response data | type | +|----------|---------------------------------|--------------| +| retcode | `200` | `String` | +| message | `Success` | `String` | +| data | `{"count": 0, playerList": []}` | `JsonObject` | #### `Send code` ##### Request -| Request | Request data | type | -| ------- | -------------- | ------ | -| action | `sendCode` |`String`| -| data | `uid` |`Int` | +| Request | Request data | type | +|---------|--------------|----------| +| action | `sendCode` | `String` | +| data | `uid` | `Int` | ##### Response -| Response | Response data | type | -| -------- | --------------- | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `token` |`String`| +| Response | Response data | type | +|----------|---------------|----------| +| retcode | `200` | `String` | +| message | `Success` | `String` | +| data | `token` | `String` | #### `Verify code` ##### Request -| Request | Request data | type | -| ------- | -------------- | ------ | -| action | `verify` |`String`| -| token | `token` |`String`| -| data | `code` |`Int` | +| Request | Request data | type | +|---------|--------------|----------| +| action | `verify` | `String` | +| token | `token` | `String` | +| data | `code` | `Int` | ##### Response Success -| Response | Response data | type | -| -------- | -------------- | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `null` | `null` | +| Response | Response data | type | +|----------|---------------|----------| +| retcode | `200` | `String` | +| message | `Success` | `String` | +| data | `null` | `null` | Failed -| Response | Response data | type | -| -------- | -------------------- | ------ | -| retcode | `400` |`String`| -| message | `Verification failed`|`String`| -| data | `null` |`null` | +| Response | Response data | type | +|----------|-----------------------|----------| +| retcode | `400` | `String` | +| message | `Verification failed` | `String` | +| data | `null` | `null` | #### `Run command` ##### Request -| Request | Request data | type | -| ------- | -------------- | ------ | -| action | `command` |`String`| -| token | `token` |`String`| -| data | `command` |`String`| +| Request | Request data | type | +|---------|--------------|----------| +| action | `command` | `String` | +| token | `token` | `String` | +| data | `command` | `String` | ##### Response Success -| Response | Response data | type | -| -------- | ------------------ | ------ | -| retcode | `200` |`String`| -| message | `success` |`String`| -| data | `Command return` |`String`| \ No newline at end of file +| Response | Response data | type | +|----------|------------------|----------| +| retcode | `200` | `String` | +| message | `Success` | `String` | +| data | `Command return` | `String` | + +### Run console command + +#### `Get run mode` + +##### Request + +| Request | Request data | Type | +|---------|--------------|----------| +| action | `runmode` | `String` | +| token | `token` | `String` | + +##### Response + +Success + +| Request | Response data | Type | +|---------|----------------------------------------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `1 (Multi server) / 0 (Single server)` | `Int` | + +#### `Get mulit server list` + +##### Request + +| Request | Request data | Type | +|---------|--------------|----------| +| action | `server` | `String` | +| token | `token` | `String` | + +##### Response + +Success + +| Request | Response data | Type | +|---------|---------------|--------------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `{}` | `JsonObject` | + +```json +{ + "retcode": 200, + "message": "success", + "data": { + // Server UUID + "13d82d0d-c7d9-47dd-830c-76588006ef6e": "2.8.0 Server", + "e6b83224-a761-4023-be57-e054c5bb823a": "2.8.0 Dev server" + } +} +``` + +#### `Run command` + +##### Request + +| Request | Request data | Type | +|---------|--------------|----------| +| action | `command` | `String` | +| token | `token` | `String` | +| server | `UUID` | `String` | +| data | `command` | `String` | + +##### Response + +Success + +| Request | Response data | Type | +|---------|------------------|----------| +| retcode | `200` | `Int` | +| message | `Success` | `String` | +| data | `Command return` | `String` | \ No newline at end of file diff --git a/src/main/java/com/github/jie65535/opencommand/EventListeners.java b/src/main/java/com/github/jie65535/opencommand/EventListeners.java index 0201fe9..df27331 100644 --- a/src/main/java/com/github/jie65535/opencommand/EventListeners.java +++ b/src/main/java/com/github/jie65535/opencommand/EventListeners.java @@ -15,9 +15,11 @@ import java.util.ArrayList; public final class EventListeners { private static MessageHandler consoleMessageHandler; + public static void setConsoleMessageHandler(MessageHandler handler) { consoleMessageHandler = handler; } + public static void onCommandResponse(ReceiveCommandFeedbackEvent event) { if (consoleMessageHandler != null && event.getPlayer() == null) { consoleMessageHandler.setMessage(event.getMessage()); diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandConfig.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandConfig.java index fad781a..766bb5d 100644 --- a/src/main/java/com/github/jie65535/opencommand/OpenCommandConfig.java +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandConfig.java @@ -25,4 +25,5 @@ public class OpenCommandConfig { public int socketPort = 5746; public String socketToken = ""; public String socketHost = "127.0.0.1"; + public String socketDisplayName = ""; } diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java index 13677b3..5b2a92a 100644 --- a/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandHandler.java @@ -19,6 +19,7 @@ package com.github.jie65535.opencommand; import com.github.jie65535.opencommand.json.JsonRequest; import com.github.jie65535.opencommand.json.JsonResponse; +import com.github.jie65535.opencommand.socket.SocketData; import emu.grasscutter.command.CommandMap; import emu.grasscutter.server.http.Router; import emu.grasscutter.utils.Crypto; @@ -31,6 +32,7 @@ import io.javalin.Javalin; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -85,6 +87,11 @@ public final class OpenCommandHandler implements Router { } else if (req.action.equals("ping")) { response.json(new JsonResponse()); return; + } else if (req.action.equals("online")) { + var p = new ArrayList(); + plugin.getServer().getPlayers().forEach((uid, player) -> p.add(player.getNickname())); + response.json(new JsonResponse(200, "Success", new SocketData.OnlinePlayer(p))); + return; } // token is required @@ -118,6 +125,9 @@ public final class OpenCommandHandler implements Router { } } return; + } else if (req.action.equals("runmode")) { + response.json(new JsonResponse(200, "Success", 0)); + return; } } else if (codes.containsKey(req.token)) { if (req.action.equals("verify")) { diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java index 8530217..db21c84 100644 --- a/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandOnlyHttpHandler.java @@ -23,6 +23,7 @@ import com.github.jie65535.opencommand.socket.SocketData; import com.github.jie65535.opencommand.socket.SocketDataWait; import com.github.jie65535.opencommand.socket.SocketServer; import com.github.jie65535.opencommand.socket.packet.HttpPacket; +import com.github.jie65535.opencommand.socket.packet.RunConsoleCommand; import com.github.jie65535.opencommand.socket.packet.player.Player; import com.github.jie65535.opencommand.socket.packet.player.PlayerEnum; import emu.grasscutter.command.CommandMap; @@ -92,6 +93,9 @@ public final class OpenCommandOnlyHttpHandler implements Router { } else if (req.action.equals("ping")) { response.json(new JsonResponse()); return; + } else if (req.action.equals("online")) { + response.json(new JsonResponse(200, "Success", SocketData.getOnlinePlayer())); + return; } // token is required @@ -110,20 +114,40 @@ public final class OpenCommandOnlyHttpHandler implements Router { response.json(new JsonResponse()); return; } else if (req.action.equals("command")) { - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (plugin) { - try { - plugin.getLogger().info(String.format("IP: %s run command in console > %s", request.ip(), req.data)); - var resultCollector = new MessageHandler(); - EventListeners.setConsoleMessageHandler(resultCollector); - CommandMap.getInstance().invoke(null, null, req.data.toString()); - response.json(new JsonResponse(resultCollector.getMessage())); - } catch (Exception e) { - plugin.getLogger().warn("Run command failed.", e); - EventListeners.setConsoleMessageHandler(null); - response.json(new JsonResponse(500, "error", e.getLocalizedMessage())); - } + var server = SocketServer.getClientInfoByUuid(req.server); + if (server == null) { + response.json(new JsonResponse(404, "Server Not Found.")); + return; } + plugin.getLogger().info(String.format("IP: %s run command in console > %s", request.ip(), req.data)); + var wait = new SocketDataWait(2000L) { + @Override + public void run() { + } + + @Override + public HttpPacket initData(HttpPacket data) { + return data; + } + + @Override + public void timeout() { + } + }; + + SocketServer.sendPacketAndWait(server.ip, new RunConsoleCommand(req.data.toString()), wait); + var data = wait.getData(); + if (data == null) { + response.json(new JsonResponse(408, "Timeout")); + return; + } + response.json(new JsonResponse(data.code, data.message, data.data)); + return; + } else if (req.action.equals("server")) { + response.json(new JsonResponse(200, "Success", SocketServer.getOnlineClient())); + return; + } else if (req.action.equals("runmode")) { + response.json(new JsonResponse(200, "Success", 1)); return; } } else if (codes.containsKey(req.token)) { @@ -143,7 +167,8 @@ public final class OpenCommandOnlyHttpHandler implements Router { if (req.action.equals("command")) { SocketDataWait socketDataWait = new SocketDataWait<>(1000L * 10L) { @Override - public void run() {} + public void run() { + } @Override public HttpPacket initData(HttpPacket data) { @@ -152,7 +177,6 @@ public final class OpenCommandOnlyHttpHandler implements Router { @Override public void timeout() { - response.json(new JsonResponse(408, "Wait server timeout")); } }; @@ -173,7 +197,7 @@ public final class OpenCommandOnlyHttpHandler implements Router { HttpPacket httpPacket = socketDataWait.getData(); if (httpPacket == null) { - response.json(new JsonResponse(500, "error", "Server connect failed.")); + response.json(new JsonResponse(500, "error", "Wait timeout")); return; } response.json(new JsonResponse(httpPacket.code, httpPacket.message)); diff --git a/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java b/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java index 4529878..6337e37 100644 --- a/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java +++ b/src/main/java/com/github/jie65535/opencommand/OpenCommandPlugin.java @@ -35,7 +35,10 @@ import java.io.IOException; public final class OpenCommandPlugin extends Plugin { private static OpenCommandPlugin instance; - public static OpenCommandPlugin getInstance() { return instance; } + + public static OpenCommandPlugin getInstance() { + return instance; + } private OpenCommandConfig config; @@ -82,19 +85,19 @@ public final class OpenCommandPlugin extends Plugin { var configFile = new File(getDataFolder(), "config.json"); if (!configFile.exists()) { config = new OpenCommandConfig(); - try (var file = new FileWriter(configFile)){ + try (var file = new FileWriter(configFile)) { file.write(Grasscutter.getGsonFactory().toJson(config)); } catch (IOException e) { - getLogger().error("Unable to write to config file."); + getLogger().error("[OpenCommand] Unable to write to config file."); } catch (Exception e) { - getLogger().error("Unable to save config file."); + getLogger().error("[OpenCommand] Unable to save config file."); } } else { try (var file = new FileReader(configFile)) { config = Grasscutter.getGsonFactory().fromJson(file, OpenCommandConfig.class); } catch (Exception exception) { config = new OpenCommandConfig(); - getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); + getLogger().error("[OpenCommand] There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); } } // 启动Socket @@ -110,7 +113,7 @@ public final class OpenCommandPlugin extends Plugin { try { SocketServer.startServer(); } catch (IOException e) { - getLogger().error("Unable to start socket server.", e); + getLogger().error("[OpenCommand] Unable to start socket server.", e); } } } diff --git a/src/main/java/com/github/jie65535/opencommand/json/JsonRequest.java b/src/main/java/com/github/jie65535/opencommand/json/JsonRequest.java index e88e426..ef8dd9f 100644 --- a/src/main/java/com/github/jie65535/opencommand/json/JsonRequest.java +++ b/src/main/java/com/github/jie65535/opencommand/json/JsonRequest.java @@ -20,5 +20,6 @@ package com.github.jie65535.opencommand.json; public final class JsonRequest { public String token = ""; public String action = ""; + public String server = ""; public Object data = null; } \ No newline at end of file diff --git a/src/main/java/com/github/jie65535/opencommand/json/JsonResponse.java b/src/main/java/com/github/jie65535/opencommand/json/JsonResponse.java index 5a37837..1163142 100644 --- a/src/main/java/com/github/jie65535/opencommand/json/JsonResponse.java +++ b/src/main/java/com/github/jie65535/opencommand/json/JsonResponse.java @@ -19,7 +19,7 @@ package com.github.jie65535.opencommand.json; public final class JsonResponse { public int retcode = 200; - public String message = "success"; + public String message = "Success"; public Object data; public JsonResponse() { diff --git a/src/main/java/com/github/jie65535/opencommand/socket/ClientInfo.java b/src/main/java/com/github/jie65535/opencommand/socket/ClientInfo.java new file mode 100644 index 0000000..df004f7 --- /dev/null +++ b/src/main/java/com/github/jie65535/opencommand/socket/ClientInfo.java @@ -0,0 +1,14 @@ +package com.github.jie65535.opencommand.socket; + +public class ClientInfo { + + public final String uuid; + public final SocketServer.ClientThread clientThread; + public final String ip; + + public ClientInfo(String uuid, String ip, SocketServer.ClientThread clientThread) { + this.uuid = uuid; + this.clientThread = clientThread; + this.ip = ip; + } +} diff --git a/src/main/java/com/github/jie65535/opencommand/socket/SocketClient.java b/src/main/java/com/github/jie65535/opencommand/socket/SocketClient.java index 692092b..92245e5 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/SocketClient.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/SocketClient.java @@ -1,5 +1,6 @@ package com.github.jie65535.opencommand.socket; +import com.github.jie65535.opencommand.EventListeners; import com.github.jie65535.opencommand.OpenCommandConfig; import com.github.jie65535.opencommand.OpenCommandPlugin; import com.github.jie65535.opencommand.socket.packet.*; @@ -33,6 +34,14 @@ public class SocketClient { // 连接服务器 public static void connectServer() { if (connect) return; + if (clientThread != null) { + mLogger.warn("[OpenCommand] Retry connecting to the server after 15 seconds"); + try { + Thread.sleep(15000); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } OpenCommandConfig config = OpenCommandPlugin.getInstance().getConfig(); mLogger = OpenCommandPlugin.getInstance().getLogger(); clientThread = new ClientThread(config.socketHost, config.socketPort); @@ -160,6 +169,24 @@ public class SocketClient { } } break; + case RunConsoleCommand: + var consoleCommand = Grasscutter.getGsonFactory().fromJson(packet.data, RunConsoleCommand.class); + var plugin = OpenCommandPlugin.getInstance(); + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (plugin) { + try { + var resultCollector = new MessageHandler(); + EventListeners.setConsoleMessageHandler(resultCollector); + CommandMap.getInstance().invoke(null, null, consoleCommand.command); + sendPacket(new HttpPacket(resultCollector.getMessage()), packet.packetID); + } catch (Exception e) { + mLogger.warn("[OpenCommand] Run command failed.", e); + EventListeners.setConsoleMessageHandler(null); + sendPacket(new HttpPacket(500, "error", e.getLocalizedMessage()), packet.packetID); + } finally { + EventListeners.setConsoleMessageHandler(null); + } + } } } catch (Throwable e) { e.printStackTrace(); @@ -206,18 +233,12 @@ public class SocketClient { socket = new Socket(ip, port); os = socket.getOutputStream(); - mLogger.info("[OpenCommand]Connect to server: " + ip + ":" + port); - SocketClient.sendPacket(new AuthPacket(OpenCommandPlugin.getInstance().getConfig().socketToken)); + mLogger.info("[OpenCommand] Connect to server: " + ip + ":" + port); + SocketClient.sendPacket(new AuthPacket(OpenCommandPlugin.getInstance().getConfig().socketToken, OpenCommandPlugin.getInstance().getConfig().socketDisplayName)); receiveThread = new ReceiveThread(socket); } catch (IOException e) { connect = false; - mLogger.warn("[OpenCommand]Connect to server failed: " + ip + ":" + port); - mLogger.warn("[OpenCommand] Retry connecting to the server after 15 seconds"); - try { - Thread.sleep(15000); - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } + mLogger.warn("[OpenCommand] Connect to server failed: " + ip + ":" + port); connectServer(); } } diff --git a/src/main/java/com/github/jie65535/opencommand/socket/SocketData.java b/src/main/java/com/github/jie65535/opencommand/socket/SocketData.java index c9cfb64..b05ba6b 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/SocketData.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/SocketData.java @@ -2,6 +2,7 @@ package com.github.jie65535.opencommand.socket; import com.github.jie65535.opencommand.socket.packet.player.PlayerList; +import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.atomic.AtomicReference; @@ -27,4 +28,21 @@ public class SocketData { }); return ret.get(); } + + public static OnlinePlayer getOnlinePlayer() { + ArrayList player = new ArrayList<>(); + playerList.forEach((address, playerMap) -> playerMap.playerMap.forEach((uid, name) -> player.add(name))); + return new OnlinePlayer(player); + } + + public static class OnlinePlayer { + public int count; + public ArrayList playerList; + + public OnlinePlayer(ArrayList playerList) { + this.playerList = playerList; + this.count = playerList.size(); + } + + } } diff --git a/src/main/java/com/github/jie65535/opencommand/socket/SocketServer.java b/src/main/java/com/github/jie65535/opencommand/socket/SocketServer.java index fa36092..4c8d192 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/SocketServer.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/SocketServer.java @@ -14,16 +14,34 @@ import java.net.Socket; import java.util.HashMap; import java.util.Timer; import java.util.TimerTask; +import java.util.UUID; // Socket 服务器 public class SocketServer { // 客户端超时时间 private static final int TIMEOUT = 5000; - private static final HashMap clientList = new HashMap<>(); + private static final HashMap clientList = new HashMap<>(); private static final HashMap clientTimeout = new HashMap<>(); private static Logger mLogger; + public static HashMap getOnlineClient() { + HashMap onlineClient = new HashMap<>(); + for (var key : clientList.entrySet()) { + onlineClient.put(key.getValue().uuid, key.getValue().clientThread.getDisplayName()); + } + return onlineClient; + } + + public static ClientInfo getClientInfoByUuid(String uuid) { + for (var key : clientList.entrySet()) { + if (key.getValue().uuid.equals(uuid)) { + return key.getValue(); + } + } + return null; + } + public static void startServer() throws IOException { int port = OpenCommandPlugin.getInstance().getConfig().socketPort; mLogger = OpenCommandPlugin.getInstance().getLogger(); @@ -34,7 +52,7 @@ public class SocketServer { // 向全部客户端发送数据 public static boolean sendAllPacket(BasePacket packet) { var p = SocketUtils.getPacket(packet); - HashMap old = (HashMap) clientList.clone(); + HashMap old = (HashMap) clientList.clone(); for (var client : old.entrySet()) { if (!client.getValue().sendPacket(p)) { mLogger.warn("[OpenCommand] Send packet to client {} failed", client.getKey()); @@ -49,7 +67,21 @@ public class SocketServer { var p = SocketUtils.getPacket(packet); var client = clientList.get(address); if (client != null) { - if (client.sendPacket(p)) { + if (client.clientThread.sendPacket(p)) { + return true; + } + mLogger.warn("[OpenCommand] Send packet to client {} failed", address); + clientList.remove(address); + } + return false; + } + + public static boolean sendPacketAndWait(String address, BasePacket packet, SocketDataWait wait) { + var p = SocketUtils.getPacketAndPackID(packet); + var client = clientList.get(address); + if (client != null) { + wait.uid = p.get(0); + if (client.clientThread.sendPacket(p.get(1), wait)) { return true; } mLogger.warn("[OpenCommand] Send packet to client {} failed", address); @@ -66,7 +98,7 @@ public class SocketServer { var client = clientList.get(clientID); if (client != null) { socketDataWait.uid = p.get(0); - if (!client.sendPacket(p.get(1), socketDataWait)) { + if (!client.clientThread.sendPacket(p.get(1), socketDataWait)) { mLogger.warn("[OpenCommand] Send packet to client {} failed", clientID); clientList.remove(clientID); return false; @@ -97,13 +129,14 @@ public class SocketServer { } // 客户端数据包处理 - private static class ClientThread extends Thread { + static class ClientThread extends Thread { private final Socket socket; private InputStream is; private OutputStream os; private final String address; private final String token; private boolean auth = false; + private String displayName = ""; private final HashMap> socketDataWaitList = new HashMap<>(); @@ -147,11 +180,12 @@ public class SocketServer { String data = SocketUtils.readString(is); Packet packet = Grasscutter.getGsonFactory().fromJson(data, Packet.class); if (packet.type == PacketEnum.AuthPacket) { - AuthPacket authPacket = Grasscutter.getGsonFactory().fromJson(data, AuthPacket.class); + AuthPacket authPacket = Grasscutter.getGsonFactory().fromJson(packet.data, AuthPacket.class); if (authPacket.token.equals(token)) { auth = true; - mLogger.info("[OpenCommand] Client {} auth success", address); - clientList.put(address, this); + displayName = authPacket.displayName; + mLogger.info("[OpenCommand] Client {} auth success, name: {}", address, displayName); + clientList.put(address, new ClientInfo(UUID.randomUUID().toString(), address, this)); clientTimeout.put(address, 0); } else { mLogger.warn("[OpenCommand] Client {} auth failed", address); @@ -196,6 +230,10 @@ public class SocketServer { } } } + + public String getDisplayName() { + return displayName; + } } // 等待客户端连接 diff --git a/src/main/java/com/github/jie65535/opencommand/socket/SocketUtils.java b/src/main/java/com/github/jie65535/opencommand/socket/SocketUtils.java index 74a5faa..79ad9e9 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/SocketUtils.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/SocketUtils.java @@ -17,12 +17,12 @@ public class SocketUtils { /** * 获取打包后的数据包 + * * @param bPacket 数据包 * @return 打包后的数据包 */ public static String getPacket(BasePacket bPacket) { Packet packet = new Packet(); - packet.token = OpenCommandPlugin.getInstance().getConfig().socketToken; packet.type = bPacket.getType(); packet.data = bPacket.getPacket(); packet.packetID = UUID.randomUUID().toString(); @@ -31,12 +31,12 @@ public class SocketUtils { /** * 获取打包后的数据包 + * * @param bPacket BasePacket * @return list[0] 是包ID, list[1] 是数据包 */ public static List getPacketAndPackID(BasePacket bPacket) { Packet packet = new Packet(); - packet.token = OpenCommandPlugin.getInstance().getConfig().socketToken; packet.type = bPacket.getType(); packet.data = bPacket.getPacket(); packet.packetID = UUID.randomUUID().toString(); @@ -49,13 +49,13 @@ public class SocketUtils { /** * 获取打包后的数据包 - * @param bPacket 数据包 + * + * @param bPacket 数据包 * @param packetID 数据包ID * @return 打包后的数据包 */ public static String getPacketAndPackID(BasePacket bPacket, String packetID) { Packet packet = new Packet(); - packet.token = OpenCommandPlugin.getInstance().getConfig().socketToken; packet.type = bPacket.getType(); packet.data = bPacket.getPacket(); packet.packetID = packetID; @@ -64,6 +64,7 @@ public class SocketUtils { /** * 读整数 + * * @param is 输入流 * @return 整数 */ @@ -77,32 +78,34 @@ public class SocketUtils { e.printStackTrace(); } - return values[0]<<24 | values[1]<<16 | values[2]<<8 | values[3]; + return values[0] << 24 | values[1] << 16 | values[2] << 8 | values[3]; } /** * 写整数 - * @param os 输出流 + * + * @param os 输出流 * @param value 整数 */ public static void writeInt(OutputStream os, int value) { int[] values = new int[4]; - values[0] = (value>>24)&0xFF; - values[1] = (value>>16)&0xFF; - values[2] = (value>>8)&0xFF; - values[3] = (value)&0xFF; + values[0] = (value >> 24) & 0xFF; + values[1] = (value >> 16) & 0xFF; + values[2] = (value >> 8) & 0xFF; + values[3] = (value) & 0xFF; - try{ + try { for (int i = 0; i < 4; i++) { os.write(values[i]); } - }catch (IOException e){ + } catch (IOException e) { e.printStackTrace(); } } /** * 读字符串 + * * @param is 输入流 * @return 字符串 */ @@ -120,15 +123,16 @@ public class SocketUtils { /** * 写字符串 + * * @param os 输出流 - * @param s 字符串 + * @param s 字符串 * @return 是否成功 */ - public static boolean writeString(OutputStream os,String s) { + public static boolean writeString(OutputStream os, String s) { try { byte[] bytes = s.getBytes(); int len = bytes.length; - writeInt(os,len); + writeInt(os, len); os.write(bytes); return true; } catch (IOException e) { diff --git a/src/main/java/com/github/jie65535/opencommand/socket/packet/AuthPacket.java b/src/main/java/com/github/jie65535/opencommand/socket/packet/AuthPacket.java index d244018..768beea 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/packet/AuthPacket.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/packet/AuthPacket.java @@ -4,8 +4,10 @@ import emu.grasscutter.Grasscutter; public class AuthPacket extends BasePacket { public String token; + public String displayName; - public AuthPacket(String token) { + public AuthPacket(String token, String displayName) { + this.displayName = displayName; this.token = token; } @@ -18,4 +20,9 @@ public class AuthPacket extends BasePacket { public PacketEnum getType() { return PacketEnum.AuthPacket; } + + @Override + public String toString() { + return "AuthPacket [token=" + token + ", displayName=" + displayName + "]"; + } } diff --git a/src/main/java/com/github/jie65535/opencommand/socket/packet/HttpPacket.java b/src/main/java/com/github/jie65535/opencommand/socket/packet/HttpPacket.java index 5cc5a46..4e81e7d 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/packet/HttpPacket.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/packet/HttpPacket.java @@ -4,8 +4,8 @@ import emu.grasscutter.Grasscutter; // http返回数据 public class HttpPacket extends BasePacket { - public int code; - public String message; + public int code = 200; + public String message = "Success"; public String data; public HttpPacket(int code, String message, String data) { @@ -18,6 +18,9 @@ public class HttpPacket extends BasePacket { this.code = code; this.message = message; } + public HttpPacket(String data) { + this.data = data; + } @Override public String getPacket() { diff --git a/src/main/java/com/github/jie65535/opencommand/socket/packet/Packet.java b/src/main/java/com/github/jie65535/opencommand/socket/packet/Packet.java index bc45524..6fe303a 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/packet/Packet.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/packet/Packet.java @@ -2,13 +2,12 @@ package com.github.jie65535.opencommand.socket.packet; // 数据包结构 public class Packet { - public String token; public PacketEnum type; public String data; public String packetID; @Override public String toString() { - return "Packet [token=" + token + ", type=" + type + ", data=" + data + ", packetID=" + packetID + "]"; + return "Packet [type=" + type + ", data=" + data + ", packetID=" + packetID + "]"; } } diff --git a/src/main/java/com/github/jie65535/opencommand/socket/packet/PacketEnum.java b/src/main/java/com/github/jie65535/opencommand/socket/packet/PacketEnum.java index dadb1ff..54a8fdf 100644 --- a/src/main/java/com/github/jie65535/opencommand/socket/packet/PacketEnum.java +++ b/src/main/java/com/github/jie65535/opencommand/socket/packet/PacketEnum.java @@ -6,5 +6,6 @@ public enum PacketEnum { Player, HttpPacket, AuthPacket, + RunConsoleCommand, HeartBeat } diff --git a/src/main/java/com/github/jie65535/opencommand/socket/packet/RunConsoleCommand.java b/src/main/java/com/github/jie65535/opencommand/socket/packet/RunConsoleCommand.java new file mode 100644 index 0000000..48d8d5a --- /dev/null +++ b/src/main/java/com/github/jie65535/opencommand/socket/packet/RunConsoleCommand.java @@ -0,0 +1,21 @@ +package com.github.jie65535.opencommand.socket.packet; + +import emu.grasscutter.Grasscutter; + +public class RunConsoleCommand extends BasePacket { + public String command; + + public RunConsoleCommand(String command) { + this.command = command; + } + + @Override + public String getPacket() { + return Grasscutter.getGsonFactory().toJson(this); + } + + @Override + public PacketEnum getType() { + return PacketEnum.RunConsoleCommand; + } +}