mioku-plugin-admin 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/package.json ADDED
@@ -0,0 +1,135 @@
1
+ {
2
+ "name": "mioku-plugin-admin",
3
+ "version": "1.0.0",
4
+ "description": "管理插件,提供事件通知与群管/个人管理指令",
5
+ "main": "index.ts",
6
+ "keywords": ["mioku"],
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/mioku-lab/mioku-plugin-admin.git"
10
+ },
11
+ "mioku": {
12
+ "services": [
13
+ "config",
14
+ "ai"
15
+ ],
16
+ "help": {
17
+ "title": "管理",
18
+ "description": "Bot管理插件,提供事件通知与管理指令",
19
+ "commands": [
20
+ {
21
+ "cmd": "/改头像",
22
+ "desc": "修改Bot头像",
23
+ "role": "master"
24
+ },
25
+ {
26
+ "cmd": "/改昵称",
27
+ "desc": "修改Bot昵称",
28
+ "role": "master"
29
+ },
30
+ {
31
+ "cmd": "/改签名",
32
+ "desc": "修改Bot个性签名",
33
+ "role": "master"
34
+ },
35
+ {
36
+ "cmd": "/改性别",
37
+ "desc": "修改Bot性别",
38
+ "role": "master"
39
+ },
40
+ {
41
+ "cmd": "/改群名片",
42
+ "desc": "修改Bot在群里的名片",
43
+ "role": "admin"
44
+ },
45
+ {
46
+ "cmd": "/改群昵称",
47
+ "desc": "修改群聊名称",
48
+ "role": "admin"
49
+ },
50
+ {
51
+ "cmd": "/改群头像",
52
+ "desc": "修改群头像",
53
+ "role": "admin"
54
+ },
55
+ {
56
+ "cmd": "/改头衔",
57
+ "desc": "设置群成员专属头衔",
58
+ "usage": "/改头衔 qq号或@人 头衔",
59
+ "role": "admin"
60
+ },
61
+ {
62
+ "cmd": "我要头衔",
63
+ "desc": "普通群员给自己设置专属头衔",
64
+ "usage": "我要头衔 头衔内容",
65
+ "role": "member"
66
+ },
67
+ {
68
+ "cmd": "/踢",
69
+ "desc": "踢出群成员",
70
+ "role": "admin"
71
+ },
72
+ {
73
+ "cmd": "/禁言",
74
+ "desc": "禁言群成员(支持分钟/小时/天)",
75
+ "usage": "/禁言 @人 10分钟",
76
+ "role": "admin"
77
+ },
78
+ {
79
+ "cmd": "/解禁",
80
+ "desc": "解除群成员禁言",
81
+ "role": "admin"
82
+ },
83
+ {
84
+ "cmd": "/设管理",
85
+ "desc": "设置群管理员",
86
+ "role": "admin"
87
+ },
88
+ {
89
+ "cmd": "/全体禁言",
90
+ "desc": "开启全体禁言",
91
+ "role": "admin"
92
+ },
93
+ {
94
+ "cmd": "/全体解禁",
95
+ "desc": "关闭全体禁言",
96
+ "role": "admin"
97
+ },
98
+ {
99
+ "cmd": "/删好友",
100
+ "desc": "删除好友",
101
+ "usage": "/删好友 qq号",
102
+ "role": "master"
103
+ },
104
+ {
105
+ "cmd": "/退群",
106
+ "desc": "退出群聊",
107
+ "usage": "/退群 群号",
108
+ "role": "master"
109
+ },
110
+ {
111
+ "cmd": "/发好友",
112
+ "desc": "给好友发送私聊消息",
113
+ "usage": "/发好友 qq号 内容",
114
+ "role": "master"
115
+ },
116
+ {
117
+ "cmd": "/发群聊",
118
+ "desc": "给群聊发送消息",
119
+ "usage": "/发群聊 群号 内容",
120
+ "role": "master"
121
+ },
122
+ {
123
+ "cmd": "/全部好友",
124
+ "desc": "获取全部好友列表",
125
+ "role": "master"
126
+ },
127
+ {
128
+ "cmd": "/全部群聊",
129
+ "desc": "获取全部群聊列表",
130
+ "role": "master"
131
+ }
132
+ ]
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,407 @@
1
+ import type { AISkill } from "../../../src";
2
+ import { getMemberRole } from "../config";
3
+ import { getImageUrlByMessageId } from "./message-image";
4
+
5
+ async function resolveGroupRuntime(runtimeCtx: any) {
6
+ const ctx = runtimeCtx?.ctx;
7
+ if (!ctx) return { error: "无法获取上下文" };
8
+
9
+ const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
10
+ const groupId = Number(event?.group_id || 0);
11
+ if (!groupId) return { error: "这个工具只能在群聊中使用" };
12
+
13
+ const selfId = runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
14
+ if (!selfId) return { error: "无法获取Bot ID" };
15
+
16
+ const bot = ctx.pickBot(selfId);
17
+ if (!bot) return { error: "Bot不可用" };
18
+
19
+ return { ctx, selfId, bot, groupId, event };
20
+ }
21
+
22
+ function logAdminSkillError(runtimeCtx: any, toolName: string, err: unknown) {
23
+ runtimeCtx?.ctx?.logger?.error?.(
24
+ `[admin skills] ${toolName} failed: ${String(err)}`,
25
+ );
26
+ }
27
+
28
+ function formatGroupRole(role: string): string {
29
+ if (role === "owner") return "群主";
30
+ if (role === "admin") return "管理员";
31
+ return "群成员";
32
+ }
33
+
34
+ async function checkDangerousTargetPermission(
35
+ runtime: any,
36
+ targetUserId: number,
37
+ ): Promise<string | null> {
38
+ const operatorUserId = Number(runtime.event?.user_id || 0);
39
+ if (!operatorUserId) return "无法获取当前操作人ID";
40
+
41
+ const operatorRole = await getMemberRole(
42
+ runtime.bot,
43
+ runtime.groupId,
44
+ operatorUserId,
45
+ );
46
+ const targetRole = await getMemberRole(
47
+ runtime.bot,
48
+ runtime.groupId,
49
+ targetUserId,
50
+ );
51
+ if (operatorRole !== targetRole) return null;
52
+
53
+ return `无权操作同级成员:操作人与被操作人均为${formatGroupRole(operatorRole)}`;
54
+ }
55
+
56
+ const groupAdminSkill: AISkill = {
57
+ name: "admin_group",
58
+ description:
59
+ "群管理工具,提供踢人、禁言、解禁、设管理、全体禁言、改群名、改群头像、设头衔等群管操作",
60
+ permission: "admin",
61
+ tools: [
62
+ {
63
+ name: "kick_member",
64
+ description: "将群成员踢出群聊",
65
+ parameters: {
66
+ type: "object",
67
+ properties: {
68
+ user_id: { type: "number", description: "要踢出的成员QQ号" },
69
+ },
70
+ required: ["user_id"],
71
+ },
72
+ handler: async (args: any, runtimeCtx?: any) => {
73
+ const runtime = await resolveGroupRuntime(runtimeCtx);
74
+ if ("error" in runtime) return { error: runtime.error };
75
+ const { bot, groupId } = runtime;
76
+ const permissionError = await checkDangerousTargetPermission(
77
+ runtime,
78
+ args.user_id,
79
+ );
80
+ if (permissionError) {
81
+ logAdminSkillError(
82
+ runtimeCtx,
83
+ "admin_group.kick_member",
84
+ permissionError,
85
+ );
86
+ return { error: permissionError };
87
+ }
88
+ try {
89
+ await bot.api("set_group_kick", {
90
+ group_id: groupId,
91
+ user_id: args.user_id,
92
+ });
93
+ return {
94
+ success: true,
95
+ message: `已将 ${args.user_id} 移出当前群`,
96
+ };
97
+ } catch (err) {
98
+ logAdminSkillError(runtimeCtx, "admin_group.kick_member", err);
99
+ return { error: `踢人失败: ${err}` };
100
+ }
101
+ },
102
+ },
103
+ {
104
+ name: "mute_member",
105
+ description: "禁言群成员,不指定时长时默认10分钟",
106
+ parameters: {
107
+ type: "object",
108
+ properties: {
109
+ user_id: { type: "number", description: "要禁言的成员QQ号" },
110
+ duration: {
111
+ type: "number",
112
+ description: "禁言时长(秒),不提供或小于等于0时默认600秒",
113
+ },
114
+ },
115
+ required: ["user_id"],
116
+ },
117
+ handler: async (args: any, runtimeCtx?: any) => {
118
+ const runtime = await resolveGroupRuntime(runtimeCtx);
119
+ if ("error" in runtime) return { error: runtime.error };
120
+ const { bot, groupId } = runtime;
121
+ const duration =
122
+ Number(args.duration) > 0 ? Number(args.duration) : 10 * 60;
123
+ const permissionError = await checkDangerousTargetPermission(
124
+ runtime,
125
+ args.user_id,
126
+ );
127
+ if (permissionError) {
128
+ logAdminSkillError(
129
+ runtimeCtx,
130
+ "admin_group.mute_member",
131
+ permissionError,
132
+ );
133
+ return { error: permissionError };
134
+ }
135
+ try {
136
+ await bot.setGroupBan(groupId, args.user_id, duration);
137
+ return {
138
+ success: true,
139
+ message: `已禁言 ${args.user_id} ${duration}秒`,
140
+ };
141
+ } catch (err) {
142
+ logAdminSkillError(runtimeCtx, "admin_group.mute_member", err);
143
+ return { error: `禁言失败: ${err}` };
144
+ }
145
+ },
146
+ },
147
+ {
148
+ name: "unmute_member",
149
+ description: "解除群成员禁言",
150
+ parameters: {
151
+ type: "object",
152
+ properties: {
153
+ user_id: { type: "number", description: "要解禁的成员QQ号" },
154
+ },
155
+ required: ["user_id"],
156
+ },
157
+ handler: async (args: any, runtimeCtx?: any) => {
158
+ const runtime = await resolveGroupRuntime(runtimeCtx);
159
+ if ("error" in runtime) return { error: runtime.error };
160
+ const { bot, groupId } = runtime;
161
+ try {
162
+ await bot.setGroupBan(groupId, args.user_id, 0);
163
+ return { success: true, message: `已解除 ${args.user_id} 的禁言` };
164
+ } catch (err) {
165
+ logAdminSkillError(runtimeCtx, "admin_group.unmute_member", err);
166
+ return { error: `解禁失败: ${err}` };
167
+ }
168
+ },
169
+ },
170
+ {
171
+ name: "set_group_admin",
172
+ description: "设置或取消群管理员",
173
+ parameters: {
174
+ type: "object",
175
+ properties: {
176
+ user_id: { type: "number", description: "要设为管理的QQ号" },
177
+ enable: {
178
+ type: "boolean",
179
+ description: "true设为管理,false取消管理",
180
+ },
181
+ },
182
+ required: ["user_id", "enable"],
183
+ },
184
+ handler: async (args: any, runtimeCtx?: any) => {
185
+ const runtime = await resolveGroupRuntime(runtimeCtx);
186
+ if ("error" in runtime) return { error: runtime.error };
187
+ const { bot, groupId } = runtime;
188
+ try {
189
+ await bot.api("set_group_admin", {
190
+ group_id: groupId,
191
+ user_id: args.user_id,
192
+ enable: args.enable,
193
+ });
194
+ return {
195
+ success: true,
196
+ message: args.enable
197
+ ? `已将 ${args.user_id} 设为当前群的管理员`
198
+ : `已取消 ${args.user_id} 在当前群的管理员`,
199
+ };
200
+ } catch (err) {
201
+ logAdminSkillError(runtimeCtx, "admin_group.set_group_admin", err);
202
+ return { error: `设管理失败: ${err}` };
203
+ }
204
+ },
205
+ },
206
+ {
207
+ name: "set_group_whole_ban",
208
+ description: "开启或关闭全体禁言",
209
+ parameters: {
210
+ type: "object",
211
+ properties: {
212
+ enable: {
213
+ type: "boolean",
214
+ description: "true开启全体禁言,false关闭",
215
+ },
216
+ },
217
+ required: ["enable"],
218
+ },
219
+ handler: async (args: any, runtimeCtx?: any) => {
220
+ const runtime = await resolveGroupRuntime(runtimeCtx);
221
+ if ("error" in runtime) return { error: runtime.error };
222
+ const { bot, groupId } = runtime;
223
+ try {
224
+ await bot.api("set_group_whole_ban", {
225
+ group_id: groupId,
226
+ enable: args.enable,
227
+ });
228
+ return {
229
+ success: true,
230
+ message: args.enable
231
+ ? "当前群已开启全体禁言"
232
+ : "当前群已关闭全体禁言",
233
+ };
234
+ } catch (err) {
235
+ logAdminSkillError(
236
+ runtimeCtx,
237
+ "admin_group.set_group_whole_ban",
238
+ err,
239
+ );
240
+ return { error: `全体禁言操作失败: ${err}` };
241
+ }
242
+ },
243
+ },
244
+ {
245
+ name: "set_group_name",
246
+ description: "修改群聊名称",
247
+ parameters: {
248
+ type: "object",
249
+ properties: {
250
+ group_name: { type: "string", description: "新群名" },
251
+ },
252
+ required: ["group_name"],
253
+ },
254
+ handler: async (args: any, runtimeCtx?: any) => {
255
+ const runtime = await resolveGroupRuntime(runtimeCtx);
256
+ if ("error" in runtime) return { error: runtime.error };
257
+ const { bot, groupId } = runtime;
258
+ try {
259
+ await bot.api("set_group_name", {
260
+ group_id: groupId,
261
+ group_name: args.group_name,
262
+ });
263
+ return {
264
+ success: true,
265
+ message: `当前群名称已修改为 ${args.group_name}`,
266
+ };
267
+ } catch (err) {
268
+ logAdminSkillError(runtimeCtx, "admin_group.set_group_name", err);
269
+ return { error: `修改群名失败: ${err}` };
270
+ }
271
+ },
272
+ },
273
+ {
274
+ name: "set_group_card",
275
+ description: "修改Bot在当前群内的群名片",
276
+ parameters: {
277
+ type: "object",
278
+ properties: {
279
+ card: { type: "string", description: "新的群名片" },
280
+ },
281
+ required: ["card"],
282
+ },
283
+ handler: async (args: any, runtimeCtx?: any) => {
284
+ const runtime = await resolveGroupRuntime(runtimeCtx);
285
+ if ("error" in runtime) return { error: runtime.error };
286
+ const { bot, selfId, groupId } = runtime;
287
+ const card = String(args?.card || "").trim();
288
+ if (!card) return { error: "card 不能为空" };
289
+ try {
290
+ await bot.setGroupCard(groupId, selfId, card);
291
+ return {
292
+ success: true,
293
+ message: `Bot在当前群的群名片已修改为 ${card}`,
294
+ };
295
+ } catch (err) {
296
+ logAdminSkillError(runtimeCtx, "admin_group.set_group_card", err);
297
+ return { error: `修改群名片失败: ${err}` };
298
+ }
299
+ },
300
+ },
301
+ {
302
+ name: "set_group_avatar",
303
+ description: "修改群头像",
304
+ parameters: {
305
+ type: "object",
306
+ properties: {
307
+ message_id: {
308
+ type: "number",
309
+ description: "包含图片的消息 message_id",
310
+ },
311
+ },
312
+ required: ["message_id"],
313
+ },
314
+ handler: async (args: any, runtimeCtx?: any) => {
315
+ const runtime = await resolveGroupRuntime(runtimeCtx);
316
+ if ("error" in runtime) return { error: runtime.error };
317
+ const { bot, groupId } = runtime;
318
+ const messageId = Number(args?.message_id);
319
+ if (!Number.isFinite(messageId) || messageId <= 0) {
320
+ return { error: "请提供有效的 message_id" };
321
+ }
322
+ const imageUrl = await getImageUrlByMessageId(bot, messageId);
323
+ if (!imageUrl) {
324
+ return { error: "指定 message_id 中未找到图片" };
325
+ }
326
+ try {
327
+ await bot.api("set_group_portrait", {
328
+ group_id: groupId,
329
+ file: imageUrl,
330
+ });
331
+ return { success: true, message: "当前群头像已修改" };
332
+ } catch (err) {
333
+ logAdminSkillError(runtimeCtx, "admin_group.set_group_avatar", err);
334
+ return { error: `修改群头像失败: ${err}` };
335
+ }
336
+ },
337
+ },
338
+ {
339
+ name: "set_group_title",
340
+ description: "设置群成员专属头衔",
341
+ parameters: {
342
+ type: "object",
343
+ properties: {
344
+ user_id: { type: "number", description: "成员QQ号" },
345
+ title: { type: "string", description: "新头衔" },
346
+ },
347
+ required: ["user_id", "title"],
348
+ },
349
+ handler: async (args: any, runtimeCtx?: any) => {
350
+ const runtime = await resolveGroupRuntime(runtimeCtx);
351
+ if ("error" in runtime) return { error: runtime.error };
352
+ const { bot, groupId } = runtime;
353
+ try {
354
+ await (bot as any).setGroupSpecialTitle(
355
+ groupId,
356
+ args.user_id,
357
+ args.title,
358
+ );
359
+ return {
360
+ success: true,
361
+ message: `已将 ${args.user_id} 的头衔设为 "${args.title}"`,
362
+ };
363
+ } catch (err) {
364
+ logAdminSkillError(runtimeCtx, "admin_group.set_group_title", err);
365
+ return { error: `设置头衔失败: ${err}` };
366
+ }
367
+ },
368
+ },
369
+ {
370
+ name: "set_self_group_title",
371
+ description: "给当前发起用户设置自己的群专属头衔,不能指定或修改别人",
372
+ parameters: {
373
+ type: "object",
374
+ properties: {
375
+ title: { type: "string", description: "新头衔" },
376
+ },
377
+ required: ["title"],
378
+ },
379
+ handler: async (args: any, runtimeCtx?: any) => {
380
+ const runtime = await resolveGroupRuntime(runtimeCtx);
381
+ if ("error" in runtime) return { error: runtime.error };
382
+ const { bot, groupId } = runtime;
383
+ const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
384
+ const userId = event?.user_id;
385
+ if (!userId) return { error: "无法获取当前用户ID" };
386
+ const title = String(args?.title || "").trim();
387
+ if (!title) return { error: "title 不能为空" };
388
+ try {
389
+ await (bot as any).setGroupSpecialTitle(groupId, userId, title);
390
+ return {
391
+ success: true,
392
+ message: `已将你的头衔设为 "${title}"`,
393
+ };
394
+ } catch (err) {
395
+ logAdminSkillError(
396
+ runtimeCtx,
397
+ "admin_group.set_self_group_title",
398
+ err,
399
+ );
400
+ return { error: `设置头衔失败: ${err}` };
401
+ }
402
+ },
403
+ },
404
+ ],
405
+ };
406
+
407
+ export default groupAdminSkill;
@@ -0,0 +1,20 @@
1
+ export async function getImageUrlByMessageId(
2
+ bot: any,
3
+ messageId: number,
4
+ ): Promise<string | null> {
5
+ try {
6
+ const msg =
7
+ typeof bot?.getMsg === "function"
8
+ ? await bot.getMsg(messageId)
9
+ : await bot.api("get_msg", { message_id: messageId });
10
+ const segments = Array.isArray(msg?.message) ? msg.message : [];
11
+ const imageSeg = segments.find((seg: any) => seg?.type === "image");
12
+ if (!imageSeg) {
13
+ return null;
14
+ }
15
+
16
+ return imageSeg.url || imageSeg.file || null;
17
+ } catch {
18
+ return null;
19
+ }
20
+ }