koishi-plugin-minecraft-search 0.0.6 → 0.0.8

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 forgetmelody
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 forgetmelody
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/lib/index.d.ts CHANGED
@@ -6,9 +6,14 @@ export interface ServerConfig {
6
6
  host: string;
7
7
  port: number;
8
8
  }
9
+ export interface InstanceMapping {
10
+ id: number;
11
+ instanceId: string;
12
+ }
9
13
  export interface MinekuaiConfig {
10
14
  apiKey: string;
11
15
  baseUrl?: string;
16
+ instances?: InstanceMapping[];
12
17
  }
13
18
  export interface Config {
14
19
  servers: ServerConfig[];
package/lib/index.js CHANGED
@@ -37,7 +37,11 @@ var Config = import_koishi.Schema.object({
37
37
  })).description("Minecraft服务器列表").role("table").collapse().required(),
38
38
  minekuai: import_koishi.Schema.object({
39
39
  apiKey: import_koishi.Schema.string().description("麦块联机API密钥"),
40
- baseUrl: import_koishi.Schema.string().description("API基础URL").default("https://minekuai.com/api/client")
40
+ baseUrl: import_koishi.Schema.string().description("API基础URL").default("https://minekuai.com/api/client"),
41
+ instances: import_koishi.Schema.array(import_koishi.Schema.object({
42
+ id: import_koishi.Schema.number().description("服务器ID(对应上方服务器列表中的ID)"),
43
+ instanceId: import_koishi.Schema.string().description("麦块联机实例ID")
44
+ })).description("服务器与实例关联配置(将服务器ID映射到麦块联机实例ID)").role("table").collapse()
41
45
  }).description("麦块联机配置")
42
46
  });
43
47
  function removeFormatting(str) {
@@ -75,6 +79,43 @@ async function minekuaiRequest(ctx, config, endpoint, method = "GET", data) {
75
79
  }
76
80
  }
77
81
  __name(minekuaiRequest, "minekuaiRequest");
