koishi-plugin-minecraft-adapter 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -2
- package/lib/index.d.ts +34 -0
- package/lib/index.js +224 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# koishi-plugin-minecraft-adapter
|
|
2
2
|
|
|
3
|
-
Koishi 的 Minecraft 适配器:支持 RCON
|
|
3
|
+
Koishi 的 Minecraft 适配器:支持 RCON、鹊桥(Queqiao) Webhook 与 WebSocket。
|
|
4
4
|
|
|
5
5
|
- RCON:连接并发送命令/广播
|
|
6
|
-
- Webhook:接收聊天、加入、离开等事件并在 Koishi 中触发
|
|
6
|
+
- Webhook/WS:接收聊天、加入、离开等事件并在 Koishi 中触发
|
|
7
7
|
|
|
8
8
|
参考实现:
|
|
9
9
|
- https://github.com/17TheWord/nonebot-adapter-minecraft
|
|
@@ -19,6 +19,25 @@ npm i koishi-plugin-minecraft-adapter
|
|
|
19
19
|
|
|
20
20
|
- RCON:host/port/password/timeout/reconnectInterval/reconnectStrategy/maxReconnectInterval/broadcastMode
|
|
21
21
|
- Webhook:enabled/path/secret/verifyMode/signatureHeader/secretHeader
|
|
22
|
+
- WebSocket(对齐鹊桥官方)
|
|
23
|
+
- enabled: 是否启用
|
|
24
|
+
- url: WS 地址,例如 `ws://127.0.0.1:8080`
|
|
25
|
+
- serverName: 必填,需与鹊桥 `config.yml` 的 `server_name` 完全一致
|
|
26
|
+
- accessToken: 可选,对应鹊桥 `config.yml` 的 `access_token`
|
|
27
|
+
- extraHeaders: 可选,附加自定义请求头
|
|
28
|
+
- reconnectStrategy: `fixed | exponential`
|
|
29
|
+
- reconnectInterval / maxReconnectInterval: 断线重连间隔
|
|
30
|
+
|
|
31
|
+
握手头部:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
x-self-name: <serverName>
|
|
35
|
+
Authorization: Bearer <accessToken> // 若 accessToken 不为空
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
鹊桥项目参考:
|
|
39
|
+
|
|
40
|
+
- QueQiao: https://github.com/17TheWord/QueQiao
|
|
22
41
|
|
|
23
42
|
## 用法
|
|
24
43
|
|
|
@@ -32,4 +51,18 @@ ctx.on('minecraft/chat', (p) => {
|
|
|
32
51
|
})
|
|
33
52
|
```
|
|
34
53
|
|
|
54
|
+
### ChatLuna(AI)联动
|
|
55
|
+
|
|
56
|
+
启用后可直接在 Koishi 中使用以下命令,便于 ChatLuna 的“指令模型/函数调用/意图解析”触发:
|
|
57
|
+
|
|
58
|
+
- `mc.exec <command>`:通过 RCON 执行任意指令
|
|
59
|
+
- `mc.say <message>`:向全服广播(优先走 WS,失败回退 RCON)
|
|
60
|
+
- `mc.tell <player> <message>`:向指定玩家发送消息(优先 WS,失败回退 RCON)
|
|
61
|
+
- `mc.title <player> <title> [subtitle]`:标题/子标题(WS 或 RCON 回退)
|
|
62
|
+
- `mc.actionbar <player> <message>`:动作栏(WS 或 RCON 回退)
|
|
63
|
+
|
|
64
|
+
可在插件配置中调整:
|
|
65
|
+
- `commands.enabled`:是否注册上述命令(默认开启)
|
|
66
|
+
- `commands.authority`:执行所需权限(默认 2),避免普通用户误触发
|
|
67
|
+
|
|
35
68
|
MIT
|
package/lib/index.d.ts
CHANGED
|
@@ -6,6 +6,14 @@ export interface MinecraftServiceAPI {
|
|
|
6
6
|
broadcast(message: string): Promise<string>;
|
|
7
7
|
/** 向指定玩家发送消息(tellraw) */
|
|
8
8
|
sendTo(player: string, message: string): Promise<string>;
|
|
9
|
+
/** 通过 WS 广播(可回退 RCON) */
|
|
10
|
+
wsBroadcast(message: string): Promise<string | void>;
|
|
11
|
+
/** 通过 WS 私聊(可回退 RCON) */
|
|
12
|
+
wsTell(player: string, message: string): Promise<string | void>;
|
|
13
|
+
/** 通过 WS 或 RCON 发送标题、子标题 */
|
|
14
|
+
wsTitle(player: string, title: string, subtitle?: string): Promise<string | void>;
|
|
15
|
+
/** 通过 WS 或 RCON 发送动作栏 */
|
|
16
|
+
wsActionbar(player: string, message: string): Promise<string | void>;
|
|
9
17
|
}
|
|
10
18
|
declare class MinecraftService extends Service implements MinecraftServiceAPI {
|
|
11
19
|
ctx: Context;
|
|
@@ -20,6 +28,9 @@ declare class MinecraftService extends Service implements MinecraftServiceAPI {
|
|
|
20
28
|
private currentReconnectInterval?;
|
|
21
29
|
private commandQueue;
|
|
22
30
|
private isProcessingQueue;
|
|
31
|
+
private ws?;
|
|
32
|
+
private wsReconnectTimer?;
|
|
33
|
+
private wsCurrentInterval?;
|
|
23
34
|
constructor(ctx: Context, config: MinecraftService.Config);
|
|
24
35
|
private registerWebhook;
|
|
25
36
|
start(): void;
|
|
@@ -28,10 +39,18 @@ declare class MinecraftService extends Service implements MinecraftServiceAPI {
|
|
|
28
39
|
private scheduleReconnect;
|
|
29
40
|
private ensureConnected;
|
|
30
41
|
private disconnect;
|
|
42
|
+
private connectWs;
|
|
43
|
+
private disconnectWs;
|
|
44
|
+
private ensureWsConnected;
|
|
45
|
+
private scheduleWsReconnect;
|
|
31
46
|
execute(command: string): Promise<string>;
|
|
32
47
|
broadcast(message: string): Promise<string>;
|
|
33
48
|
sendTo(player: string, message: string): Promise<string>;
|
|
34
49
|
private enqueue;
|
|
50
|
+
wsBroadcast(message: string): Promise<string | void>;
|
|
51
|
+
wsTell(player: string, message: string): Promise<string | void>;
|
|
52
|
+
wsTitle(player: string, title: string, subtitle?: string): Promise<string | void>;
|
|
53
|
+
wsActionbar(player: string, message: string): Promise<string | void>;
|
|
35
54
|
private processQueue;
|
|
36
55
|
}
|
|
37
56
|
declare namespace MinecraftService {
|
|
@@ -54,9 +73,24 @@ declare namespace MinecraftService {
|
|
|
54
73
|
signatureHeader?: string;
|
|
55
74
|
secretHeader?: string;
|
|
56
75
|
}
|
|
76
|
+
interface WebSocketConfig {
|
|
77
|
+
enabled: boolean;
|
|
78
|
+
url: string;
|
|
79
|
+
serverName: string;
|
|
80
|
+
accessToken?: string;
|
|
81
|
+
extraHeaders?: Record<string, string>;
|
|
82
|
+
reconnectInterval?: number;
|
|
83
|
+
reconnectStrategy?: 'fixed' | 'exponential';
|
|
84
|
+
maxReconnectInterval?: number;
|
|
85
|
+
}
|
|
57
86
|
interface Config {
|
|
58
87
|
rcon?: RconConfig;
|
|
59
88
|
webhook?: WebhookConfig;
|
|
89
|
+
websocket?: WebSocketConfig;
|
|
90
|
+
commands?: {
|
|
91
|
+
enabled?: boolean;
|
|
92
|
+
authority?: number;
|
|
93
|
+
};
|
|
60
94
|
}
|
|
61
95
|
const Config: Schema<Config>;
|
|
62
96
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
const koishi_1 = require("koishi");
|
|
4
7
|
const rcon_client_1 = require("rcon-client");
|
|
8
|
+
const ws_1 = __importDefault(require("ws"));
|
|
5
9
|
const logger = new koishi_1.Logger('minecraft');
|
|
6
10
|
class MinecraftService extends koishi_1.Service {
|
|
7
11
|
ctx;
|
|
@@ -16,6 +20,10 @@ class MinecraftService extends koishi_1.Service {
|
|
|
16
20
|
currentReconnectInterval;
|
|
17
21
|
commandQueue = [];
|
|
18
22
|
isProcessingQueue = false;
|
|
23
|
+
// WebSocket
|
|
24
|
+
ws;
|
|
25
|
+
wsReconnectTimer;
|
|
26
|
+
wsCurrentInterval;
|
|
19
27
|
constructor(ctx, config) {
|
|
20
28
|
super(ctx, 'minecraft', true);
|
|
21
29
|
this.ctx = ctx;
|
|
@@ -23,6 +31,85 @@ class MinecraftService extends koishi_1.Service {
|
|
|
23
31
|
if (config.webhook?.enabled) {
|
|
24
32
|
this.registerWebhook();
|
|
25
33
|
}
|
|
34
|
+
// 注册 Koishi 指令,便于 ChatLuna 通过自然语言“触发命令”
|
|
35
|
+
if (config.commands?.enabled !== false) {
|
|
36
|
+
const authority = config.commands?.authority ?? 2;
|
|
37
|
+
ctx.command('mc.exec <command:text>', '执行 Minecraft 指令 (RCON)')
|
|
38
|
+
.action(async ({ session }, command) => {
|
|
39
|
+
const currentAuth = Number((session?.user?.authority) ?? 0);
|
|
40
|
+
if (currentAuth < authority)
|
|
41
|
+
return '权限不足';
|
|
42
|
+
if (!command)
|
|
43
|
+
return '用法: mc.exec <command>';
|
|
44
|
+
try {
|
|
45
|
+
const res = await this.execute(command);
|
|
46
|
+
return res || '已执行';
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return '执行失败: ' + (e?.message || e);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
ctx.command('mc.say <message:text>', '向服务器广播消息')
|
|
53
|
+
.action(async ({ session }, message) => {
|
|
54
|
+
const currentAuth = Number((session?.user?.authority) ?? 0);
|
|
55
|
+
if (currentAuth < authority)
|
|
56
|
+
return '权限不足';
|
|
57
|
+
if (!message)
|
|
58
|
+
return '用法: mc.say <message>';
|
|
59
|
+
try {
|
|
60
|
+
const res = await this.broadcast(message);
|
|
61
|
+
return res || '已广播';
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return '广播失败: ' + (e?.message || e);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
ctx.command('mc.tell <player:string> <message:text>', '向指定玩家发送消息')
|
|
68
|
+
.action(async ({ session }, player, message) => {
|
|
69
|
+
const currentAuth = Number((session?.user?.authority) ?? 0);
|
|
70
|
+
if (currentAuth < authority)
|
|
71
|
+
return '权限不足';
|
|
72
|
+
if (!player || !message)
|
|
73
|
+
return '用法: mc.tell <player> <message>';
|
|
74
|
+
try {
|
|
75
|
+
const res = await this.sendTo(player, message);
|
|
76
|
+
return res || '已发送';
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return '发送失败: ' + (e?.message || e);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
ctx.command('mc.title <player:string> <title:text> [subtitle:text]', '向玩家发送标题/子标题')
|
|
83
|
+
.action(async ({ session }, player, title, subtitle) => {
|
|
84
|
+
const currentAuth = Number((session?.user?.authority) ?? 0);
|
|
85
|
+
if (currentAuth < authority)
|
|
86
|
+
return '权限不足';
|
|
87
|
+
if (!player || !title)
|
|
88
|
+
return '用法: mc.title <player> <title> [subtitle]';
|
|
89
|
+
try {
|
|
90
|
+
await this.wsTitle(player, title, subtitle);
|
|
91
|
+
return '已发送';
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
return '发送失败: ' + (e?.message || e);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
ctx.command('mc.actionbar <player:string> <message:text>', '向玩家发送动作栏')
|
|
98
|
+
.action(async ({ session }, player, message) => {
|
|
99
|
+
const currentAuth = Number((session?.user?.authority) ?? 0);
|
|
100
|
+
if (currentAuth < authority)
|
|
101
|
+
return '权限不足';
|
|
102
|
+
if (!player || !message)
|
|
103
|
+
return '用法: mc.actionbar <player> <message>';
|
|
104
|
+
try {
|
|
105
|
+
await this.wsActionbar(player, message);
|
|
106
|
+
return '已发送';
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
return '发送失败: ' + (e?.message || e);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
26
113
|
}
|
|
27
114
|
registerWebhook() {
|
|
28
115
|
const { path = '/minecraft/webhook', secret, verifyMode = 'header-secret', signatureHeader = 'x-queqiao-signature', secretHeader = 'x-queqiao-secret' } = this.config.webhook;
|
|
@@ -73,6 +160,9 @@ class MinecraftService extends koishi_1.Service {
|
|
|
73
160
|
if (this.config.rcon?.enabled) {
|
|
74
161
|
this.ensureConnected();
|
|
75
162
|
}
|
|
163
|
+
if (this.config.websocket?.enabled) {
|
|
164
|
+
this.ensureWsConnected();
|
|
165
|
+
}
|
|
76
166
|
}
|
|
77
167
|
stop() {
|
|
78
168
|
if (this.reconnectTimer) {
|
|
@@ -80,6 +170,11 @@ class MinecraftService extends koishi_1.Service {
|
|
|
80
170
|
this.reconnectTimer = undefined;
|
|
81
171
|
}
|
|
82
172
|
this.disconnect();
|
|
173
|
+
if (this.wsReconnectTimer) {
|
|
174
|
+
clearTimeout(this.wsReconnectTimer);
|
|
175
|
+
this.wsReconnectTimer = undefined;
|
|
176
|
+
}
|
|
177
|
+
this.disconnectWs();
|
|
83
178
|
}
|
|
84
179
|
async connectRcon() {
|
|
85
180
|
if (this.isConnecting || this.rcon)
|
|
@@ -138,6 +233,72 @@ class MinecraftService extends koishi_1.Service {
|
|
|
138
233
|
this.rcon = undefined;
|
|
139
234
|
}
|
|
140
235
|
}
|
|
236
|
+
connectWs() {
|
|
237
|
+
if (this.ws)
|
|
238
|
+
return;
|
|
239
|
+
const { url, serverName, accessToken, extraHeaders } = this.config.websocket;
|
|
240
|
+
logger.info('WS 连接中: ' + url);
|
|
241
|
+
const headers = { 'x-self-name': serverName, ...(extraHeaders || {}) };
|
|
242
|
+
if (accessToken)
|
|
243
|
+
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
244
|
+
const ws = new ws_1.default(url, { headers });
|
|
245
|
+
this.ws = ws;
|
|
246
|
+
ws.on('open', () => {
|
|
247
|
+
logger.info('WS 连接成功');
|
|
248
|
+
});
|
|
249
|
+
ws.on('message', (data) => {
|
|
250
|
+
try {
|
|
251
|
+
const text = data.toString('utf8');
|
|
252
|
+
const obj = JSON.parse(text);
|
|
253
|
+
const type = obj.type || obj.event || 'unknown';
|
|
254
|
+
const payload = obj.data ?? obj;
|
|
255
|
+
const mapped = mapQueqiaoEvent(type, payload);
|
|
256
|
+
this.ctx.emit(`minecraft/${mapped.type}`, mapped.payload);
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
logger.warn('WS 消息解析失败: ' + e?.message);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
ws.on('close', () => {
|
|
263
|
+
logger.warn('WS 连接断开');
|
|
264
|
+
this.ws = undefined;
|
|
265
|
+
this.scheduleWsReconnect();
|
|
266
|
+
});
|
|
267
|
+
ws.on('error', (err) => {
|
|
268
|
+
logger.warn('WS 错误: ' + err?.message);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
disconnectWs() {
|
|
272
|
+
if (this.ws) {
|
|
273
|
+
try {
|
|
274
|
+
this.ws.terminate();
|
|
275
|
+
}
|
|
276
|
+
catch { }
|
|
277
|
+
this.ws = undefined;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
ensureWsConnected() {
|
|
281
|
+
if (this.ws)
|
|
282
|
+
return;
|
|
283
|
+
this.connectWs();
|
|
284
|
+
}
|
|
285
|
+
scheduleWsReconnect() {
|
|
286
|
+
const base = this.config.websocket?.reconnectInterval ?? 5000;
|
|
287
|
+
const strategy = this.config.websocket?.reconnectStrategy ?? 'fixed';
|
|
288
|
+
const maxInterval = this.config.websocket?.maxReconnectInterval ?? 60000;
|
|
289
|
+
if (this.wsCurrentInterval == null)
|
|
290
|
+
this.wsCurrentInterval = base;
|
|
291
|
+
const interval = strategy === 'exponential' ? Math.min(this.wsCurrentInterval * 2, maxInterval) : base;
|
|
292
|
+
if (!this.config.websocket?.enabled)
|
|
293
|
+
return;
|
|
294
|
+
if (this.wsReconnectTimer)
|
|
295
|
+
return;
|
|
296
|
+
this.wsReconnectTimer = setTimeout(() => {
|
|
297
|
+
this.wsReconnectTimer = undefined;
|
|
298
|
+
this.wsCurrentInterval = interval;
|
|
299
|
+
this.ensureWsConnected();
|
|
300
|
+
}, interval);
|
|
301
|
+
}
|
|
141
302
|
async execute(command) {
|
|
142
303
|
if (!this.config.rcon?.enabled)
|
|
143
304
|
throw new Error('RCON 未启用');
|
|
@@ -169,6 +330,42 @@ class MinecraftService extends koishi_1.Service {
|
|
|
169
330
|
this.processQueue();
|
|
170
331
|
});
|
|
171
332
|
}
|
|
333
|
+
// ========== WS 发送:优先 WS,不可用则回退 RCON ==========
|
|
334
|
+
async wsBroadcast(message) {
|
|
335
|
+
if (this.ws && this.config.websocket?.enabled) {
|
|
336
|
+
this.ws.send(JSON.stringify(buildWsOutboundPayload(this.config, 'broadcast', { message })));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
return await this.broadcast(message);
|
|
340
|
+
}
|
|
341
|
+
async wsTell(player, message) {
|
|
342
|
+
if (this.ws && this.config.websocket?.enabled) {
|
|
343
|
+
this.ws.send(JSON.stringify(buildWsOutboundPayload(this.config, 'tell', { player, message })));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
return await this.sendTo(player, message);
|
|
347
|
+
}
|
|
348
|
+
async wsTitle(player, title, subtitle) {
|
|
349
|
+
if (this.ws && this.config.websocket?.enabled) {
|
|
350
|
+
this.ws.send(JSON.stringify(buildWsOutboundPayload(this.config, 'title', { player, title, subtitle })));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
// RCON 回退
|
|
354
|
+
await this.execute(`title ${player} title ${JSON.stringify({ text: title })}`);
|
|
355
|
+
if (subtitle) {
|
|
356
|
+
await this.execute(`title ${player} subtitle ${JSON.stringify({ text: subtitle })}`);
|
|
357
|
+
}
|
|
358
|
+
return 'OK';
|
|
359
|
+
}
|
|
360
|
+
async wsActionbar(player, message) {
|
|
361
|
+
if (this.ws && this.config.websocket?.enabled) {
|
|
362
|
+
this.ws.send(JSON.stringify(buildWsOutboundPayload(this.config, 'actionbar', { player, message })));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// RCON 回退
|
|
366
|
+
await this.execute(`title ${player} actionbar ${JSON.stringify({ text: message })}`);
|
|
367
|
+
return 'OK';
|
|
368
|
+
}
|
|
172
369
|
async processQueue() {
|
|
173
370
|
if (this.isProcessingQueue)
|
|
174
371
|
return;
|
|
@@ -233,6 +430,29 @@ function escapeForMc(text) {
|
|
|
233
430
|
signatureHeader: 'x-queqiao-signature',
|
|
234
431
|
secretHeader: 'x-queqiao-secret',
|
|
235
432
|
}),
|
|
433
|
+
websocket: koishi_1.Schema.object({
|
|
434
|
+
enabled: koishi_1.Schema.boolean().description('启用鹊桥 WebSocket').default(false),
|
|
435
|
+
url: koishi_1.Schema.string().description('WebSocket 地址,如 ws://host:port').required(),
|
|
436
|
+
serverName: koishi_1.Schema.string().description('服务器名称,需与鹊桥 config.yml 的 server_name 一致').required(),
|
|
437
|
+
accessToken: koishi_1.Schema.string().role('secret').description('访问令牌,对应鹊桥 config.yml 的 access_token,可为空'),
|
|
438
|
+
extraHeaders: koishi_1.Schema.dict(String).description('附加请求头,可选'),
|
|
439
|
+
reconnectInterval: koishi_1.Schema.number().description('断线重连基础间隔(ms)').default(5000),
|
|
440
|
+
reconnectStrategy: koishi_1.Schema.union(['fixed', 'exponential']).description('重连策略').default('fixed'),
|
|
441
|
+
maxReconnectInterval: koishi_1.Schema.number().description('最大重连间隔(ms)').default(60000),
|
|
442
|
+
}).description('WebSocket 设置').default({
|
|
443
|
+
enabled: false,
|
|
444
|
+
url: '',
|
|
445
|
+
serverName: 'Server-1',
|
|
446
|
+
accessToken: '',
|
|
447
|
+
extraHeaders: {},
|
|
448
|
+
reconnectInterval: 5000,
|
|
449
|
+
reconnectStrategy: 'fixed',
|
|
450
|
+
maxReconnectInterval: 60000,
|
|
451
|
+
}),
|
|
452
|
+
commands: koishi_1.Schema.object({
|
|
453
|
+
enabled: koishi_1.Schema.boolean().description('注册 Koishi 指令: mc.exec/mc.say/mc.tell').default(true),
|
|
454
|
+
authority: koishi_1.Schema.number().description('执行这些指令所需的权限等级').default(2),
|
|
455
|
+
}).description('命令设置').default({ enabled: true, authority: 2 }),
|
|
236
456
|
});
|
|
237
457
|
})(MinecraftService || (MinecraftService = {}));
|
|
238
458
|
exports.default = MinecraftService;
|
|
@@ -279,3 +499,7 @@ function mapQueqiaoEvent(type, data) {
|
|
|
279
499
|
return { type, payload: data };
|
|
280
500
|
}
|
|
281
501
|
}
|
|
502
|
+
function buildWsOutboundPayload(config, api, data) {
|
|
503
|
+
// 参考 QueQiao 的 WS 入站接口语义,采用 { api, data } 基本结构
|
|
504
|
+
return { api, data };
|
|
505
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-minecraft-adapter",
|
|
3
3
|
"description": "Minecraft RCON & webhook integration for Koishi",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"koishi": "^4.17.9"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"rcon-client": "^4.2.5"
|
|
25
|
+
"rcon-client": "^4.2.5",
|
|
26
|
+
"ws": "^8.18.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"typescript": "^5.0.0"
|