16 Commits

Author SHA1 Message Date
a7833a5ca0 Fix plugin constructor visibility level 2023-12-17 17:19:43 +08:00
497d13ef97 Update to build action 2023-12-17 14:20:45 +08:00
67b34e73b4 Update to support Lunar Core 2023-12-17 14:12:18 +08:00
58cd171296 Update to support Lunar Core 2023-12-17 14:08:42 +08:00
896b802bff Update README_en-US.md 2023-11-22 20:44:26 +08:00
4c4840be36 Update README.md 2023-11-22 20:42:04 +08:00
03663406de Update API Version to 2 2023-09-03 10:07:51 +08:00
de765c575e Fix console multi-line commands 2023-09-02 20:26:18 +08:00
f8a4e6f205 Fix empty command 2023-09-02 18:43:27 +08:00
6279ed2982 Update version to v1.7.0 2023-09-02 18:39:18 +08:00
1b86226951 Support multi-line commands
Update auto-generate console token
2023-09-02 18:38:59 +08:00
e9e2805738 Merge pull request #36 from JDDKCN/master
Update plugin.json
2023-09-01 13:44:08 +08:00
剧毒的KCN
d5162a6eac Update plugin.json 2023-09-01 13:41:08 +08:00
54e63d300d Fix verify api 2023-06-04 20:32:00 +08:00
a084d39b02 Update version to v1.6.1 2023-06-04 20:05:50 +08:00
6c19c09c00 Fix deserialization formatting error (#185) 2023-06-04 20:05:32 +08:00
29 changed files with 182 additions and 1946 deletions

View File

@@ -6,6 +6,7 @@ on:
- "**.java"
branches:
- "master"
- "LunarCode"
pull_request:
paths:
- "**.java"
@@ -35,7 +36,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Download latest grasscutter jar
run: wget https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip && mkdir lib && unzip Grasscutter.zip -d lib
run: wget https://nightly.link/Melledy/LunarCore/workflows/build/development/LunarCore.zip && mkdir lib && unzip LunarCore.zip -d lib
- name: Change permission
run: chmod +x gradlew
@@ -46,5 +47,5 @@ jobs:
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: opencommand
path: opencommand*.jar
name: OpenCommand
path: '*.jar'

149
README.md
View File

@@ -2,62 +2,37 @@
中文 | [English](README_en-US.md)
一个为第三方客户端开放GC命令执行接口的插件
一个为第三方客户端开放LC命令执行接口的插件
## 使用本插件的应用
- [GrasscutterTools](https://github.com/jie65535/GrasscutterCommandGenerator) —— Windows 客户端工具
- [JGrasscutterCommand](https://github.com/jie65535/JGrasscutterCommand) —— [Mirai](https://github.com/mamoe/mirai) 插件在QQ里执行命令
- [Yunzai-GrasscutterCommand](https://github.com/Zyy-boop/Yunzai-GrasscutterCommand) —— Yunzai-bot插件在QQ里执行命令
- 待补充
`1.7.0` 起可以通过 `|` 或者换行来分隔多条命令,例如:
```shell
/a 1 | /a 2
/a 3
```
调用 `ping` 响应数据将包含插件版本号。
## 服务端安装
1. 在 [Release](https://github.com/jie65535/gc-opencommand-plugin/releases) 下载 `jar`
2. 放入 `plugins` 文件夹即可
2. 放入 `LunarCore/plugins` 文件夹
3. 重启 `LunarCore` 即可生效
> 注意,如果出现以下错误:
> ```log
> INFO:PluginManager Enabling plugin: opencommand-plugin
> Exception in thread "main" java.lang.NoSuchMethodError: 'void emu.grasscutter.server.event.EventHandler.register(emu.grasscutter.plugin.Plugin)'
> at com.github.jie65535.opencommand.OpenCommandPlugin.onEnable(OpenCommandPlugin.java:49)
> at emu.grasscutter.plugin.PluginManager.lambda$enablePlugins$3(PluginManager.java:131)
> ```
> 请使用v1.2.1版本插件,因为该报错表示你的服务端是旧版!
## 玩家使用流程
1. 在远程工具中填写服务地址,查询插件状态
2. 填写UID发送验证码需要在线
3. 将游戏内收到的**4位整数验证码**填入工具校验
4. 享受便利!
## 控制台连接
1. 首次启动时,会在 `plugins` 目录下生成一个 `opencommand-plugin` 目录,打开并编辑 `config.json`
2. 设置 `consoleToken` 的值为你的连接秘钥建议使用至少32字符的长随机字符串。
2. 设置 `consoleToken` 的值为你的连接秘钥建议使用至少32字符的长随机字符串。(检测到为空时会自动生成,生成时会在控制台中输出)
3. 重新启动服务端即可生效配置
4.客户端中选择控制台身份,并填写你的 `consoleToken` 即可以控制台身份运行指令
4.工具中选择控制台身份,并填写你的 `consoleToken` 即可以控制台身份运行指令
## 多服务器
### 主服务器 (Dispatch)
1.`opencommand-plugin` 目录下打开 `config.json`
2. 修改 `socketPort` 值为一个未被使用的端口
3. 设置 `socketToken` 多服务器通信密钥建议使用至少32字符的长随机字符串。
4. 重新启动服务端即可生效配置
### 子服务器 (Game)
1.`opencommand-plugin` 目录下打开 `config.json`
2. 修改 `socketHost``socketPort` 值为主服务器的地址和端口
3. 设置 `socketToken` 和主服务器相同的值
4. 设置 `socketDisplayName` 值为你的服务器名称 (用途请见[下方](https://github.com/jie65535/gc-opencommand-plugin#%E8%8E%B7%E5%8F%96%E5%A4%9A%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%88%97%E8%A1%A8))
5. 重新启动服务端即可生效配置
## 构建说明
1. 克隆仓库
2. 在目录下新建 `lib` 目录
3.`grasscutter-1.1.x-dev.jar` 放入 `lib` 目录
4. `gradle build`
## 玩家使用流程
1. 在客户端中填写服务地址,确认是否支持
2. 填写UID发送验证码
3. 将游戏内收到的**4位整数验证码**填入客户端校验
4. 享受便利!
---
## 客户端请求流程
@@ -66,13 +41,20 @@
3. 使用 `token` 和**4位整数验证码**发送 `verify` 校验
4. 如果验证通过,可以使用该 `token` 执行 `command` 动作
## 插件构建说明
1. 克隆仓库
2. 在目录下新建 `lib` 目录
3.`LunarCore.jar` 放入 `lib` 目录
4. 执行 `gradle build`
---
## `config.json`
```json
```json5
{
// 控制台连接令牌
// 控制台连接令牌(检测到空时会自动生成)
"consoleToken": "",
// 验证码过期时间(秒)
"codeExpirationTime_S": 60,
@@ -80,14 +62,6 @@
"tempTokenExpirationTime_S": 300,
// 授权令牌最后使用过期时间(小时)
"tokenLastUseExpirationTime_H": 48,
// 多服务器通信端口
"socketPort": 5746,
// 多服务器通信密钥
"socketToken": "",
// 多服务器Dispatch服务器地址
"socketHost": "127.0.0.1",
// 多服务器显示名称
"socketDisplayName": ""
}
```
@@ -138,22 +112,6 @@ public final class JsonResponse {
| message | `Success` | `String` |
| data | `null` | `null` |
#### `获取在线玩家`
##### Request
| 请求参数 | 请求数据 | 类型 |
|--------|----------|----------|
| action | `online` | `String` |
##### Response
| 返回参数 | 返回数据 | 类型 |
|---------|---------------------------------|--------------|
| retcode | `200` | `Int` |
| message | `Success` | `String` |
| data | `{"count": 0, playerList": []}` | `JsonObject` |
#### `发送验证码`
##### Request
@@ -220,57 +178,6 @@ public final class JsonResponse {
| 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

View File

@@ -2,55 +2,39 @@
[中文](README.md) | English
A plugin that opens the GC command execution interface for third-party clients
A plugin that opens the LC command execution interface for third-party clients
## Applications using this plug-in
- [GrasscutterTools](https://github.com/jie65535/GrasscutterCommandGenerator) —— Windows Client Tools
- [JGrasscutterCommand](https://github.com/jie65535/JGrasscutterCommand) —— [Mirai](https://github.com/mamoe/mirai) Plugin, run commands in QQ
- TODO
multiple commands can be separated by `|` or newline, for example:
```shell
/a 1 | /a 2
/a 3
```
Invoking `ping` the response data will contain the plugin version.
## Server installation
1. Download the `jar` in [Release](https://github.com/jie65535/gc-opencommand-plugin/releases)
2. Put it in the `plugins` folder
2. Put it in the `LunarCore/plugins` folder
3. Restart `LunarCore` server
## Player
1. Fill in the service address in the Tool to check plugin status
2. Fill in the UID and send the verification code
3. Fill in the **4-digit integer verification code** received in the game into the Tool verification
4. Enjoy the convenience!
## Console connection
1. When starting for the first time, a `opencommand-plugin` directory will be generated under the `plugins` directory,
open and edit `config.json`
2. Set the value of `consoleToken` to your connection key. It is recommended to use a long random string of at least 32
characters.
characters. (automatically generated when empty is detected)
3. Restart the server to take effect
4. Select the console identity in the client, and fill in your `consoleToken` to run the command as the console identity
## Multi server
### Master server (Dispatch)
1. Open `config.json` in the `opencommand-plugin` directory
2. Modify the `socketPort` value to an unused port
3. Set `sockettoken` multi server communication key. It is recommended to use a long random string of at least 32 characters.
4. Restart the server to make the configuration effective
### Sub server (Game)
1. Open `config.json` in the `opencommand-plugin` directory
2. Modify the `sockethost` and `socketport` values to the address and port of the primary server
3. Set the same value of `sockettoken` and the primary server
4. Set the `socketDisplayName` value to your server name (See below for usage [Jump](https://github.com/jie65535/gc-opencommand-plugin/blob/master/README_en-US.md#get-mulit-server-list))
5. Restart the server to make the configuration effective
## Build
1. `git clone https://github.com/jie65535/gc-opencommand-plugin`
2. `cd gc-opencommand-plugin`
3. `mkdir lib`
4. `mv path/to/grasscutter-1.x.x-dev.jar ./lib`
5. `gradle build`
## Player
1. Fill in the service address in the client to confirm whether it supports
2. Fill in the UID and send the verification code
3. Fill in the **4-digit integer verification code** received in the game into the client verification
4. Enjoy the convenience!
---
## Client request
@@ -60,19 +44,28 @@ A plugin that opens the GC command execution interface for third-party clients
3. Send `verify` check using `token` and **4-digit integer verification code**
4. If the verification is passed, you can use the `token` to execute the `command` action
## Build
1. `git clone https://github.com/jie65535/gc-opencommand-plugin`
2. `cd gc-opencommand-plugin`
3. `mkdir lib`
4. `mv path/to/LunarCore.jar ./lib`
5. `gradle build`
---
## `config.json`
```json
```json5
{
// console connection token (automatically generated when empty is detected)
"consoleToken": "",
// Verification code expiration time (seconds)
"codeExpirationTime_S": 60,
// Temporary token expiration time (seconds)
"tempTokenExpirationTime_S": 300,
// Authorization token last used expiration time (hours)
"tokenLastUseExpirationTime_H": 48,
"socketPort": 5746,
"socketToken": "",
"socketHost": "127.0.0.1"
}
```
@@ -123,22 +116,6 @@ public final class JsonResponse {
| 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
@@ -206,56 +183,6 @@ Success
### 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

View File

@@ -4,7 +4,7 @@ plugins {
}
group 'com.github.jie65535.opencommand'
version '1.6.0'
version '1.0.0'
sourceCompatibility = 17
targetCompatibility = 17
@@ -18,7 +18,7 @@ dependencies {
}
jar {
jar.baseName = 'opencommand'
jar.baseName = 'OpenCommand-LunarCore'
destinationDir = file(".")
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,6 +1,6 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
* Copyright (C) 2022-2023 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@@ -17,103 +17,47 @@
*/
package com.github.jie65535.opencommand;
import com.github.jie65535.opencommand.socket.SocketClient;
import com.github.jie65535.opencommand.socket.packet.player.PlayerList;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent;
import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
public final class EventListeners {
private static StringBuilder consoleMessageHandler;
private static final Int2ObjectMap<StringBuilder> playerMessageHandlers = new Int2ObjectOpenHashMap<>();
public static void setConsoleMessageHandler(StringBuilder handler) {
consoleMessageHandler = handler;
}
/**
* 获取新的玩家消息处理类
* 获取时将创建或清空消息处理器并返回实例,**在执行命令前获取!**
* @param uid 玩家uid
* @return 新的玩家消息处理类
*/
public static StringBuilder getPlayerMessageHandler(int uid) {
var handler = playerMessageHandlers.get(uid);
if (handler == null) {
handler = new StringBuilder();
playerMessageHandlers.put(uid, handler);
}
return handler;
}
/**
* 命令执行反馈事件处理
*/
public static void onCommandResponse(ReceiveCommandFeedbackEvent event) {
StringBuilder handler;
if (event.getPlayer() == null) {
handler = consoleMessageHandler;
} else {
handler = playerMessageHandlers.get(event.getPlayer().getUid());
}
if (handler != null) {
if (!handler.isEmpty()) {
// New line
handler.append(System.lineSeparator());
}
handler.append(event.getMessage());
}
}
public static void onPlayerJoin(PlayerJoinEvent playerJoinEvent) {
PlayerList playerList = new PlayerList();
playerList.player = Grasscutter.getGameServer().getPlayers().size();
ArrayList<String> playerNames = new ArrayList<>();
playerNames.add(playerJoinEvent.getPlayer().getNickname());
playerList.playerMap.put(playerJoinEvent.getPlayer().getUid(), playerJoinEvent.getPlayer().getNickname());
for (Player player : Grasscutter.getGameServer().getPlayers().values()) {
playerNames.add(player.getNickname());
playerList.playerMap.put(player.getUid(), player.getNickname());
}
playerList.playerList = playerNames;
SocketClient.sendPacket(playerList);
}
/**
* 仅游戏模式下玩家离开事件处理方法
* 用于更新玩家列表
*/
public static void onPlayerQuit(PlayerQuitEvent playerQuitEvent) {
PlayerList playerList = new PlayerList();
playerList.player = Grasscutter.getGameServer().getPlayers().size();
ArrayList<String> playerNames = new ArrayList<>();
for (Player player : Grasscutter.getGameServer().getPlayers().values()) {
playerNames.add(player.getNickname());
playerList.playerMap.put(player.getUid(), player.getNickname());
}
playerList.playerMap.remove(playerQuitEvent.getPlayer().getUid());
playerNames.remove(playerQuitEvent.getPlayer().getNickname());
playerList.playerList = playerNames;
SocketClient.sendPacket(playerList);
}
/**
* 玩家离开事件处理 2
* 用于清理内存
*/
public static void onPlayerQuit2(PlayerQuitEvent playerQuitEvent) {
var uid = playerQuitEvent.getPlayer().getUid();
if (playerMessageHandlers.containsKey(uid)) {
playerMessageHandlers.remove(uid);
}
}
// private static StringBuilder consoleMessageHandler;
// private static final Int2ObjectMap<StringBuilder> playerMessageHandlers = new Int2ObjectOpenHashMap<>();
//
// public static void setConsoleMessageHandler(StringBuilder handler) {
// consoleMessageHandler = handler;
// }
//
// /**
// * 获取新的玩家消息处理类
// * 获取时将创建或清空消息处理器并返回实例,**在执行命令前获取!**
// * @param uid 玩家uid
// * @return 新的玩家消息处理类
// */
// public static StringBuilder getPlayerMessageHandler(int uid) {
// var handler = playerMessageHandlers.get(uid);
// if (handler == null) {
// handler = new StringBuilder();
// playerMessageHandlers.put(uid, handler);
// }
// return handler;
// }
//
// /**
// * 命令执行反馈事件处理
// */
// public static void onCommandResponse(ReceiveCommandFeedbackEvent event) {
// StringBuilder handler;
// if (event.getPlayer() == null) {
// handler = consoleMessageHandler;
// } else {
// handler = playerMessageHandlers.get(event.getPlayer().getUid());
// }
//
// if (handler != null) {
// if (!handler.isEmpty()) {
// // New line
// handler.append(System.lineSeparator());
// }
// handler.append(event.getMessage());
// }
// }
}

View File

@@ -1,6 +1,6 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
* Copyright (C) 2022-2023 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@@ -37,24 +37,4 @@ public class OpenCommandConfig {
* Token 最后使用过期时间(单位小时)
*/
public int tokenLastUseExpirationTime_H = 48;
/**
* Socket 端口
*/
public int socketPort = 5746;
/**
* Socket Token
*/
public String socketToken = "";
/**
* Socket 主机地址
*/
public String socketHost = "127.0.0.1";
/**
* Socket 显示名称
*/
public String socketDisplayName = "";
}

View File

@@ -1,6 +1,6 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
* Copyright (C) 2022-2023 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@@ -20,31 +20,24 @@ package com.github.jie65535.opencommand;
import com.github.jie65535.opencommand.json.JsonRequest;
import com.github.jie65535.opencommand.json.JsonResponse;
import com.github.jie65535.opencommand.model.Client;
import com.github.jie65535.opencommand.socket.SocketData;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
import io.javalin.Javalin;
import emu.lunarcore.LunarCore;
import emu.lunarcore.game.player.Player;
import emu.lunarcore.util.Crypto;
import io.javalin.http.Context;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public final class OpenCommandHandler implements Router {
@Override
public void applyRoutes(Javalin javalin) {
javalin.post("/opencommand/api", OpenCommandHandler::handle);
}
public final class OpenCommandHandler {
private static final Map<String, Integer> codes = new HashMap<>();
private static final Int2ObjectMap<Date> codeExpireTime = new Int2ObjectOpenHashMap<>();
private static final SecureRandom secureRandom = new SecureRandom();
public static void handle(Context context) {
var plugin = OpenCommandPlugin.getInstance();
@@ -58,8 +51,8 @@ public final class OpenCommandHandler implements Router {
var req = context.bodyAsClass(JsonRequest.class);
if (req.action.equals("sendCode")) {
int playerId = Integer.parseInt(req.data.toString());
var player = plugin.getServer().getPlayerByUid(playerId);
int playerId = (int)Double.parseDouble(req.data.toString());
var player = LunarCore.getGameServer().getPlayerByUid(playerId, false);
if (player == null) {
context.json(new JsonResponse(404, "Player Not Found."));
} else {
@@ -73,22 +66,17 @@ public final class OpenCommandHandler implements Router {
String token = req.token;
if (token == null || token.isEmpty())
token = Utils.bytesToHex(Crypto.createSessionKey(32));
int code = Utils.randomRange(1000, 9999);
token = Crypto.createSessionKey(player.getAccountUid());
int code = secureRandom.nextInt(1000, 9999);
codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L));
codes.put(token, code);
data.addClient(new Client(token, playerId, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L)));
player.dropMessage("[Open Command] Verification code: " + code);
player.sendMessage("[Open Command] Verification code: " + code);
context.json(new JsonResponse(token));
}
return;
} else if (req.action.equals("ping")) {
context.json(new JsonResponse());
return;
} else if (req.action.equals("online")) {
var p = new ArrayList<String>();
plugin.getServer().getPlayers().forEach((uid, player) -> p.add(player.getNickname()));
context.json(new JsonResponse(200, "Success", new SocketData.OnlinePlayer(p)));
context.json(new JsonResponse(plugin.getVersion()));
return;
}
@@ -113,13 +101,10 @@ public final class OpenCommandHandler implements Router {
synchronized (plugin) {
try {
plugin.getLogger().info(String.format("IP: %s run command in console > %s", context.ip(), req.data));
var resultCollector = new StringBuilder();
EventListeners.setConsoleMessageHandler(resultCollector);
CommandMap.getInstance().invoke(null, null, req.data.toString());
context.json(new JsonResponse(resultCollector.toString()));
tryInvokeCommand(null, req.data.toString());
context.json(new JsonResponse("OK"));
} catch (Exception e) {
plugin.getLogger().warn("Run command failed.", e);
EventListeners.setConsoleMessageHandler(null);
context.json(new JsonResponse(500, "error", e.getLocalizedMessage()));
}
}
@@ -130,7 +115,7 @@ public final class OpenCommandHandler implements Router {
}
} else if (codes.containsKey(req.token)) {
if (req.action.equals("verify")) {
if (codes.get(req.token).equals(req.data)) {
if (codes.get(req.token) == (int)Double.parseDouble(req.data.toString())) {
codes.remove(req.token);
// update token expire time
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
@@ -146,24 +131,19 @@ public final class OpenCommandHandler implements Router {
if (req.action.equals("command")) {
// update token expire time
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
var player = plugin.getServer().getPlayerByUid(client.playerId);
var command = req.data.toString();
var player = LunarCore.getGameServer().getPlayerByUid(client.playerId, false);
if (player == null) {
context.json(new JsonResponse(404, "Player not found"));
return;
}
// Player MessageHandler do not support concurrency
var handler = EventListeners.getPlayerMessageHandler(player.getUid());
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (handler) {
try {
handler.setLength(0);
CommandMap.getInstance().invoke(player, player, command);
context.json(new JsonResponse(handler.toString()));
} catch (Exception e) {
plugin.getLogger().warn("Run command failed.", e);
context.json(new JsonResponse(500, "error", e.getLocalizedMessage()));
}
// var history = player.getChatManager().getHistoryByUid(GameConstants.SERVER_CONSOLE_UID);
try {
tryInvokeCommand(player, req.data.toString());
context.json(new JsonResponse("OK"));
} catch (Exception e) {
plugin.getLogger().warn("Run command failed.", e);
context.json(new JsonResponse(500, "error", e.getLocalizedMessage()));
}
return;
}
@@ -174,6 +154,17 @@ public final class OpenCommandHandler implements Router {
}
}
private static void tryInvokeCommand(Player sender, String rawMessage) {
for (var command : rawMessage.split("\n[/!]|\\|")) {
command = command.trim();
if (command.isEmpty()) continue;
if (command.charAt(0) == '/' || command.charAt(0) == '!') {
command = command.substring(1);
}
LunarCore.getCommandManager().invoke(sender, command);
}
}
private static void cleanupExpiredCodes() {
var now = new Date();
codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now));

View File

@@ -1,215 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand;
import com.github.jie65535.opencommand.json.JsonRequest;
import com.github.jie65535.opencommand.json.JsonResponse;
import com.github.jie65535.opencommand.model.Client;
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.server.http.Router;
import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.Utils;
import io.javalin.Javalin;
import io.javalin.http.Context;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public final class OpenCommandOnlyHttpHandler implements Router {
@Override
public void applyRoutes(Javalin javalin) {
javalin.post("/opencommand/api", OpenCommandOnlyHttpHandler::handle);
}
private static final Map<String, Integer> codes = new HashMap<>();
private static final Int2ObjectMap<Date> codeExpireTime = new Int2ObjectOpenHashMap<>();
public static void handle(Context context) {
var plugin = OpenCommandPlugin.getInstance();
try {
var config = plugin.getConfig();
var data = plugin.getData();
var now = new Date();
// Trigger cleanup action
cleanupExpiredCodes();
data.removeExpiredClients();
var req = context.bodyAsClass(JsonRequest.class);
if (req.action.equals("sendCode")) {
int playerId = Integer.parseInt(req.data.toString());
var player = SocketData.getPlayer(playerId);
if (player == null) {
context.json(new JsonResponse(404, "Player Not Found."));
} else {
if (codeExpireTime.containsKey(playerId)) {
var expireTime = codeExpireTime.get(playerId);
if (now.before(expireTime)) {
context.json(new JsonResponse(403, "Requests are too frequent"));
return;
}
}
String token = req.token;
if (token == null || token.isEmpty())
token = Utils.bytesToHex(Crypto.createSessionKey(32));
int code = Utils.randomRange(1000, 9999);
codeExpireTime.put(playerId, new Date(now.getTime() + config.codeExpirationTime_S * 1000L));
codes.put(token, code);
data.addClient(new Client(token, playerId, new Date(now.getTime() + config.tempTokenExpirationTime_S * 1000L)));
Player.dropMessage(playerId, "[Open Command] Verification code: " + code);
context.json(new JsonResponse(token));
}
return;
} else if (req.action.equals("ping")) {
context.json(new JsonResponse());
return;
} else if (req.action.equals("online")) {
context.json(new JsonResponse(200, "Success", SocketData.getOnlinePlayer()));
return;
}
// token is required
if (req.token == null || req.token.isEmpty()) {
context.json(new JsonResponse(401, "Unauthorized"));
return;
}
var isConsole = req.token.equals(config.consoleToken);
var client = data.getClientByToken(req.token);
if (!isConsole && client == null) {
context.json(new JsonResponse(401, "Unauthorized"));
return;
}
if (isConsole) {
if (req.action.equals("verify")) {
context.json(new JsonResponse());
return;
} else if (req.action.equals("command")) {
var server = SocketServer.getClientInfoByUuid(req.server);
if (server == null) {
context.json(new JsonResponse(404, "Server Not Found."));
return;
}
plugin.getLogger().info(String.format("IP: %s run command in console > %s", context.ip(), req.data));
var wait = new SocketDataWait<HttpPacket>(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 packet = wait.getData();
if (packet == null) {
context.json(new JsonResponse(408, "Timeout"));
return;
}
context.json(new JsonResponse(packet.code, packet.message, packet.data));
return;
} else if (req.action.equals("server")) {
context.json(new JsonResponse(200, "Success", SocketServer.getOnlineClient()));
return;
} else if (req.action.equals("runmode")) {
context.json(new JsonResponse(200, "Success", 1));
return;
}
} else if (codes.containsKey(req.token)) {
if (req.action.equals("verify")) {
if (codes.get(req.token).equals(req.data)) {
codes.remove(req.token);
// update token expire time
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
context.json(new JsonResponse());
plugin.getLogger().info(String.format("Player %d has passed the verification, ip: %s", client.playerId, context.ip()));
plugin.saveData();
} else {
context.json(new JsonResponse(400, "Verification failed"));
}
return;
}
} else {
if (req.action.equals("command")) {
SocketDataWait<HttpPacket> socketDataWait = new SocketDataWait<>(1000L * 10L) {
@Override
public void run() {
}
@Override
public HttpPacket initData(HttpPacket data) {
return data;
}
@Override
public void timeout() {
}
};
// update token expire time
client.tokenExpireTime = new Date(now.getTime() + config.tokenLastUseExpirationTime_H * 60L * 60L * 1000L);
var command = req.data.toString();
var player = new Player();
player.uid = client.playerId;
player.type = PlayerEnum.RunCommand;
player.data = command;
if (!SocketServer.sendUidPacketAndWait(client.playerId, player, socketDataWait)) {
context.json(new JsonResponse(404, "Player Not Found."));
return;
}
HttpPacket httpPacket = socketDataWait.getData();
if (httpPacket == null) {
context.json(new JsonResponse(500, "error", "Wait timeout"));
return;
}
context.json(new JsonResponse(httpPacket.code, httpPacket.message));
return;
}
}
context.json(new JsonResponse(403, "forbidden"));
} catch (Exception ex) {
plugin.getLogger().error("[OpenCommand] handler error.", ex);
}
}
private static void cleanupExpiredCodes() {
var now = new Date();
codeExpireTime.int2ObjectEntrySet().removeIf(entry -> entry.getValue().before(now));
if (codeExpireTime.isEmpty())
codes.clear();
}
}

View File

@@ -1,6 +1,6 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
* Copyright (C) 2022-2023 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@@ -17,18 +17,14 @@
*/
package com.github.jie65535.opencommand;
import com.github.jie65535.opencommand.socket.SocketClient;
import com.github.jie65535.opencommand.socket.SocketServer;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.plugin.Plugin;
import emu.grasscutter.server.event.EventHandler;
import emu.grasscutter.server.event.HandlerPriority;
import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent;
import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent;
import emu.grasscutter.utils.JsonUtils;
import emu.lunarcore.LunarCore;
import emu.lunarcore.plugin.Plugin;
import emu.lunarcore.util.Crypto;
import emu.lunarcore.util.JsonUtils;
import org.slf4j.Logger;
import java.io.*;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -36,6 +32,10 @@ public final class OpenCommandPlugin extends Plugin {
private static OpenCommandPlugin instance;
public OpenCommandPlugin(Identifier identifier, URLClassLoader classLoader, File dataFolder, Logger logger) {
super(identifier, classLoader, dataFolder, logger);
}
public static OpenCommandPlugin getInstance() {
return instance;
}
@@ -44,8 +44,6 @@ public final class OpenCommandPlugin extends Plugin {
private OpenCommandData data;
private Grasscutter.ServerRunMode runMode = Grasscutter.ServerRunMode.HYBRID;
@Override
public void onLoad() {
instance = this;
@@ -53,37 +51,11 @@ public final class OpenCommandPlugin extends Plugin {
loadConfig();
// 加载数据
loadData();
// 启动Socket
startSocket();
}
@Override
public void onEnable() {
// 监听命令执行反馈
new EventHandler<>(ReceiveCommandFeedbackEvent.class)
.priority(HandlerPriority.HIGH)
.listener(EventListeners::onCommandResponse)
.register(this);
// 监听玩家离开事件
new EventHandler<>(PlayerQuitEvent.class)
.priority(HandlerPriority.NORMAL)
.listener(EventListeners::onPlayerQuit2)
.register(this);
if (runMode == Grasscutter.ServerRunMode.GAME_ONLY) {
// 仅运行游戏服务器时注册玩家加入和离开事件
new EventHandler<>(PlayerJoinEvent.class)
.priority(HandlerPriority.HIGH)
.listener(EventListeners::onPlayerJoin)
.register(this);
new EventHandler<>(PlayerQuitEvent.class)
.priority(HandlerPriority.HIGH)
.listener(EventListeners::onPlayerQuit)
.register(this);
} else if (runMode == Grasscutter.ServerRunMode.DISPATCH_ONLY) {
getHandle().addRouter(OpenCommandOnlyHttpHandler.class);
} else {
getHandle().addRouter(OpenCommandHandler.class);
}
LunarCore.getHttpServer().getApp().post("/opencommand/api", OpenCommandHandler::handle);
getLogger().info("[OpenCommand] Enabled. https://github.com/jie65535/gc-opencommand-plugin");
}
@@ -105,28 +77,33 @@ 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)) {
file.write(JsonUtils.encode(config));
} catch (IOException e) {
getLogger().error("[OpenCommand] Unable to write to config file.");
} catch (Exception e) {
getLogger().error("[OpenCommand] Unable to save config file.");
}
saveConfig();
} else {
try {
config = JsonUtils.decode(Files.readString(configFile.toPath(), StandardCharsets.UTF_8),
OpenCommandConfig.class);
} catch (Exception exception) {
config = new OpenCommandConfig();
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.");
}
if (config == null) {
config = new OpenCommandConfig();
}
}
try {
runMode = Grasscutter.getConfig().server.runMode;
} catch (Exception ex) {
getLogger().warn("[OpenCommand] Failed to load server configuration, default HYBRID mode is being used.");
// 检查控制台Token
if (config.consoleToken == null || config.consoleToken.isEmpty()) {
config.consoleToken = Crypto.createSessionKey("1");
saveConfig();
getLogger().warn("Detected that consoleToken is empty, automatically generated Token for you as follows: {}", config.consoleToken);
}
}
private void saveConfig() {
var configFile = new File(getDataFolder(), "config.json");
try (var file = new FileWriter(configFile)) {
file.write(JsonUtils.encode(config));
} catch (IOException e) {
getLogger().error("[OpenCommand] Unable to write to config file.");
} catch (Exception e) {
getLogger().error("[OpenCommand] Unable to save config file.");
}
}
@@ -157,18 +134,4 @@ public final class OpenCommandPlugin extends Plugin {
getLogger().error("[OpenCommand] Unable to save data file.");
}
}
private void startSocket() {
if (runMode == Grasscutter.ServerRunMode.GAME_ONLY) {
getLogger().info("[OpenCommand] Starting socket client...");
SocketClient.connectServer();
} else if (runMode == Grasscutter.ServerRunMode.DISPATCH_ONLY) {
getLogger().info("[OpenCommand] Starting socket server...");
try {
SocketServer.startServer();
} catch (IOException e) {
getLogger().error("[OpenCommand] Unable to start socket server.", e);
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
* Copyright (C) 2022-2023 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@@ -1,6 +1,6 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
* Copyright (C) 2022-2023 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@@ -1,31 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -1,260 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.*;
import com.github.jie65535.opencommand.socket.packet.player.Player;
import com.github.jie65535.opencommand.socket.packet.player.PlayerList;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.utils.JsonUtils;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
// Socket 客户端
public class SocketClient {
public static ClientThread clientThread;
public static Logger mLogger;
public static Timer timer;
public static boolean connect = false;
public static ReceiveThread receiveThread;
// 连接服务器
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);
if (timer != null) {
timer.cancel();
}
timer = new Timer();
timer.schedule(new SendHeartBeatPacket(), 500);
timer.schedule(new SendPlayerListPacket(), 1000);
}
// 发送数据包
public static boolean sendPacket(BasePacket packet) {
var p = SocketUtils.getPacket(packet);
if (!clientThread.sendPacket(p)) {
mLogger.warn("[OpenCommand] Send packet to server failed");
mLogger.info("[OpenCommand] Reconnect to server");
connect = false;
connectServer();
return false;
}
return true;
}
// 发送数据包带数据包ID
public static boolean sendPacket(BasePacket packet, String packetID) {
if (!clientThread.sendPacket(SocketUtils.getPacketAndPackID(packet, packetID))) {
mLogger.warn("[OpenCommand] Send packet to server failed");
mLogger.info("[OpenCommand] Reconnect to server");
connect = false;
connectServer();
return false;
}
return true;
}
// 心跳包发送
private static class SendHeartBeatPacket extends TimerTask {
@Override
public void run() {
if (connect) {
sendPacket(new HeartBeat("Pong"));
}
}
}
private static class SendPlayerListPacket extends TimerTask {
@Override
public void run() {
if (connect) {
PlayerList playerList = new PlayerList();
playerList.player = Grasscutter.getGameServer().getPlayers().size();
ArrayList<String> playerNames = new ArrayList<>();
for (emu.grasscutter.game.player.Player player : Grasscutter.getGameServer().getPlayers().values()) {
playerNames.add(player.getNickname());
playerList.playerMap.put(player.getUid(), player.getNickname());
}
playerList.playerList = playerNames;
sendPacket(playerList);
}
}
}
// 数据包接收
private static class ReceiveThread extends Thread {
private InputStream is;
private boolean exit = false;
public ReceiveThread(Socket socket) {
try {
is = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
start();
}
@Override
public void run() {
while (true) {
try {
if (exit) {
return;
}
String data = SocketUtils.readString(is);
Packet packet = JsonUtils.decode(data, Packet.class);
switch (packet.type) {
// 玩家类
case Player:
var player = JsonUtils.decode(packet.data, Player.class);
switch (player.type) {
// 运行命令
case RunCommand -> {
var command = player.data;
var playerData = OpenCommandPlugin.getInstance().getServer().getPlayerByUid(player.uid);
if (playerData == null) {
sendPacket(new HttpPacket(404, "Player not found."), packet.packetID);
return;
}
// Player MessageHandler do not support concurrency
var handler = EventListeners.getPlayerMessageHandler(playerData.getUid());
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (handler) {
try {
handler.setLength(0);
CommandMap.getInstance().invoke(playerData, playerData, command);
sendPacket(new HttpPacket(200, handler.toString()), packet.packetID);
} catch (Exception e) {
OpenCommandPlugin.getInstance().getLogger().warn("[OpenCommand] Run command failed.", e);
sendPacket(new HttpPacket(500, "error", e.getLocalizedMessage()), packet.packetID);
}
}
}
// 发送信息
case DropMessage -> {
var playerData = OpenCommandPlugin.getInstance().getServer().getPlayerByUid(player.uid);
if (playerData == null) {
return;
}
playerData.dropMessage(player.data);
}
}
break;
case RunConsoleCommand:
var consoleCommand = JsonUtils.decode(packet.data, RunConsoleCommand.class);
var plugin = OpenCommandPlugin.getInstance();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (plugin) {
try {
var resultCollector = new StringBuilder();
EventListeners.setConsoleMessageHandler(resultCollector);
CommandMap.getInstance().invoke(null, null, consoleCommand.command);
sendPacket(new HttpPacket(resultCollector.toString()), 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();
if (!sendPacket(new HeartBeat("Pong"))) {
return;
}
}
}
}
public void exit() {
exit = true;
}
}
// 客户端连接线程
private static class ClientThread extends Thread {
private final String ip;
private final int port;
private Socket socket;
private OutputStream os;
public ClientThread(String ip, int port) {
this.ip = ip;
this.port = port;
start();
}
public Socket getSocket() {
return socket;
}
public boolean sendPacket(String string) {
return SocketUtils.writeString(os, string);
}
@Override
public void run() {
try {
connect = true;
if (receiveThread != null) {
receiveThread.exit();
}
socket = new Socket(ip, port);
os = socket.getOutputStream();
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);
connectServer();
}
}
}
}

View File

@@ -1,65 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
// Socket 数据保存
public class SocketData {
public static HashMap<String, PlayerList> playerList = new HashMap<>();
public static String getPlayer(int uid) {
for (PlayerList player : playerList.values()) {
if (player.playerMap.get(uid) != null) {
return player.playerMap.get(uid);
}
}
return null;
}
public static String getPlayerInServer(int uid) {
AtomicReference<String> ret = new AtomicReference<>();
playerList.forEach((key, value) -> {
if (value.playerMap.get(uid) != null) {
ret.set(key);
}
});
return ret.get();
}
public static OnlinePlayer getOnlinePlayer() {
ArrayList<String> 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<String> playerList;
public OnlinePlayer(ArrayList<String> playerList) {
this.playerList = playerList;
this.count = playerList.size();
}
}
}

View File

@@ -1,77 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket;
// 异步等待数据返回
public abstract class SocketDataWait<T> extends Thread {
public T data;
public long timeout;
public long time;
public String uid;
/**
* 异步等待数据返回
* @param timeout 超时时间
*/
public SocketDataWait(long timeout) {
this.timeout = timeout;
start();
}
public abstract void run();
/**
* 数据处理
* @param data 数据
* @return 处理后的数据
*/
public abstract T initData(T data);
/**
* 超时回调
*/
public abstract void timeout();
/**
* 异步设置数据
* @param data 数据
*/
public void setData(Object data) {
this.data = initData((T) data);
}
/**
* 获取异步数据(此操作会一直堵塞直到获取到数据)
* @return 数据
*/
public T getData() {
while (data == null) {
try {
time += 100;
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (time > timeout) {
timeout();
return null;
}
}
return data;
}
}

View File

@@ -1,294 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket;
import com.github.jie65535.opencommand.OpenCommandPlugin;
import com.github.jie65535.opencommand.socket.packet.*;
import com.github.jie65535.opencommand.socket.packet.player.PlayerList;
import emu.grasscutter.utils.JsonUtils;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
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<String, ClientInfo> clientList = new HashMap<>();
private static final HashMap<String, Integer> clientTimeout = new HashMap<>();
private static Logger mLogger;
public static HashMap<String, String> getOnlineClient() {
HashMap<String, String> 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();
new Timer().schedule(new SocketClientCheck(), 500);
new WaitClientConnect(port);
}
// 向全部客户端发送数据
public static boolean sendAllPacket(BasePacket packet) {
var p = SocketUtils.getPacket(packet);
HashMap<String, ClientInfo> old = (HashMap<String, ClientInfo>) clientList.clone();
for (var client : old.entrySet()) {
if (!client.getValue().clientThread.sendPacket(p)) {
mLogger.warn("[OpenCommand] Send packet to client {} failed", client.getKey());
clientList.remove(client.getKey());
}
}
return false;
}
// 根据地址发送到相应的客户端
public static boolean sendPacket(String address, BasePacket packet) {
var p = SocketUtils.getPacket(packet);
var client = clientList.get(address);
if (client != null) {
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);
clientList.remove(address);
}
return false;
}
public static boolean sendUidPacket(Integer playerId, BasePacket player) {
var p = SocketUtils.getPacket(player);
var clientID = SocketData.getPlayerInServer(playerId);
if (clientID == null) return false;
var client = clientList.get(clientID);
if (client != null) {
if (!client.clientThread.sendPacket(p)) {
mLogger.warn("[OpenCommand] Send packet to client {} failed", clientID);
clientList.remove(clientID);
return false;
}
return true;
}
return false;
}
// 根据Uid发送到相应的客户端异步返回数据
public static boolean sendUidPacketAndWait(Integer playerId, BasePacket player, SocketDataWait<?> socketDataWait) {
var p = SocketUtils.getPacketAndPackID(player);
var clientID = SocketData.getPlayerInServer(playerId);
if (clientID == null) return false;
var client = clientList.get(clientID);
if (client != null) {
socketDataWait.uid = p.get(0);
if (!client.clientThread.sendPacket(p.get(1), socketDataWait)) {
mLogger.warn("[OpenCommand] Send packet to client {} failed", clientID);
clientList.remove(clientID);
return false;
}
return true;
}
return false;
}
// 客户端超时检测
private static class SocketClientCheck extends TimerTask {
@Override
public void run() {
HashMap<String, Integer> old = (HashMap<String, Integer>) clientTimeout.clone();
for (var client : old.entrySet()) {
var clientID = client.getKey();
var clientTime = client.getValue();
if (clientTime > TIMEOUT) {
mLogger.info("[OpenCommand] Client {} timeout, disconnect.", clientID);
clientList.remove(clientID);
clientTimeout.remove(clientID);
SocketData.playerList.remove(clientID);
} else {
clientTimeout.put(clientID, clientTime + 500);
}
}
}
}
// 客户端数据包处理
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<String, SocketDataWait<?>> socketDataWaitList = new HashMap<>();
public ClientThread(Socket accept) {
socket = accept;
address = socket.getInetAddress() + ":" + socket.getPort();
token = OpenCommandPlugin.getInstance().getConfig().socketToken;
try {
is = accept.getInputStream();
os = accept.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
start();
}
public Socket getSocket() {
return socket;
}
// 发送数据包
public boolean sendPacket(String packet) {
return SocketUtils.writeString(os, packet);
}
// 发送异步数据包
public boolean sendPacket(String packet, SocketDataWait<?> socketDataWait) {
if (SocketUtils.writeString(os, packet)) {
socketDataWaitList.put(socketDataWait.uid, socketDataWait);
return true;
} else {
return false;
}
}
@Override
public void run() {
while (true) {
try {
String data = SocketUtils.readString(is);
Packet packet = JsonUtils.decode(data, Packet.class);
if (packet.type == PacketEnum.AuthPacket) {
AuthPacket authPacket = JsonUtils.decode(packet.data, AuthPacket.class);
if (authPacket.token.equals(token)) {
auth = true;
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);
socket.close();
return;
}
}
if (!auth) {
mLogger.warn("[OpenCommand] Client {} auth failed", address);
socket.close();
return;
}
switch (packet.type) {
// 缓存玩家列表
case PlayerList -> {
PlayerList playerList = JsonUtils.decode(packet.data, PlayerList.class);
SocketData.playerList.put(address, playerList);
}
// Http信息返回
case HttpPacket -> {
HttpPacket httpPacket = JsonUtils.decode(packet.data, HttpPacket.class);
var socketWait = socketDataWaitList.get(packet.packetID);
if (socketWait == null) {
mLogger.error("[OpenCommand] HttpPacket: " + packet.packetID + " not found");
return;
}
socketWait.setData(httpPacket);
socketDataWaitList.remove(packet.packetID);
}
// 心跳包
case HeartBeat -> clientTimeout.put(address, 0);
}
} catch (Throwable e) {
e.printStackTrace();
mLogger.error("[OpenCommand] Client {} disconnect.", address);
clientList.remove(address);
clientTimeout.remove(address);
SocketData.playerList.remove(address);
break;
}
}
}
public String getDisplayName() {
return displayName;
}
}
// 等待客户端连接
private static class WaitClientConnect extends Thread {
ServerSocket socketServer;
public WaitClientConnect(int port) throws IOException {
socketServer = new ServerSocket(port);
start();
}
@Override
public void run() {
mLogger.info("[OpenCommand] Start socket server on port " + socketServer.getLocalPort());
// noinspection InfiniteLoopStatement
while (true) {
try {
Socket accept = socketServer.accept();
String address = accept.getInetAddress() + ":" + accept.getPort();
mLogger.info("[OpenCommand] Client connect: " + address);
new ClientThread(accept);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

View File

@@ -1,158 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket;
import com.github.jie65535.opencommand.socket.packet.BasePacket;
import com.github.jie65535.opencommand.socket.packet.Packet;
import emu.grasscutter.utils.JsonUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
// Socket 工具类
public class SocketUtils {
/**
* 获取打包后的数据包
*
* @param bPacket 数据包
* @return 打包后的数据包
*/
public static String getPacket(BasePacket bPacket) {
Packet packet = new Packet();
packet.type = bPacket.getType();
packet.data = bPacket.getPacket();
packet.packetID = UUID.randomUUID().toString();
return JsonUtils.encode(packet);
}
/**
* 获取打包后的数据包
*
* @param bPacket BasePacket
* @return list[0] 是包ID, list[1] 是数据包
*/
public static List<String> getPacketAndPackID(BasePacket bPacket) {
Packet packet = new Packet();
packet.type = bPacket.getType();
packet.data = bPacket.getPacket();
packet.packetID = UUID.randomUUID().toString();
List<String> list = new ArrayList<>();
list.add(packet.packetID);
list.add(JsonUtils.encode(packet));
return list;
}
/**
* 获取打包后的数据包
*
* @param bPacket 数据包
* @param packetID 数据包ID
* @return 打包后的数据包
*/
public static String getPacketAndPackID(BasePacket bPacket, String packetID) {
Packet packet = new Packet();
packet.type = bPacket.getType();
packet.data = bPacket.getPacket();
packet.packetID = packetID;
return JsonUtils.encode(packet);
}
/**
* 读整数
*
* @param is 输入流
* @return 整数
*/
public static int readInt(InputStream is) {
int[] values = new int[4];
try {
for (int i = 0; i < 4; i++) {
values[i] = is.read();
}
} catch (IOException e) {
e.printStackTrace();
}
return values[0] << 24 | values[1] << 16 | values[2] << 8 | values[3];
}
/**
* 写整数
*
* @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;
try {
for (int i = 0; i < 4; i++) {
os.write(values[i]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读字符串
*
* @param is 输入流
* @return 字符串
*/
public static String readString(InputStream is) {
int len = readInt(is);
byte[] sByte = new byte[len];
try {
is.read(sByte);
} catch (IOException e) {
e.printStackTrace();
}
return new String(sByte);
}
/**
* 写字符串
*
* @param os 输出流
* @param s 字符串
* @return 是否成功
*/
public static boolean writeString(OutputStream os, String s) {
try {
byte[] bytes = s.getBytes();
int len = bytes.length;
writeInt(os, len);
os.write(bytes);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}

View File

@@ -1,45 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet;
import emu.grasscutter.utils.JsonUtils;
public class AuthPacket extends BasePacket {
public String token;
public String displayName;
public AuthPacket(String token, String displayName) {
this.displayName = displayName;
this.token = token;
}
@Override
public String getPacket() {
return JsonUtils.encode(this);
}
@Override
public PacketEnum getType() {
return PacketEnum.AuthPacket;
}
@Override
public String toString() {
return "AuthPacket [token=" + token + ", displayName=" + displayName + "]";
}
}

View File

@@ -1,25 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet;
// 基本数据包
public abstract class BasePacket {
public abstract String getPacket();
public abstract PacketEnum getType();
}

View File

@@ -1,39 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet;
import emu.grasscutter.utils.JsonUtils;
// 心跳包
public class HeartBeat extends BasePacket {
public String ping;
public HeartBeat(String ping) {
this.ping = ping;
}
@Override
public String getPacket() {
return JsonUtils.encode(this);
}
@Override
public PacketEnum getType() {
return PacketEnum.HeartBeat;
}
}

View File

@@ -1,56 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet;
import emu.grasscutter.utils.JsonUtils;
// http返回数据
public class HttpPacket extends BasePacket {
public int code = 200;
public String message = "Success";
public String data;
public HttpPacket(int code, String message, String data) {
this.code = code;
this.message = message;
this.data = data;
}
public HttpPacket(int code, String message) {
this.code = code;
this.message = message;
}
public HttpPacket(String data) {
this.data = data;
}
@Override
public String getPacket() {
return JsonUtils.encode(this);
}
@Override
public PacketEnum getType() {
return PacketEnum.HttpPacket;
}
@Override
public String toString() {
return "HttpPacket [code=" + code + ", message=" + message + ", data=" + data + "]";
}
}

View File

@@ -1,30 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet;
// 数据包结构
public class Packet {
public PacketEnum type;
public String data;
public String packetID;
@Override
public String toString() {
return "Packet [type=" + type + ", data=" + data + ", packetID=" + packetID + "]";
}
}

View File

@@ -1,28 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet;
// 数据包类型列表
public enum PacketEnum {
PlayerList,
Player,
HttpPacket,
AuthPacket,
RunConsoleCommand,
HeartBeat
}

View File

@@ -1,38 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet;
import emu.grasscutter.utils.JsonUtils;
public class RunConsoleCommand extends BasePacket {
public String command;
public RunConsoleCommand(String command) {
this.command = command;
}
@Override
public String getPacket() {
return JsonUtils.encode(this);
}
@Override
public PacketEnum getType() {
return PacketEnum.RunConsoleCommand;
}
}

View File

@@ -1,48 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet.player;
import com.github.jie65535.opencommand.socket.SocketServer;
import com.github.jie65535.opencommand.socket.packet.BasePacket;
import com.github.jie65535.opencommand.socket.packet.PacketEnum;
import emu.grasscutter.utils.JsonUtils;
// 玩家操作类
public class Player extends BasePacket {
public PlayerEnum type;
public int uid;
public String data;
@Override
public String getPacket() {
return JsonUtils.encode(this);
}
@Override
public PacketEnum getType() {
return PacketEnum.Player;
}
public static void dropMessage(int uid, String str) {
Player p = new Player();
p.type = PlayerEnum.DropMessage;
p.uid = uid;
p.data = str;
SocketServer.sendUidPacket(uid, p);
}
}

View File

@@ -1,24 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet.player;
// 玩家操作列表
public enum PlayerEnum {
DropMessage,
RunCommand
}

View File

@@ -1,44 +0,0 @@
/*
* gc-opencommand
* Copyright (C) 2022 jie65535
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.github.jie65535.opencommand.socket.packet.player;
import com.github.jie65535.opencommand.socket.packet.BasePacket;
import com.github.jie65535.opencommand.socket.packet.PacketEnum;
import emu.grasscutter.utils.JsonUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// 玩家列表信息
public class PlayerList extends BasePacket {
public int player = -1;
public List<String> playerList = new ArrayList<>();
public Map<Integer, String> playerMap = new HashMap<>();
@Override
public String getPacket() {
return JsonUtils.encode(this);
}
@Override
public PacketEnum getType() {
return PacketEnum.PlayerList;
}
}

View File

@@ -1,8 +1,8 @@
{
"name": "opencommand-plugin",
"description": "Open command interface for third-party clients",
"version": "1.6.0",
"version": "1.0.0",
"mainClass": "com.github.jie65535.opencommand.OpenCommandPlugin",
"authors": ["jie65535", "方块君"],
"api": 2
"authors": ["jie65535"],
"api": 1
}