82
+ function resolveInstanceIdentifier(input, minekuaiConfig, servers) {
83
+ if (!isNaN(Number(input))) {
84
+ const serverId = parseInt(input);
85
+ const mapping = minekuaiConfig.instances?.find((m) => m.id === serverId);
86
+ if (mapping) {
87
+ return mapping.instanceId;
88
+ } else {
89
+ const server = servers.find((s) => s.id === serverId);
90
+ if (server) {
91
+ throw new Error(`服务器ID ${serverId} (${server.name}) 未配置实例映射关系`);
92
+ } else {
93
+ throw new Error(`未找到ID为 ${serverId} 的服务器`);
94
+ }
95
+ }
96
+ }
97
+ return input;
98
+ }
99
+ __name(resolveInstanceIdentifier, "resolveInstanceIdentifier");
100
+ function getServerName(instanceId, minekuaiConfig, servers) {
101
+ const mapping = minekuaiConfig.instances?.find((m) => m.instanceId === instanceId);
102
+ if (mapping) {
103
+ const server = servers.find((s) => s.id === mapping.id);
104
+ return server ? server.name : `服务器ID: ${mapping.id}`;
105
+ }
106
+ return instanceId;
107
+ }
108
+ __name(getServerName, "getServerName");
109
+ function mapActionToEnglish(action) {
110
+ const actionMap = {
111
+ "启动": "start",
112
+ "关闭": "stop",
113
+ "重启": "restart",
114
+ "强制关闭": "kill"
115
+ };
116
+ return actionMap[action] || action;
117
+ }
118
+ __name(mapActionToEnglish, "mapActionToEnglish");
78
119
  function apply(ctx, config) {
79
120
  ctx.command("mc/查服 [serverName:string]").action(async ({ session }, serverName) => {
80
121
  const { servers } = config;
@@ -111,38 +152,52 @@ function apply(ctx, config) {
111
152
  async function queryServer(server) {
112
153
  const hostWithPort = `${server.host}:${server.port}`;
113
154
  const apiUrl = `https://motd.minebbs.com/api/status?ip=${server.host}&port=${server.port}`;
114
- try {
115
- const response = await ctx.http.get(apiUrl);
116
- if (response.status !== "online") {
117
- return `🔴 ${server.id} ${server.name}
118
- 🌐 ${hostWithPort}
155
+ let retryCount = 0;
156
+ const maxRetries = 3;
157
+ const retryDelay = 1e3;
158
+ while (retryCount <= maxRetries) {
159
+ try {
160
+ const response = await ctx.http.get(apiUrl, {
161
+ timeout: 5e3
162
+ // 设置5秒超时
163
+ });
164
+ if (response.status !== "online") {
165
+ return `🔴 [${server.id}] ${server.name}
166
+ 🌐 IP: ${hostWithPort}
119
167
  状态: 离线`;
120
- }
121
- let message = `🟢 [${server.id}] ${server.name}
168
+ }
169
+ let message = `🟢 [${server.id}] ${server.name}
122
170
  `;
123
- message += `🌐 IP: ${hostWithPort}
171
+ message += `🌐 IP: ${hostWithPort}
124
172
  `;
125
- message += `📝 MOTD:
173
+ message += `📝 MOTD:
126
174
  ${removeFormatting(response.pureMotd || response.motd?.text || "无")}
127
175
  `;
128
- message += `🎮 版本: ${response.version} (协议 ${response.protocol})
176
+ message += `🎮 版本: ${response.version} (协议 ${response.protocol})
129
177
  `;
130
- message += `👥 玩家: ${response.players.online}/${response.players.max}
178
+ message += `👥 玩家: ${response.players.online}/${response.players.max}
131
179
  `;
132
- message += `⏱️ 延迟: ${response.delay}ms
180
+ message += `⏱️ 延迟: ${response.delay}ms
133
181
  `;
134
- if (response.players.online > 0 && response.players.sample) {
135
- const playerNames = Array.isArray(response.players.sample) ? response.players.sample : response.players.sample.split(", ");
136
- message += `🎯 在线玩家: ${playerNames.join(", ")}`;
137
- } else if (response.players.online > 0) {
138
- message += "🎯 在线玩家: 有玩家在线但未获取到列表";
139
- } else {
140
- message += "🎯 当前没有在线玩家";
182
+ if (response.players.online > 0 && response.players.sample) {
183
+ const playerNames = Array.isArray(response.players.sample) ? response.players.sample : response.players.sample.split(", ");
184
+ message += `🎯 在线玩家: ${playerNames.join(", ")}`;
185
+ } else if (response.players.online > 0) {
186
+ message += "🎯 在线玩家: 有玩家在线但未获取到列表";
187
+ } else {
188
+ message += "🎯 当前没有在线玩家";
189
+ }
190
+ return message;
191
+ } catch (error) {
192
+ retryCount++;
193
+ if (retryCount <= maxRetries) {
194
+ ctx.logger("minecraft-search").warn(`查询服务器 ${server.id} ${server.name} 失败,第 ${retryCount} 次重试...`, error.message);
195
+ await new Promise((resolve) => setTimeout(resolve, retryDelay * retryCount));
196
+ } else {
197
+ ctx.logger("minecraft-search").warn(`查询服务器 ${server.id} ${server.name} 重试 ${maxRetries} 次后失败`, error);
198
+ throw new Error("服务器繁忙,请稍后再试。");
199
+ }
141
200
  }
142
- return message;
143
- } catch (error) {
144
- ctx.logger("minecraft-search").warn(`查询服务器 ${server.id} ${server.name} 失败`, error);
145
- throw new Error(`查询失败: ${error.message}`);
146
201
  }
147
202
  }
148
203
  __name(queryServer, "queryServer");
@@ -162,6 +217,19 @@ ${serverList}
162
217
  });
163
218
  if (config.minekuai?.apiKey) {
164
219
  const minekuaiConfig = config.minekuai;
220
+ ctx.command("麦块/实例映射", { authority: 3 }).action(async ({ session }) => {
221
+ if (!minekuaiConfig.instances || minekuaiConfig.instances.length === 0) {
222
+ return "❌ 未配置任何服务器与实例的映射关系";
223
+ }
224
+ let message = "📋 服务器与实例映射关系:\n";
225
+ minekuaiConfig.instances.forEach((mapping, index) => {
226
+ const server = config.servers.find((s) => s.id === mapping.id);
227
+ const serverName = server ? server.name : "未知服务器";
228
+ message += `
229
+ ${index + 1}. 服务器: ${serverName} (ID: ${mapping.id}) → 实例ID: ${mapping.instanceId}`;
230
+ });
231
+ return message;
232
+ });
165
233
  ctx.command("麦块/实例列表", { authority: 3 }).action(async ({ session }) => {
166
234
  try {
167
235
  const response = await minekuaiRequest(ctx, minekuaiConfig, "/");
@@ -171,8 +239,9 @@ ${serverList}
171
239
  let message = "📋 麦块联机实例列表:\n";
172
240
  response.data.forEach((instance, index) => {
173
241
  const attrs = instance.attributes;
242
+ const serverName = getServerName(attrs.identifier, minekuaiConfig, config.servers);
174
243
  message += `
175
- ${index + 1}. ${removeFormatting(attrs.name || attrs.identifier)}
244
+ ${index + 1}. ${removeFormatting(attrs.name || serverName)}
176
245
  `;
177
246
  message += ` 🔧 标识符: ${attrs.identifier}
178
247
  `;
@@ -190,20 +259,26 @@ ${index + 1}. ${removeFormatting(attrs.name || attrs.identifier)}
190
259
  });
191
260
  ctx.command("麦块/实例信息 <identifier:string>", { authority: 3 }).action(async ({ session }, identifier) => {
192
261
  if (!identifier) {
193
- return "❌ 请提供实例标识符";
262
+ return "❌ 请提供实例标识符或服务器ID";
194
263
  }
195
264
  try {
196
- const response = await minekuaiRequest(ctx, minekuaiConfig, `/servers/${identifier}`);
265
+ const instanceId = resolveInstanceIdentifier(identifier, minekuaiConfig, config.servers);
266
+ const serverName = getServerName(instanceId, minekuaiConfig, config.servers);
267
+ const response = await minekuaiRequest(ctx, minekuaiConfig, `/servers/${instanceId}`);
197
268
  if (!response || !response.attributes) {
198
269
  return "❌ 未找到指定实例";
199
270
  }
200
271
  const attrs = response.attributes;
201
272
  const allocations = attrs.relationships?.allocations?.data || [];
202
273
  const defaultAllocation = allocations.find((alloc) => alloc.attributes.is_default) || allocations[0];
203
- let message = `🖥️ 实例信息: ${removeFormatting(attrs.name || identifier)}
274
+ let message = `🖥️ 实例信息: ${removeFormatting(attrs.name || serverName)}
204
275
  `;
205
- message += `🔧 标识符: ${identifier}
276
+ message += `🔧 标识符: ${instanceId}
206
277
  `;
278
+ if (identifier !== instanceId) {
279
+ message += `🔗 对应服务器ID: ${identifier}
280
+ `;
281
+ }
207
282
  message += `📝 描述: ${removeFormatting(attrs.description || "无")}
208
283
  `;
209
284
  message += `🌐 节点: ${attrs.node}
@@ -230,17 +305,23 @@ ${index + 1}. ${removeFormatting(attrs.name || attrs.identifier)}
230
305
  });
231
306
  ctx.command("麦块/实例资源 <identifier:string>", { authority: 3 }).action(async ({ session }, identifier) => {
232
307
  if (!identifier) {
233
- return "❌ 请提供实例标识符";
308
+ return "❌ 请提供实例标识符或服务器ID";
234
309
  }
235
310
  try {
236
- const response = await minekuaiRequest(ctx, minekuaiConfig, `/servers/${identifier}/resources`);
311
+ const instanceId = resolveInstanceIdentifier(identifier, minekuaiConfig, config.servers);
312
+ const serverName = getServerName(instanceId, minekuaiConfig, config.servers);
313
+ const response = await minekuaiRequest(ctx, minekuaiConfig, `/servers/${instanceId}/resources`);
237
314
  if (!response || !response.attributes) {
238
315
  return "❌ 未找到指定实例的资源信息";
239
316
  }
240
317
  const attrs = response.attributes;
241
318
  const resources = attrs.resources;
242
- let message = `📊 实例资源使用情况: ${identifier}
319
+ let message = `📊 实例资源使用情况: ${serverName}
243
320
  `;
321
+ if (identifier !== instanceId) {
322
+ message += `🔗 对应服务器ID: ${identifier}
323
+ `;
324
+ }
244
325
  message += `🔧 当前状态: ${attrs.current_state}
245
326
  `;
246
327
  message += `⏸️ 是否暂停: ${attrs.is_suspended ? "是" : "否"}
@@ -264,30 +345,44 @@ ${index + 1}. ${removeFormatting(attrs.name || attrs.identifier)}
264
345
  });
265
346
  ctx.command("麦块/实例电源 <identifier:string> <action:string>", { authority: 3 }).action(async ({ session }, identifier, action) => {
266
347
  if (!identifier || !action) {
267
- return "❌ 请提供实例标识符和操作类型 (start/stop/restart/kill)";
348
+ return "❌ 请提供实例标识符/服务器ID和操作类型 (启动/关闭/重启/强制关闭)";
268
349
  }
269
- const validActions = ["start", "stop", "restart", "kill"];
270
- if (!validActions.includes(action)) {
350
+ const englishAction = mapActionToEnglish(action);
351
+ const validActions = ["启动", "关闭", "重启", "强制关闭"];
352
+ const validEnglishActions = ["start", "stop", "restart", "kill"];
353
+ if (!validActions.includes(action) && !validEnglishActions.includes(englishAction)) {
271
354
  return `❌ 无效的操作类型。可用操作: ${validActions.join(", ")}`;
272
355
  }
273
356
  try {
274
- await minekuaiRequest(ctx, minekuaiConfig, `/servers/${identifier}/power`, "POST", {
275
- signal: action
357
+ const instanceId = resolveInstanceIdentifier(identifier, minekuaiConfig, config.servers);
358
+ const serverName = getServerName(instanceId, minekuaiConfig, config.servers);
359
+ await minekuaiRequest(ctx, minekuaiConfig, `/servers/${instanceId}/power`, "POST", {
360
+ signal: englishAction
276
361
  });
277
- return `✅ 已发送 ${action} 指令到实例 ${identifier}`;
362
+ let message = `✅ 已发送 ${action} 指令到实例 ${serverName}`;
363
+ if (identifier !== instanceId) {
364
+ message += ` (服务器ID: ${identifier})`;
365
+ }
366
+ return message;
278
367
  } catch (error) {
279
368
  return `❌ 电源操作失败: ${error.message}`;
280
369
  }
281
370
  });
282
371
  ctx.command("麦块/实例命令 <identifier:string> <command:text>", { authority: 3 }).action(async ({ session }, identifier, command) => {
283
372
  if (!identifier || !command) {
284
- return "❌ 请提供实例标识符和命令内容";
373
+ return "❌ 请提供实例标识符/服务器ID和命令内容";
285
374
  }
286
375
  try {
287
- await minekuaiRequest(ctx, minekuaiConfig, `/servers/${identifier}/command`, "POST", {
376
+ const instanceId = resolveInstanceIdentifier(identifier, minekuaiConfig, config.servers);
377
+ const serverName = getServerName(instanceId, minekuaiConfig, config.servers);
378
+ await minekuaiRequest(ctx, minekuaiConfig, `/servers/${instanceId}/command`, "POST", {
288
379
  command
289
380
  });
290
- return `✅ 已发送命令到实例 ${identifier}: ${command}`;
381
+ let message = `✅ 已发送命令到实例 ${serverName}: ${command}`;
382
+ if (identifier !== instanceId) {
383
+ message += ` (服务器ID: ${identifier})`;
384
+ }
385
+ return message;
291
386
  } catch (error) {
292
387
  return `❌ 发送命令失败: ${error.message}`;
293
388
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-minecraft-search",
3
3
  "description": "使用API查询基础的mc服务器信息",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [