koishi-plugin-minecraft-command 1.0.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 +12 -0
- package/lib/index.js +241 -0
- package/package.json +42 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "minecraft-command";
|
|
3
|
+
export interface Config {
|
|
4
|
+
botId: string;
|
|
5
|
+
allowedChannels: string[];
|
|
6
|
+
allowedUsers: string[];
|
|
7
|
+
adminUsers: string[];
|
|
8
|
+
adminAuthority: number;
|
|
9
|
+
userAuthority: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const Config: Schema<Config>;
|
|
12
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Config = exports.name = void 0;
|
|
4
|
+
exports.apply = apply;
|
|
5
|
+
const koishi_1 = require("koishi");
|
|
6
|
+
exports.name = 'minecraft-command';
|
|
7
|
+
const logger = new koishi_1.Logger('mc-cmd');
|
|
8
|
+
function hasExecuteCommand(bot) {
|
|
9
|
+
return 'executeCommand' in bot;
|
|
10
|
+
}
|
|
11
|
+
exports.Config = koishi_1.Schema.object({
|
|
12
|
+
botId: koishi_1.Schema.string()
|
|
13
|
+
.description('指定 Minecraft Bot ID(selfId),留空则自动选择第一个可用的 Minecraft Bot')
|
|
14
|
+
.default(''),
|
|
15
|
+
allowedChannels: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
16
|
+
.description('允许执行命令的频道/群组 ID 列表(留空则不限制频道)')
|
|
17
|
+
.default([]),
|
|
18
|
+
allowedUsers: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
19
|
+
.description('允许执行普通命令的用户 ID 列表(留空则不限制用户)')
|
|
20
|
+
.default([]),
|
|
21
|
+
adminUsers: koishi_1.Schema.array(koishi_1.Schema.string())
|
|
22
|
+
.description('允许执行管理命令的用户 ID 列表(mc.cmd / mc.tp / mc.give / mc.weather / mc.time)')
|
|
23
|
+
.default([]),
|
|
24
|
+
adminAuthority: koishi_1.Schema.number()
|
|
25
|
+
.description('管理命令所需的 Koishi authority 等级(与 adminUsers 为「或」关系,满足其一即可)')
|
|
26
|
+
.default(3),
|
|
27
|
+
userAuthority: koishi_1.Schema.number()
|
|
28
|
+
.description('普通命令所需的 Koishi authority 等级')
|
|
29
|
+
.default(1),
|
|
30
|
+
});
|
|
31
|
+
function apply(ctx, config) {
|
|
32
|
+
function findBot() {
|
|
33
|
+
for (const bot of ctx.bots) {
|
|
34
|
+
if (bot.platform !== 'minecraft')
|
|
35
|
+
continue;
|
|
36
|
+
if (!hasExecuteCommand(bot))
|
|
37
|
+
continue;
|
|
38
|
+
if (config.botId && bot.selfId !== config.botId)
|
|
39
|
+
continue;
|
|
40
|
+
return bot;
|
|
41
|
+
}
|
|
42
|
+
// 指定了 botId 但未匹配时,fallback 到任意可用的 minecraft bot
|
|
43
|
+
if (config.botId) {
|
|
44
|
+
for (const bot of ctx.bots) {
|
|
45
|
+
if (bot.platform === 'minecraft' && hasExecuteCommand(bot)) {
|
|
46
|
+
return bot;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
async function runCommand(command) {
|
|
53
|
+
const bot = findBot();
|
|
54
|
+
if (!bot) {
|
|
55
|
+
throw new Error('未找到可用的 Minecraft Bot,请确保 minecraft-adapter 插件已启用并连接');
|
|
56
|
+
}
|
|
57
|
+
return await bot.executeCommand(command);
|
|
58
|
+
}
|
|
59
|
+
function extractAuthority(user) {
|
|
60
|
+
if (user != null && typeof user === 'object' && 'authority' in user) {
|
|
61
|
+
return typeof user.authority === 'number' ? user.authority : 0;
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
function checkPermission(channelId, userId, authority, requireAdmin) {
|
|
66
|
+
if (config.allowedChannels.length > 0) {
|
|
67
|
+
if (!channelId || !config.allowedChannels.includes(channelId)) {
|
|
68
|
+
return '此频道不允许执行 Minecraft 命令';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (requireAdmin) {
|
|
72
|
+
// adminUsers 列表 或 authority >= adminAuthority,满足其一即可
|
|
73
|
+
const inAdminList = userId ? config.adminUsers.includes(userId) : false;
|
|
74
|
+
const hasAuth = authority >= config.adminAuthority;
|
|
75
|
+
if (!inAdminList && !hasAuth) {
|
|
76
|
+
return '你没有权限执行此管理命令';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// allowedUsers 为空则不限制;非空时需在列表中 或 authority 满足要求
|
|
81
|
+
if (config.allowedUsers.length > 0) {
|
|
82
|
+
const inUserList = userId ? config.allowedUsers.includes(userId) : false;
|
|
83
|
+
const hasAuth = authority >= config.userAuthority;
|
|
84
|
+
if (!inUserList && !hasAuth) {
|
|
85
|
+
return '你没有权限执行此命令';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
ctx.command('mc', 'Minecraft 服务器管理')
|
|
92
|
+
.usage('通过子命令管理 Minecraft 服务器:mc.list / mc.say / mc.tp / mc.give / mc.weather / mc.time / mc.cmd');
|
|
93
|
+
ctx.command('mc.list', '查看在线玩家')
|
|
94
|
+
.alias('mc.在线')
|
|
95
|
+
.action(async ({ session }) => {
|
|
96
|
+
if (!session)
|
|
97
|
+
return;
|
|
98
|
+
const denied = checkPermission(session.channelId, session.userId, extractAuthority(session.user), false);
|
|
99
|
+
if (denied)
|
|
100
|
+
return denied;
|
|
101
|
+
try {
|
|
102
|
+
const result = await runCommand('list');
|
|
103
|
+
return result || '命令已执行,但服务器未返回数据';
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
logger.warn('mc.list 执行失败', e);
|
|
107
|
+
return `执行失败: ${e.message}`;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
ctx.command('mc.say <message:text>', '在服务器中广播消息')
|
|
111
|
+
.alias('mc.广播')
|
|
112
|
+
.action(async ({ session }, message) => {
|
|
113
|
+
if (!session)
|
|
114
|
+
return;
|
|
115
|
+
const denied = checkPermission(session.channelId, session.userId, extractAuthority(session.user), false);
|
|
116
|
+
if (denied)
|
|
117
|
+
return denied;
|
|
118
|
+
if (!message)
|
|
119
|
+
return '请输入要广播的消息';
|
|
120
|
+
try {
|
|
121
|
+
const result = await runCommand(`say ${message}`);
|
|
122
|
+
return `广播成功${result ? `: ${result}` : ''}`;
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
logger.warn('mc.say 执行失败', e);
|
|
126
|
+
return `广播失败: ${e.message}`;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
ctx.command('mc.tp <player:string> <target:text>', '传送玩家到目标位置或玩家')
|
|
130
|
+
.alias('mc.传送')
|
|
131
|
+
.usage('示例:\n mc.tp Steve Alex — 将 Steve 传送到 Alex\n mc.tp Steve 100 64 200 — 将 Steve 传送到坐标')
|
|
132
|
+
.action(async ({ session }, player, target) => {
|
|
133
|
+
if (!session)
|
|
134
|
+
return;
|
|
135
|
+
const denied = checkPermission(session.channelId, session.userId, extractAuthority(session.user), true);
|
|
136
|
+
if (denied)
|
|
137
|
+
return denied;
|
|
138
|
+
if (!player || !target)
|
|
139
|
+
return '用法: mc.tp <玩家名> <目标玩家或坐标>';
|
|
140
|
+
try {
|
|
141
|
+
const result = await runCommand(`tp ${player} ${target}`);
|
|
142
|
+
return `传送成功${result ? `: ${result}` : ''}`;
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
logger.warn('mc.tp 执行失败', e);
|
|
146
|
+
return `传送失败: ${e.message}`;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
ctx.command('mc.give <player:string> <item:string> [count:number]', '给予玩家物品')
|
|
150
|
+
.alias('mc.给予')
|
|
151
|
+
.usage('示例: mc.give Steve diamond 64')
|
|
152
|
+
.action(async ({ session }, player, item, count) => {
|
|
153
|
+
if (!session)
|
|
154
|
+
return;
|
|
155
|
+
const denied = checkPermission(session.channelId, session.userId, extractAuthority(session.user), true);
|
|
156
|
+
if (denied)
|
|
157
|
+
return denied;
|
|
158
|
+
if (!player || !item)
|
|
159
|
+
return '用法: mc.give <玩家名> <物品ID> [数量]';
|
|
160
|
+
const amount = count ?? 1;
|
|
161
|
+
try {
|
|
162
|
+
const result = await runCommand(`give ${player} ${item} ${amount}`);
|
|
163
|
+
return `已给予 ${player} ${amount} 个 ${item}${result ? `\n${result}` : ''}`;
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
logger.warn('mc.give 执行失败', e);
|
|
167
|
+
return `给予失败: ${e.message}`;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
ctx.command('mc.weather <type:string>', '设置服务器天气')
|
|
171
|
+
.alias('mc.天气')
|
|
172
|
+
.usage('可选值: clear (晴天) / rain (雨天) / thunder (雷暴)')
|
|
173
|
+
.action(async ({ session }, type) => {
|
|
174
|
+
if (!session)
|
|
175
|
+
return;
|
|
176
|
+
const denied = checkPermission(session.channelId, session.userId, extractAuthority(session.user), true);
|
|
177
|
+
if (denied)
|
|
178
|
+
return denied;
|
|
179
|
+
if (!type)
|
|
180
|
+
return '请指定天气类型: clear / rain / thunder';
|
|
181
|
+
const validTypes = ['clear', 'rain', 'thunder'];
|
|
182
|
+
if (!validTypes.includes(type.toLowerCase())) {
|
|
183
|
+
return `无效的天气类型,可选值: ${validTypes.join(' / ')}`;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const result = await runCommand(`weather ${type.toLowerCase()}`);
|
|
187
|
+
return `天气已设置为 ${type}${result ? `\n${result}` : ''}`;
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
logger.warn('mc.weather 执行失败', e);
|
|
191
|
+
return `设置天气失败: ${e.message}`;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
ctx.command('mc.time <value:string>', '设置服务器时间')
|
|
195
|
+
.alias('mc.时间')
|
|
196
|
+
.usage('可选值: day / night / noon / midnight / 游戏刻数字 (如 6000)')
|
|
197
|
+
.action(async ({ session }, value) => {
|
|
198
|
+
if (!session)
|
|
199
|
+
return;
|
|
200
|
+
const denied = checkPermission(session.channelId, session.userId, extractAuthority(session.user), true);
|
|
201
|
+
if (denied)
|
|
202
|
+
return denied;
|
|
203
|
+
if (!value)
|
|
204
|
+
return '请指定时间值: day / night / noon / midnight / 游戏刻数字';
|
|
205
|
+
const presets = ['day', 'night', 'noon', 'midnight'];
|
|
206
|
+
const isPreset = presets.includes(value.toLowerCase());
|
|
207
|
+
const isNumber = /^\d+$/.test(value);
|
|
208
|
+
if (!isPreset && !isNumber) {
|
|
209
|
+
return `无效的时间值,可选: ${presets.join(' / ')} 或游戏刻数字`;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const result = await runCommand(`time set ${isPreset ? value.toLowerCase() : value}`);
|
|
213
|
+
return `时间已设置为 ${value}${result ? `\n${result}` : ''}`;
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
logger.warn('mc.time 执行失败', e);
|
|
217
|
+
return `设置时间失败: ${e.message}`;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
ctx.command('mc.cmd <command:text>', '执行任意 Minecraft 服务器命令')
|
|
221
|
+
.alias('mc.命令')
|
|
222
|
+
.usage('直接执行原始服务器命令,如: mc.cmd op Steve')
|
|
223
|
+
.action(async ({ session }, command) => {
|
|
224
|
+
if (!session)
|
|
225
|
+
return;
|
|
226
|
+
const denied = checkPermission(session.channelId, session.userId, extractAuthority(session.user), true);
|
|
227
|
+
if (denied)
|
|
228
|
+
return denied;
|
|
229
|
+
if (!command)
|
|
230
|
+
return '请输入要执行的命令';
|
|
231
|
+
try {
|
|
232
|
+
const result = await runCommand(command);
|
|
233
|
+
return result || '命令已执行';
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
logger.warn('mc.cmd 执行失败', e);
|
|
237
|
+
return `执行失败: ${e.message}`;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
logger.info('minecraft-command 插件已加载');
|
|
241
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-minecraft-command",
|
|
3
|
+
"description": "Execute Minecraft server commands from chat platforms via koishi-plugin-minecraft-adapter",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"author": "johntime2005",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"koishi": "^4.17.9"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"koishi": "^4.17.9",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"registry": "https://registry.npmjs.org"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"koishi",
|
|
29
|
+
"plugin",
|
|
30
|
+
"minecraft",
|
|
31
|
+
"command",
|
|
32
|
+
"rcon",
|
|
33
|
+
"mc",
|
|
34
|
+
"game"
|
|
35
|
+
],
|
|
36
|
+
"koishi": {
|
|
37
|
+
"description": {
|
|
38
|
+
"en": "Execute Minecraft server commands from chat platforms, works with minecraft-adapter",
|
|
39
|
+
"zh": "通过聊天平台执行 Minecraft 服务器命令,与 minecraft-adapter 联动"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|