koishi-plugin-minecraft-adapter 0.4.9 → 0.5.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/lib/index.d.ts +1 -0
- package/lib/index.js +107 -59
- package/package.json +54 -54
package/lib/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export declare class MinecraftAdapter<C extends Context = Context> extends Adapt
|
|
|
38
38
|
private reconnectInterval;
|
|
39
39
|
private maxReconnectAttempts;
|
|
40
40
|
constructor(ctx: C, config: MinecraftAdapterConfig);
|
|
41
|
+
private serializeOutgoingMessage;
|
|
41
42
|
private getWebSocketCloseCode;
|
|
42
43
|
private getWebSocketStateString;
|
|
43
44
|
private connectWebSocket;
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.MinecraftAdapter = exports.MinecraftBot = void 0;
|
|
7
7
|
const koishi_1 = require("koishi");
|
|
8
|
-
const element_1 = __importDefault(require("@satorijs/element"));
|
|
9
8
|
const rcon_client_1 = require("rcon-client");
|
|
10
9
|
const ws_1 = __importDefault(require("ws"));
|
|
11
10
|
const logger = new koishi_1.Logger('minecraft');
|
|
@@ -52,71 +51,105 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
52
51
|
maxReconnectAttempts;
|
|
53
52
|
constructor(ctx, config) {
|
|
54
53
|
super(ctx);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
logger.info(`[DEBUG] MinecraftAdapter initialized with config:`, {
|
|
60
|
-
debug: this.debug,
|
|
61
|
-
reconnectInterval: this.reconnectInterval,
|
|
62
|
-
maxReconnectAttempts: this.maxReconnectAttempts,
|
|
63
|
-
botCount: config.bots.length
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
// 为每个配置创建机器人
|
|
67
|
-
ctx.on('ready', async () => {
|
|
54
|
+
try {
|
|
55
|
+
this.debug = config.debug ?? false;
|
|
56
|
+
this.reconnectInterval = config.reconnectInterval ?? 5000;
|
|
57
|
+
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 10;
|
|
68
58
|
if (this.debug) {
|
|
69
|
-
logger.info(`[DEBUG]
|
|
59
|
+
logger.info(`[DEBUG] MinecraftAdapter initialized with config:`, {
|
|
60
|
+
debug: this.debug,
|
|
61
|
+
reconnectInterval: this.reconnectInterval,
|
|
62
|
+
maxReconnectAttempts: this.maxReconnectAttempts,
|
|
63
|
+
botCount: config.bots.length
|
|
64
|
+
});
|
|
70
65
|
}
|
|
71
|
-
|
|
66
|
+
// 为每个配置创建机器人
|
|
67
|
+
ctx.on('ready', async () => {
|
|
72
68
|
if (this.debug) {
|
|
73
|
-
logger.info(`[DEBUG]
|
|
69
|
+
logger.info(`[DEBUG] Koishi ready event triggered, initializing ${config.bots.length} bots`);
|
|
74
70
|
}
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
for (const botConfig of config.bots) {
|
|
72
|
+
if (this.debug) {
|
|
73
|
+
logger.info(`[DEBUG] Initializing bot ${botConfig.selfId}`);
|
|
74
|
+
}
|
|
75
|
+
const bot = new MinecraftBot(ctx, botConfig);
|
|
76
|
+
bot.adapter = this;
|
|
77
|
+
this.bots.push(bot);
|
|
78
|
+
// 初始化 RCON 连接
|
|
79
|
+
if (botConfig.rcon) {
|
|
80
|
+
try {
|
|
81
|
+
if (this.debug) {
|
|
82
|
+
logger.info(`[DEBUG] Connecting RCON for bot ${botConfig.selfId} to ${botConfig.rcon.host}:${botConfig.rcon.port}`);
|
|
83
|
+
}
|
|
84
|
+
const rcon = await rcon_client_1.Rcon.connect({
|
|
85
|
+
host: botConfig.rcon.host,
|
|
86
|
+
port: botConfig.rcon.port,
|
|
87
|
+
password: botConfig.rcon.password,
|
|
88
|
+
timeout: botConfig.rcon.timeout || 5000,
|
|
89
|
+
});
|
|
90
|
+
this.rconConnections.set(botConfig.selfId, rcon);
|
|
91
|
+
bot.rcon = rcon;
|
|
92
|
+
logger.info(`RCON connected for bot ${botConfig.selfId}`);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
logger.warn(`Failed to connect RCON for bot ${botConfig.selfId}:`, error);
|
|
96
|
+
if (this.debug) {
|
|
97
|
+
logger.info(`[DEBUG] RCON connection error details:`, error.message, error.stack);
|
|
98
|
+
}
|
|
83
99
|
}
|
|
84
|
-
const rcon = await rcon_client_1.Rcon.connect({
|
|
85
|
-
host: botConfig.rcon.host,
|
|
86
|
-
port: botConfig.rcon.port,
|
|
87
|
-
password: botConfig.rcon.password,
|
|
88
|
-
timeout: botConfig.rcon.timeout || 5000,
|
|
89
|
-
});
|
|
90
|
-
this.rconConnections.set(botConfig.selfId, rcon);
|
|
91
|
-
bot.rcon = rcon;
|
|
92
|
-
logger.info(`RCON connected for bot ${botConfig.selfId}`);
|
|
93
100
|
}
|
|
94
|
-
|
|
95
|
-
logger.warn(`Failed to connect RCON for bot ${botConfig.selfId}:`, error);
|
|
101
|
+
else {
|
|
96
102
|
if (this.debug) {
|
|
97
|
-
logger.info(`[DEBUG] RCON
|
|
103
|
+
logger.info(`[DEBUG] No RCON config for bot ${botConfig.selfId}`);
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
// 初始化 WebSocket 连接
|
|
107
|
+
if (botConfig.websocket) {
|
|
108
|
+
if (this.debug) {
|
|
109
|
+
logger.info(`[DEBUG] Initializing WebSocket for bot ${botConfig.selfId}`);
|
|
110
|
+
}
|
|
111
|
+
await this.connectWebSocket(bot, botConfig.websocket);
|
|
104
112
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
logger.info(`[DEBUG] Initializing WebSocket for bot ${botConfig.selfId}`);
|
|
113
|
+
else {
|
|
114
|
+
if (this.debug) {
|
|
115
|
+
logger.info(`[DEBUG] No WebSocket config for bot ${botConfig.selfId}`);
|
|
116
|
+
}
|
|
110
117
|
}
|
|
111
|
-
await this.connectWebSocket(bot, botConfig.websocket);
|
|
112
118
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
logger.error('MinecraftAdapter initialization failed:', err);
|
|
123
|
+
// rethrow so Koishi can report the plugin load failure with stack
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// 将 message 元素序列化为字符串或简单数组,避免将 Element 实例原样 JSON.stringify 导致丢失内容
|
|
128
|
+
serializeOutgoingMessage(message) {
|
|
129
|
+
if (message == null)
|
|
130
|
+
return message;
|
|
131
|
+
if (typeof message === 'string')
|
|
132
|
+
return message;
|
|
133
|
+
if (Array.isArray(message)) {
|
|
134
|
+
return message.map((item) => {
|
|
135
|
+
if (item == null)
|
|
136
|
+
return item;
|
|
137
|
+
if (typeof item === 'string')
|
|
138
|
+
return item;
|
|
139
|
+
if (typeof item === 'object') {
|
|
140
|
+
// satori Element-like
|
|
141
|
+
if (item.attrs)
|
|
142
|
+
return item.attrs.content ?? JSON.stringify(item);
|
|
143
|
+
return item.content ?? item.text ?? JSON.stringify(item);
|
|
117
144
|
}
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
return String(item);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (typeof message === 'object') {
|
|
149
|
+
// 支持 satori Element-like 对象以及常见的 text/content 字段
|
|
150
|
+
return message.content ?? (message.attrs && message.attrs.content) ?? message.text ?? JSON.stringify(message);
|
|
151
|
+
}
|
|
152
|
+
return String(message);
|
|
120
153
|
}
|
|
121
154
|
getWebSocketCloseCode(code) {
|
|
122
155
|
const codes = {
|
|
@@ -375,12 +408,13 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
375
408
|
id: payload.server_name || 'minecraft',
|
|
376
409
|
name: payload.server_name || 'Minecraft Server',
|
|
377
410
|
};
|
|
411
|
+
// 将元素设为字符串数组(简洁、可靠):Session.content getter 会对 elements.join("") 返回文本
|
|
378
412
|
event.message = {
|
|
379
413
|
id: payload.message_id || Date.now().toString(),
|
|
380
414
|
content: payload.message || '',
|
|
381
415
|
timestamp: (payload.timestamp || Date.now()) * 1000,
|
|
382
416
|
user: event.user, // 现在 event.user 已经定义了
|
|
383
|
-
elements: payload.message ? [
|
|
417
|
+
elements: payload.message ? [payload.message] : [],
|
|
384
418
|
createdAt: (payload.timestamp || Date.now()) * 1000,
|
|
385
419
|
updatedAt: (payload.timestamp || Date.now()) * 1000,
|
|
386
420
|
};
|
|
@@ -435,11 +469,13 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
435
469
|
logger.info(`[DEBUG] Sending private message to player ${player}: ${message}`);
|
|
436
470
|
}
|
|
437
471
|
// 优先使用 WebSocket 发送
|
|
472
|
+
const serialized = this.serializeOutgoingMessage(message);
|
|
438
473
|
for (const [botId, ws] of this.wsConnections) {
|
|
439
474
|
if (ws.readyState === ws_1.default.OPEN) {
|
|
475
|
+
const wsMessage = Array.isArray(serialized) ? serialized.join('') : serialized;
|
|
440
476
|
const payload = {
|
|
441
477
|
api: 'tell',
|
|
442
|
-
data: { player, message }
|
|
478
|
+
data: { player, message: wsMessage }
|
|
443
479
|
};
|
|
444
480
|
if (this.debug) {
|
|
445
481
|
logger.info(`[DEBUG] Sending via WebSocket:`, payload);
|
|
@@ -451,7 +487,15 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
451
487
|
// 回退到 RCON
|
|
452
488
|
for (const [botId, rcon] of this.rconConnections) {
|
|
453
489
|
try {
|
|
454
|
-
|
|
490
|
+
// RCON tellraw expects a JSON text component array
|
|
491
|
+
let components;
|
|
492
|
+
if (Array.isArray(serialized)) {
|
|
493
|
+
components = serialized.map((s) => ({ text: String(s) }));
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
components = [{ text: String(serialized) }];
|
|
497
|
+
}
|
|
498
|
+
const json = JSON.stringify(components);
|
|
455
499
|
if (this.debug) {
|
|
456
500
|
logger.info(`[DEBUG] Sending via RCON: tellraw ${player} ${json}`);
|
|
457
501
|
}
|
|
@@ -469,11 +513,13 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
469
513
|
logger.info(`[DEBUG] Broadcasting message: ${message}`);
|
|
470
514
|
}
|
|
471
515
|
// 优先使用 WebSocket 发送
|
|
516
|
+
const serialized = this.serializeOutgoingMessage(message);
|
|
472
517
|
for (const [botId, ws] of this.wsConnections) {
|
|
473
518
|
if (ws.readyState === ws_1.default.OPEN) {
|
|
519
|
+
const wsMessage = Array.isArray(serialized) ? serialized.join('') : serialized;
|
|
474
520
|
const payload = {
|
|
475
521
|
api: 'broadcast',
|
|
476
|
-
data: { message }
|
|
522
|
+
data: { message: wsMessage }
|
|
477
523
|
};
|
|
478
524
|
if (this.debug) {
|
|
479
525
|
logger.info(`[DEBUG] Broadcasting via WebSocket:`, payload);
|
|
@@ -485,10 +531,12 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
485
531
|
// 回退到 RCON
|
|
486
532
|
for (const [botId, rcon] of this.rconConnections) {
|
|
487
533
|
try {
|
|
534
|
+
// RCON say expects a plain string
|
|
535
|
+
const text = Array.isArray(serialized) ? serialized.join('') : String(serialized);
|
|
488
536
|
if (this.debug) {
|
|
489
|
-
logger.info(`[DEBUG] Broadcasting via RCON: say ${
|
|
537
|
+
logger.info(`[DEBUG] Broadcasting via RCON: say ${text}`);
|
|
490
538
|
}
|
|
491
|
-
await rcon.send(`say ${
|
|
539
|
+
await rcon.send(`say ${text}`);
|
|
492
540
|
return;
|
|
493
541
|
}
|
|
494
542
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "koishi-plugin-minecraft-adapter",
|
|
3
|
-
"description": "Minecraft adapter for Koishi",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"typings": "lib/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"lib",
|
|
9
|
-
"dist"
|
|
10
|
-
],
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"author": "",
|
|
13
|
-
"repository": {
|
|
14
|
-
"type": "git",
|
|
15
|
-
"url": ""
|
|
16
|
-
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsc",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
20
|
-
},
|
|
21
|
-
"peerDependencies": {
|
|
22
|
-
"koishi": "^4.17.9"
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"rcon-client": "^4.2.5",
|
|
26
|
-
"ws": "^8.18.0"
|
|
27
|
-
},
|
|
28
|
-
"devDependencies": {
|
|
29
|
-
"typescript": "^5.0.0"
|
|
30
|
-
},
|
|
31
|
-
"publishConfig": {
|
|
32
|
-
"access": "public"
|
|
33
|
-
},
|
|
34
|
-
"keywords": [
|
|
35
|
-
"koishi",
|
|
36
|
-
"plugin",
|
|
37
|
-
"minecraft",
|
|
38
|
-
"rcon",
|
|
39
|
-
"websocket",
|
|
40
|
-
"adapter"
|
|
41
|
-
],
|
|
42
|
-
"koishi": {
|
|
43
|
-
"preview": true,
|
|
44
|
-
"service": {
|
|
45
|
-
"implements": [
|
|
46
|
-
"adapter"
|
|
47
|
-
]
|
|
48
|
-
},
|
|
49
|
-
"description": {
|
|
50
|
-
"en": "Minecraft adapter for Koishi with RCON and WebSocket support",
|
|
51
|
-
"zh": "Koishi
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-minecraft-adapter",
|
|
3
|
+
"description": "Minecraft adapter for Koishi",
|
|
4
|
+
"version": "0.5.0",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": ""
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"koishi": "^4.17.9"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"rcon-client": "^4.2.5",
|
|
26
|
+
"ws": "^8.18.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"koishi",
|
|
36
|
+
"plugin",
|
|
37
|
+
"minecraft",
|
|
38
|
+
"rcon",
|
|
39
|
+
"websocket",
|
|
40
|
+
"adapter"
|
|
41
|
+
],
|
|
42
|
+
"koishi": {
|
|
43
|
+
"preview": true,
|
|
44
|
+
"service": {
|
|
45
|
+
"implements": [
|
|
46
|
+
"adapter"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"description": {
|
|
50
|
+
"en": "Minecraft adapter for Koishi with RCON and WebSocket support",
|
|
51
|
+
"zh": "Koishi �?Minecraft 适配器,支持 RCON �?WebSocket"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|