koishi-plugin-minecraft-adapter 1.0.0 → 1.0.2
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 +51 -6
- package/lib/index.js +190 -60
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -27,10 +27,32 @@ export interface QueqiaoPlayer {
|
|
|
27
27
|
health?: number;
|
|
28
28
|
max_health?: number;
|
|
29
29
|
experience_level?: number;
|
|
30
|
+
experience_progress?: number;
|
|
31
|
+
total_experience?: number;
|
|
32
|
+
walk_speed?: number;
|
|
30
33
|
x?: number;
|
|
31
34
|
y?: number;
|
|
32
35
|
z?: number;
|
|
33
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* 鹊桥 V2 Death 对象
|
|
39
|
+
*/
|
|
40
|
+
export interface QueqiaoDeath {
|
|
41
|
+
key?: string;
|
|
42
|
+
args?: string;
|
|
43
|
+
text?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 鹊桥 V2 Achievement 对象
|
|
47
|
+
*/
|
|
48
|
+
export interface QueqiaoAchievement {
|
|
49
|
+
display?: {
|
|
50
|
+
title?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
frame?: string;
|
|
53
|
+
};
|
|
54
|
+
text?: string;
|
|
55
|
+
}
|
|
34
56
|
/**
|
|
35
57
|
* 鹊桥 V2 事件基础结构
|
|
36
58
|
*/
|
|
@@ -49,7 +71,7 @@ export interface PlayerChatEvent extends QueqiaoEventBase {
|
|
|
49
71
|
post_type: 'message';
|
|
50
72
|
event_name: 'PlayerChatEvent';
|
|
51
73
|
message: string;
|
|
52
|
-
|
|
74
|
+
rawMessage?: string;
|
|
53
75
|
message_id?: string;
|
|
54
76
|
player: QueqiaoPlayer;
|
|
55
77
|
}
|
|
@@ -60,7 +82,7 @@ export interface PlayerCommandEvent extends QueqiaoEventBase {
|
|
|
60
82
|
post_type: 'message';
|
|
61
83
|
event_name: 'PlayerCommandEvent';
|
|
62
84
|
command: string;
|
|
63
|
-
|
|
85
|
+
rawMessage?: string;
|
|
64
86
|
message_id?: string;
|
|
65
87
|
player: QueqiaoPlayer;
|
|
66
88
|
}
|
|
@@ -89,7 +111,7 @@ export interface PlayerDeathEvent extends QueqiaoEventBase {
|
|
|
89
111
|
post_type: 'notice';
|
|
90
112
|
event_name: 'PlayerDeathEvent';
|
|
91
113
|
sub_type: 'player_death';
|
|
92
|
-
|
|
114
|
+
death?: QueqiaoDeath;
|
|
93
115
|
player: QueqiaoPlayer;
|
|
94
116
|
}
|
|
95
117
|
/**
|
|
@@ -99,7 +121,7 @@ export interface PlayerAchievementEvent extends QueqiaoEventBase {
|
|
|
99
121
|
post_type: 'notice';
|
|
100
122
|
event_name: 'PlayerAchievementEvent';
|
|
101
123
|
sub_type: 'player_achievement';
|
|
102
|
-
achievement?:
|
|
124
|
+
achievement?: QueqiaoAchievement;
|
|
103
125
|
player: QueqiaoPlayer;
|
|
104
126
|
}
|
|
105
127
|
export type QueqiaoEvent = PlayerChatEvent | PlayerCommandEvent | PlayerJoinEvent | PlayerQuitEvent | PlayerDeathEvent | PlayerAchievementEvent;
|
|
@@ -156,6 +178,12 @@ export declare class MinecraftBot<C extends Context = Context> extends Bot<C, Mi
|
|
|
156
178
|
*/
|
|
157
179
|
executeCommand(command: string): Promise<string>;
|
|
158
180
|
}
|
|
181
|
+
export interface ChatImageConfig {
|
|
182
|
+
/** 是否启用 ChatImage CICode 生成(出站方向),默认关闭 */
|
|
183
|
+
enabled?: boolean;
|
|
184
|
+
/** 图片在聊天栏中的默认显示名称 */
|
|
185
|
+
defaultImageName?: string;
|
|
186
|
+
}
|
|
159
187
|
export interface MinecraftAdapterConfig {
|
|
160
188
|
bots: MinecraftBotConfig[];
|
|
161
189
|
debug?: boolean;
|
|
@@ -165,8 +193,11 @@ export interface MinecraftAdapterConfig {
|
|
|
165
193
|
maxReconnectAttempts?: number;
|
|
166
194
|
/** 是否在消息前添加默认前缀 [鹊桥],默认不添加(由服务端配置) */
|
|
167
195
|
useMessagePrefix?: boolean;
|
|
196
|
+
/** ChatImage 集成配置 */
|
|
197
|
+
chatImage?: ChatImageConfig;
|
|
168
198
|
}
|
|
169
199
|
export declare class MinecraftAdapter<C extends Context = Context> extends Adapter<C, MinecraftBot<C>> {
|
|
200
|
+
static reusable: boolean;
|
|
170
201
|
private rconConnections;
|
|
171
202
|
private wsConnections;
|
|
172
203
|
private reconnectAttempts;
|
|
@@ -178,15 +209,25 @@ export declare class MinecraftAdapter<C extends Context = Context> extends Adapt
|
|
|
178
209
|
private reconnectInterval;
|
|
179
210
|
private maxReconnectAttempts;
|
|
180
211
|
private useMessagePrefix;
|
|
212
|
+
private chatImageEnabled;
|
|
213
|
+
private chatImageDefaultName;
|
|
181
214
|
constructor(ctx: C, config: MinecraftAdapterConfig);
|
|
182
215
|
/**
|
|
183
216
|
* 生成唯一的请求 ID
|
|
184
217
|
*/
|
|
185
218
|
private generateEcho;
|
|
219
|
+
private toTextComponent;
|
|
220
|
+
private extractRawText;
|
|
186
221
|
/**
|
|
187
|
-
*
|
|
222
|
+
* 生成 ChatImage CICode: [[CICode,url=<url>,name=<name>]]
|
|
188
223
|
*/
|
|
189
|
-
private
|
|
224
|
+
private buildCICode;
|
|
225
|
+
/**
|
|
226
|
+
* 解析出站消息中的 Koishi 元素标签 (<img src="..."/>, <image url="..."/>)
|
|
227
|
+
*/
|
|
228
|
+
private parseOutboundMessage;
|
|
229
|
+
private extractAttr;
|
|
230
|
+
private decodeHtmlEntities;
|
|
190
231
|
/**
|
|
191
232
|
* 发送 WebSocket API 请求并等待响应
|
|
192
233
|
*/
|
|
@@ -198,14 +239,18 @@ export declare class MinecraftAdapter<C extends Context = Context> extends Adapt
|
|
|
198
239
|
private getWebSocketCloseCode;
|
|
199
240
|
private getWebSocketStateString;
|
|
200
241
|
private connectWebSocket;
|
|
242
|
+
private sessionCounter;
|
|
201
243
|
/**
|
|
202
244
|
* 根据鹊桥 V2 事件创建 Koishi Session
|
|
203
245
|
*/
|
|
204
246
|
private createSession;
|
|
205
247
|
/**
|
|
206
248
|
* 解析消息文本为 Koishi 元素数组
|
|
249
|
+
* 入站方向始终解析 CICode 和裸图片 URL(不受 chatImage.enabled 控制)
|
|
207
250
|
*/
|
|
208
251
|
private parseMessageToElements;
|
|
252
|
+
private extractCICodeParam;
|
|
253
|
+
private addTextElements;
|
|
209
254
|
/**
|
|
210
255
|
* 发送私聊消息 (send_private_msg)
|
|
211
256
|
*/
|
package/lib/index.js
CHANGED
|
@@ -14,6 +14,7 @@ class MinecraftBot extends koishi_1.Bot {
|
|
|
14
14
|
constructor(ctx, config) {
|
|
15
15
|
super(ctx, config, 'minecraft');
|
|
16
16
|
this.selfId = config.selfId;
|
|
17
|
+
this.platform = 'minecraft';
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* 发送消息到频道或私聊
|
|
@@ -56,6 +57,7 @@ class MinecraftBot extends koishi_1.Bot {
|
|
|
56
57
|
}
|
|
57
58
|
exports.MinecraftBot = MinecraftBot;
|
|
58
59
|
class MinecraftAdapter extends koishi_1.Adapter {
|
|
60
|
+
static reusable = true;
|
|
59
61
|
rconConnections = new Map();
|
|
60
62
|
wsConnections = new Map();
|
|
61
63
|
reconnectAttempts = new Map();
|
|
@@ -67,6 +69,8 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
67
69
|
reconnectInterval;
|
|
68
70
|
maxReconnectAttempts;
|
|
69
71
|
useMessagePrefix;
|
|
72
|
+
chatImageEnabled;
|
|
73
|
+
chatImageDefaultName;
|
|
70
74
|
constructor(ctx, config) {
|
|
71
75
|
super(ctx);
|
|
72
76
|
try {
|
|
@@ -76,6 +80,8 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
76
80
|
this.reconnectInterval = config.reconnectInterval ?? 5000;
|
|
77
81
|
this.maxReconnectAttempts = config.maxReconnectAttempts ?? 10;
|
|
78
82
|
this.useMessagePrefix = config.useMessagePrefix ?? false;
|
|
83
|
+
this.chatImageEnabled = config.chatImage?.enabled ?? false;
|
|
84
|
+
this.chatImageDefaultName = config.chatImage?.defaultImageName ?? '图片';
|
|
79
85
|
if (this.debug) {
|
|
80
86
|
logger.info(`[DEBUG] MinecraftAdapter initialized with config:`, {
|
|
81
87
|
debug: this.debug,
|
|
@@ -97,7 +103,7 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
97
103
|
bot.adapter = this;
|
|
98
104
|
this.bots.push(bot);
|
|
99
105
|
// 初始化 RCON 连接
|
|
100
|
-
if (botConfig.rcon) {
|
|
106
|
+
if (botConfig.rcon && botConfig.rcon.host && botConfig.rcon.port && botConfig.rcon.password) {
|
|
101
107
|
try {
|
|
102
108
|
if (this.debug) {
|
|
103
109
|
logger.info(`[DEBUG] Connecting RCON for bot ${botConfig.selfId} to ${botConfig.rcon.host}:${botConfig.rcon.port}`);
|
|
@@ -121,7 +127,12 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
121
127
|
}
|
|
122
128
|
else {
|
|
123
129
|
if (this.debug) {
|
|
124
|
-
|
|
130
|
+
if (botConfig.rcon) {
|
|
131
|
+
logger.info(`[DEBUG] RCON config incomplete for bot ${botConfig.selfId}, skipping (need host, port, password)`);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
logger.info(`[DEBUG] No RCON config for bot ${botConfig.selfId}`);
|
|
135
|
+
}
|
|
125
136
|
}
|
|
126
137
|
}
|
|
127
138
|
// 初始化 WebSocket 连接
|
|
@@ -150,53 +161,115 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
150
161
|
generateEcho() {
|
|
151
162
|
return `koishi_${Date.now()}_${++this.requestCounter}`;
|
|
152
163
|
}
|
|
153
|
-
/**
|
|
154
|
-
* 将消息转换为 Minecraft 文本组件格式
|
|
155
|
-
*/
|
|
156
164
|
toTextComponent(message) {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
165
|
+
const raw = this.extractRawText(message);
|
|
166
|
+
if (!raw)
|
|
167
|
+
return [{ text: '' }];
|
|
168
|
+
const segments = this.parseOutboundMessage(raw);
|
|
169
|
+
if (segments.length === 0)
|
|
170
|
+
return [{ text: '' }];
|
|
171
|
+
const fullText = segments.map(seg => {
|
|
172
|
+
if (seg.type === 'image') {
|
|
173
|
+
if (this.chatImageEnabled) {
|
|
174
|
+
return this.buildCICode(seg.url, seg.name);
|
|
175
|
+
}
|
|
176
|
+
return seg.url;
|
|
177
|
+
}
|
|
178
|
+
return seg.text;
|
|
179
|
+
}).join('');
|
|
180
|
+
return [{ text: fullText }];
|
|
181
|
+
}
|
|
182
|
+
extractRawText(message) {
|
|
183
|
+
if (message == null)
|
|
184
|
+
return '';
|
|
185
|
+
if (typeof message === 'string')
|
|
186
|
+
return message;
|
|
187
|
+
if (typeof message === 'number' || typeof message === 'boolean')
|
|
188
|
+
return String(message);
|
|
189
|
+
if (Array.isArray(message))
|
|
190
|
+
return message.map(item => this.extractRawText(item)).join('');
|
|
191
|
+
if (typeof message === 'object') {
|
|
192
|
+
if (message.attrs && typeof message.attrs.content === 'string')
|
|
193
|
+
return message.attrs.content;
|
|
194
|
+
if (typeof message.content === 'string')
|
|
195
|
+
return message.content;
|
|
196
|
+
if (typeof message.text === 'string')
|
|
197
|
+
return message.text;
|
|
198
|
+
if (message.children)
|
|
199
|
+
return this.extractRawText(message.children);
|
|
200
|
+
try {
|
|
201
|
+
if (typeof message.toString === 'function' && message.toString !== Object.prototype.toString) {
|
|
202
|
+
const s = message.toString();
|
|
203
|
+
if (typeof s === 'string' && s !== '[object Object]')
|
|
204
|
+
return s;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
// ignore
|
|
209
|
+
}
|
|
210
|
+
let acc = '';
|
|
211
|
+
for (const key in message) {
|
|
175
212
|
try {
|
|
176
|
-
|
|
177
|
-
const s = item.toString();
|
|
178
|
-
if (typeof s === 'string' && s !== '[object Object]')
|
|
179
|
-
return s;
|
|
180
|
-
}
|
|
213
|
+
acc += this.extractRawText(message[key]);
|
|
181
214
|
}
|
|
182
215
|
catch (e) {
|
|
183
216
|
// ignore
|
|
184
217
|
}
|
|
185
|
-
let acc = '';
|
|
186
|
-
for (const key in item) {
|
|
187
|
-
try {
|
|
188
|
-
acc += extractText(item[key]);
|
|
189
|
-
}
|
|
190
|
-
catch (e) {
|
|
191
|
-
// ignore
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return acc;
|
|
195
218
|
}
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
219
|
+
return acc;
|
|
220
|
+
}
|
|
221
|
+
return String(message);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 生成 ChatImage CICode: [[CICode,url=<url>,name=<name>]]
|
|
225
|
+
*/
|
|
226
|
+
buildCICode(url, name) {
|
|
227
|
+
const displayName = name || this.chatImageDefaultName;
|
|
228
|
+
return `[[CICode,url=${url},name=${displayName}]]`;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 解析出站消息中的 Koishi 元素标签 (<img src="..."/>, <image url="..."/>)
|
|
232
|
+
*/
|
|
233
|
+
parseOutboundMessage(content) {
|
|
234
|
+
const segments = [];
|
|
235
|
+
const imgTagRegex = /<(?:img|image)\s+([^>]*?)\/?>(?:<\/(?:img|image)>)?/gi;
|
|
236
|
+
let lastIndex = 0;
|
|
237
|
+
let match;
|
|
238
|
+
while ((match = imgTagRegex.exec(content)) !== null) {
|
|
239
|
+
if (match.index > lastIndex) {
|
|
240
|
+
segments.push({ type: 'text', text: content.slice(lastIndex, match.index) });
|
|
241
|
+
}
|
|
242
|
+
const attrs = match[1];
|
|
243
|
+
const rawUrl = this.extractAttr(attrs, 'src') || this.extractAttr(attrs, 'url');
|
|
244
|
+
if (rawUrl) {
|
|
245
|
+
const url = this.decodeHtmlEntities(rawUrl);
|
|
246
|
+
const name = this.extractAttr(attrs, 'alt') || this.extractAttr(attrs, 'name') || this.extractAttr(attrs, 'summary');
|
|
247
|
+
segments.push({ type: 'image', url, name: name || undefined });
|
|
248
|
+
}
|
|
249
|
+
lastIndex = match.index + match[0].length;
|
|
250
|
+
}
|
|
251
|
+
if (lastIndex < content.length) {
|
|
252
|
+
segments.push({ type: 'text', text: content.slice(lastIndex) });
|
|
253
|
+
}
|
|
254
|
+
return segments;
|
|
255
|
+
}
|
|
256
|
+
// 从 HTML 属性字符串中提取指定属性值: name="val" | name='val' | name=val
|
|
257
|
+
extractAttr(attrs, name) {
|
|
258
|
+
const regex = new RegExp(`${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|(\\S+))`, 'i');
|
|
259
|
+
const match = regex.exec(attrs);
|
|
260
|
+
if (!match)
|
|
261
|
+
return null;
|
|
262
|
+
return match[1] ?? match[2] ?? match[3] ?? null;
|
|
263
|
+
}
|
|
264
|
+
decodeHtmlEntities(str) {
|
|
265
|
+
return str
|
|
266
|
+
.replace(/&/g, '&')
|
|
267
|
+
.replace(/</g, '<')
|
|
268
|
+
.replace(/>/g, '>')
|
|
269
|
+
.replace(/"/g, '"')
|
|
270
|
+
.replace(/'/g, "'")
|
|
271
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
272
|
+
.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
|
|
200
273
|
}
|
|
201
274
|
/**
|
|
202
275
|
* 发送 WebSocket API 请求并等待响应
|
|
@@ -393,6 +466,7 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
393
466
|
}
|
|
394
467
|
});
|
|
395
468
|
}
|
|
469
|
+
sessionCounter = 0;
|
|
396
470
|
/**
|
|
397
471
|
* 根据鹊桥 V2 事件创建 Koishi Session
|
|
398
472
|
*/
|
|
@@ -401,10 +475,10 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
401
475
|
logger.info(`[DEBUG] Creating session for event: ${payload.event_name}, payload:`, payload);
|
|
402
476
|
}
|
|
403
477
|
const event = {
|
|
404
|
-
sn:
|
|
478
|
+
sn: ++this.sessionCounter,
|
|
405
479
|
login: {
|
|
406
480
|
sn: bot.sn,
|
|
407
|
-
adapter:
|
|
481
|
+
adapter: bot.adapterName,
|
|
408
482
|
user: bot.user || { id: bot.selfId, name: bot.selfId },
|
|
409
483
|
platform: 'minecraft',
|
|
410
484
|
selfId: bot.selfId,
|
|
@@ -551,11 +625,12 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
551
625
|
id: deathEvent.server_name || 'minecraft',
|
|
552
626
|
name: deathEvent.server_name || 'Minecraft Server',
|
|
553
627
|
};
|
|
554
|
-
//
|
|
555
|
-
|
|
628
|
+
// 从 death 对象提取死亡消息
|
|
629
|
+
const deathText = deathEvent.death?.text || '';
|
|
630
|
+
if (deathText) {
|
|
556
631
|
event.message = {
|
|
557
632
|
id: Date.now().toString(),
|
|
558
|
-
content:
|
|
633
|
+
content: deathText,
|
|
559
634
|
timestamp: payload.timestamp * 1000,
|
|
560
635
|
};
|
|
561
636
|
}
|
|
@@ -578,11 +653,14 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
578
653
|
id: achieveEvent.server_name || 'minecraft',
|
|
579
654
|
name: achieveEvent.server_name || 'Minecraft Server',
|
|
580
655
|
};
|
|
581
|
-
//
|
|
582
|
-
|
|
656
|
+
// 从 achievement 对象提取成就信息
|
|
657
|
+
const achievementText = achieveEvent.achievement?.display?.title
|
|
658
|
+
|| achieveEvent.achievement?.text
|
|
659
|
+
|| '';
|
|
660
|
+
if (achievementText) {
|
|
583
661
|
event.message = {
|
|
584
662
|
id: Date.now().toString(),
|
|
585
|
-
content:
|
|
663
|
+
content: achievementText,
|
|
586
664
|
timestamp: payload.timestamp * 1000,
|
|
587
665
|
};
|
|
588
666
|
}
|
|
@@ -612,20 +690,68 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
612
690
|
}
|
|
613
691
|
/**
|
|
614
692
|
* 解析消息文本为 Koishi 元素数组
|
|
693
|
+
* 入站方向始终解析 CICode 和裸图片 URL(不受 chatImage.enabled 控制)
|
|
615
694
|
*/
|
|
616
695
|
parseMessageToElements(messageText) {
|
|
617
696
|
if (!messageText)
|
|
618
697
|
return [];
|
|
698
|
+
const elements = [];
|
|
699
|
+
// CICode: [[CICode,url=<url>(,name=<name>)(,nsfw=<bool>)(,pre=<p>)(,suf=<s>)]]
|
|
700
|
+
// 裸图片 URL: https?://....(png|jpg|jpeg|gif|bmp|ico|jfif|webp)
|
|
701
|
+
const ciCodePattern = /\[\[CICode,([^\]]*)\]\]/g;
|
|
702
|
+
const imageUrlPattern = /https?:\/\/\S+\.(?:png|jpe?g|gif|bmp|ico|jfif|webp)(?:\?[^\s]*)?/gi;
|
|
703
|
+
const combinedPattern = new RegExp(`(${ciCodePattern.source})|(${imageUrlPattern.source})`, 'gi');
|
|
704
|
+
let lastIndex = 0;
|
|
705
|
+
let match;
|
|
706
|
+
while ((match = combinedPattern.exec(messageText)) !== null) {
|
|
707
|
+
if (match.index > lastIndex) {
|
|
708
|
+
const textBefore = messageText.slice(lastIndex, match.index);
|
|
709
|
+
this.addTextElements(elements, textBefore);
|
|
710
|
+
}
|
|
711
|
+
if (match[1]) {
|
|
712
|
+
const params = match[2];
|
|
713
|
+
const url = this.extractCICodeParam(params, 'url');
|
|
714
|
+
if (url) {
|
|
715
|
+
const el = { type: 'img', attrs: { src: url } };
|
|
716
|
+
const name = this.extractCICodeParam(params, 'name');
|
|
717
|
+
if (name)
|
|
718
|
+
el.attrs.alt = name;
|
|
719
|
+
el.toString = function () { return `[${this.attrs.alt || '图片'}]`; };
|
|
720
|
+
elements.push(el);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
const url = match[0];
|
|
725
|
+
const el = { type: 'img', attrs: { src: url } };
|
|
726
|
+
el.toString = function () { return `[图片]`; };
|
|
727
|
+
elements.push(el);
|
|
728
|
+
}
|
|
729
|
+
lastIndex = match.index + match[0].length;
|
|
730
|
+
}
|
|
731
|
+
if (lastIndex < messageText.length) {
|
|
732
|
+
const textAfter = messageText.slice(lastIndex);
|
|
733
|
+
this.addTextElements(elements, textAfter);
|
|
734
|
+
}
|
|
735
|
+
if (elements.length === 0) {
|
|
736
|
+
this.addTextElements(elements, messageText);
|
|
737
|
+
}
|
|
738
|
+
return elements;
|
|
739
|
+
}
|
|
740
|
+
// 提取 CICode 参数: "url=xxx,name=yyy" => { url: "xxx", name: "yyy" }
|
|
741
|
+
extractCICodeParam(params, key) {
|
|
742
|
+
const regex = new RegExp(`(?:^|,)${key}=([^,]*)`, 'i');
|
|
743
|
+
const match = regex.exec(params);
|
|
744
|
+
return match ? match[1] : null;
|
|
745
|
+
}
|
|
746
|
+
addTextElements(elements, text) {
|
|
619
747
|
const tokens = this.tokenizeMode === 'none'
|
|
620
|
-
? [
|
|
621
|
-
:
|
|
622
|
-
|
|
748
|
+
? [text]
|
|
749
|
+
: text.split(/(\s+)/).filter((s) => s.length > 0);
|
|
750
|
+
for (const token of tokens) {
|
|
623
751
|
const el = { type: 'text', attrs: { content: token } };
|
|
624
|
-
el.toString = function () {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
return el;
|
|
628
|
-
});
|
|
752
|
+
el.toString = function () { return this.attrs?.content ?? ''; };
|
|
753
|
+
elements.push(el);
|
|
754
|
+
}
|
|
629
755
|
}
|
|
630
756
|
/**
|
|
631
757
|
* 发送私聊消息 (send_private_msg)
|
|
@@ -768,7 +894,7 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
768
894
|
data.subtitle = subtitleComponent;
|
|
769
895
|
if (player)
|
|
770
896
|
data.nickname = player;
|
|
771
|
-
await this.sendApiRequest(ws, '
|
|
897
|
+
await this.sendApiRequest(ws, 'send_title', data);
|
|
772
898
|
return;
|
|
773
899
|
}
|
|
774
900
|
catch (error) {
|
|
@@ -789,7 +915,7 @@ class MinecraftAdapter extends koishi_1.Adapter {
|
|
|
789
915
|
const data = { message: messageComponent };
|
|
790
916
|
if (player)
|
|
791
917
|
data.nickname = player;
|
|
792
|
-
await this.sendApiRequest(ws, '
|
|
918
|
+
await this.sendApiRequest(ws, 'send_actionbar', data);
|
|
793
919
|
return;
|
|
794
920
|
}
|
|
795
921
|
catch (error) {
|
|
@@ -844,6 +970,10 @@ exports.MinecraftAdapter = MinecraftAdapter;
|
|
|
844
970
|
reconnectInterval: koishi_1.Schema.number().description('重连间隔时间(ms)').default(5000),
|
|
845
971
|
maxReconnectAttempts: koishi_1.Schema.number().description('最大重连尝试次数').default(10),
|
|
846
972
|
useMessagePrefix: koishi_1.Schema.boolean().description('是否在消息前添加默认前缀(由服务端配置)').default(false),
|
|
973
|
+
chatImage: koishi_1.Schema.object({
|
|
974
|
+
enabled: koishi_1.Schema.boolean().description('启用 ChatImage CICode 图片发送(需客户端安装 ChatImage Mod)').default(false),
|
|
975
|
+
defaultImageName: koishi_1.Schema.string().description('图片在聊天栏中的默认显示名称').default('图片'),
|
|
976
|
+
}).description('ChatImage 图片显示集成配置'),
|
|
847
977
|
bots: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
848
978
|
selfId: koishi_1.Schema.string().description('机器人 ID(唯一标识)').required(),
|
|
849
979
|
serverName: koishi_1.Schema.string().description('服务器名称(需与鹊桥 config.yml 中的 server_name 一致)'),
|
package/package.json
CHANGED