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.
Files changed (3) hide show
  1. package/lib/index.d.ts +1 -0
  2. package/lib/index.js +107 -59
  3. 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
- this.debug = config.debug ?? false;
56
- this.reconnectInterval = config.reconnectInterval ?? 5000;
57
- this.maxReconnectAttempts = config.maxReconnectAttempts ?? 10;
58
- if (this.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
- });
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] Koishi ready event triggered, initializing ${config.bots.length} bots`);
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
- for (const botConfig of config.bots) {
66
+ // 为每个配置创建机器人
67
+ ctx.on('ready', async () => {
72
68
  if (this.debug) {
73
- logger.info(`[DEBUG] Initializing bot ${botConfig.selfId}`);
69
+ logger.info(`[DEBUG] Koishi ready event triggered, initializing ${config.bots.length} bots`);
74
70
  }
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}`);
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
- catch (error) {
95
- logger.warn(`Failed to connect RCON for bot ${botConfig.selfId}:`, error);
101
+ else {
96
102
  if (this.debug) {
97
- logger.info(`[DEBUG] RCON connection error details:`, error.message, error.stack);
103
+ logger.info(`[DEBUG] No RCON config for bot ${botConfig.selfId}`);
98
104
  }
99
105
  }
100
- }
101
- else {
102
- if (this.debug) {
103
- logger.info(`[DEBUG] No RCON config for bot ${botConfig.selfId}`);
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
- // 初始化 WebSocket 连接
107
- if (botConfig.websocket) {
108
- if (this.debug) {
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
- else {
114
- if (this.debug) {
115
- logger.info(`[DEBUG] No WebSocket config for bot ${botConfig.selfId}`);
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 ? [(0, element_1.default)('text', { content: 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
- const json = JSON.stringify([{ text: message }]);
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 ${message}`);
537
+ logger.info(`[DEBUG] Broadcasting via RCON: say ${text}`);
490
538
  }
491
- await rcon.send(`say ${message}`);
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.4.9",
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
- }
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
+ }