mioku-plugin-admin 2.0.0 → 2.2.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/commands/group.ts CHANGED
@@ -41,6 +41,8 @@ export function registerGroupAdminCommands(ctx: MiokiContext) {
41
41
  "/改群名片",
42
42
  "/改群昵称",
43
43
  "/改群头像",
44
+ "/撤回",
45
+ "撤回",
44
46
  ].some((prefix) => text.startsWith(prefix));
45
47
 
46
48
  try {
@@ -479,6 +481,40 @@ export function registerGroupAdminCommands(ctx: MiokiContext) {
479
481
  }
480
482
  return;
481
483
  }
484
+
485
+ // 撤回 — 引用一条消息后,bot 撤回该消息
486
+ if (text === "/撤回" || text === "撤回") {
487
+ if (!isGroup || !groupId) {
488
+ return event.reply("这个要在群里用哦~", true);
489
+ }
490
+ if (!(await ensureAdminPermission())) return;
491
+
492
+ const quotedId = event.quote_id;
493
+ if (!quotedId) {
494
+ await replyAdminErrorNotice({
495
+ ctx,
496
+ event,
497
+ instruction:
498
+ "用户希望撤回一条消息,但命令中没有引用具体消息,请提醒用户引用要撤回的消息后输入 /撤回",
499
+ fallbackMessage: "要撤回哪条消息呀~先引用一下~",
500
+ });
501
+ return;
502
+ }
503
+
504
+ try {
505
+ await bot.api("delete_msg", { message_id: quotedId });
506
+ await replyDone();
507
+ } catch (err) {
508
+ await replyAdminErrorNotice({
509
+ ctx,
510
+ event,
511
+ instruction: `撤回消息执行失败:${String(err)}。失败原因通常是Bot没有该消息的撤回权限(需要Bot为群主或管理员,且消息发出不超过2分钟),管理员无法撤回管理员的消息或群主的消息。`,
512
+ fallbackMessage: `撤回失败啦~可能是没权限或消息超过2分钟了:${String(err)}`,
513
+ error: err,
514
+ });
515
+ }
516
+ return;
517
+ }
482
518
  } catch (err) {
483
519
  ctx.logger.error(`[admin group] 未捕获异常: ${String(err)}`);
484
520
  if (!isGroupAdminCommand) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-admin",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "管理插件,提供事件通知与群管/个人管理指令",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -24,6 +24,96 @@
24
24
  "config",
25
25
  "ai"
26
26
  ],
27
+ "accessHooks": [
28
+ {
29
+ "id": "/改头像",
30
+ "match": "/^\\/改头像/"
31
+ },
32
+ {
33
+ "id": "/改昵称",
34
+ "match": "/^\\/改昵称/"
35
+ },
36
+ {
37
+ "id": "/改签名",
38
+ "match": "/^\\/改签名/"
39
+ },
40
+ {
41
+ "id": "/改性别",
42
+ "match": "/^\\/改性别/"
43
+ },
44
+ {
45
+ "id": "/改群名片",
46
+ "match": "/^\\/改群名片/"
47
+ },
48
+ {
49
+ "id": "/改群昵称",
50
+ "match": "/^\\/改群昵称/"
51
+ },
52
+ {
53
+ "id": "/改群头像",
54
+ "match": "/^\\/改群头像/"
55
+ },
56
+ {
57
+ "id": "/撤回",
58
+ "match": "/^(\\/撤回|撤回)/"
59
+ },
60
+ {
61
+ "id": "/改头衔",
62
+ "match": "/^\\/改头衔/"
63
+ },
64
+ {
65
+ "id": "我要头衔",
66
+ "match": "/^我要头衔/"
67
+ },
68
+ {
69
+ "id": "/踢",
70
+ "match": "/^\\/踢/"
71
+ },
72
+ {
73
+ "id": "/禁言",
74
+ "match": "/^\\/禁言/"
75
+ },
76
+ {
77
+ "id": "/解禁",
78
+ "match": "/^\\/解禁/"
79
+ },
80
+ {
81
+ "id": "/设管理",
82
+ "match": "/^\\/设管理/"
83
+ },
84
+ {
85
+ "id": "/全体禁言",
86
+ "match": "/^\\/全体禁言/"
87
+ },
88
+ {
89
+ "id": "/全体解禁",
90
+ "match": "/^\\/全体解禁/"
91
+ },
92
+ {
93
+ "id": "/删好友",
94
+ "match": "/^\\/删好友/"
95
+ },
96
+ {
97
+ "id": "/退群",
98
+ "match": "/^\\/退群/"
99
+ },
100
+ {
101
+ "id": "/发好友",
102
+ "match": "/^\\/发好友/"
103
+ },
104
+ {
105
+ "id": "/发群聊",
106
+ "match": "/^\\/发群聊/"
107
+ },
108
+ {
109
+ "id": "/全部好友",
110
+ "match": "/^\\/全部好友/"
111
+ },
112
+ {
113
+ "id": "/全部群聊",
114
+ "match": "/^\\/全部群聊/"
115
+ }
116
+ ],
27
117
  "help": {
28
118
  "title": "管理",
29
119
  "description": "Bot管理插件,提供事件通知与管理指令",
@@ -63,6 +153,12 @@
63
153
  "desc": "修改群头像",
64
154
  "role": "admin"
65
155
  },
156
+ {
157
+ "cmd": "/撤回",
158
+ "desc": "撤回别人的消息",
159
+ "usage": "引用一条消息后输入 /撤回",
160
+ "role": "admin"
161
+ },
66
162
  {
67
163
  "cmd": "/改头衔",
68
164
  "desc": "设置群成员专属头衔",
package/skills/group.ts CHANGED
@@ -56,348 +56,283 @@ async function checkDangerousTargetPermission(
56
56
  const groupAdminSkill: AISkill = {
57
57
  name: "admin_group",
58
58
  description:
59
- "群管理工具,提供踢人、禁言、解禁、设管理、全体禁言、改群名、改群头像、设头衔等群管操作",
59
+ "群管理统一入口。action 决定具体行为:管理成员(踢/禁言/解禁/设管理/设头衔)或管理群(全体禁言/改群名/改Bot群名片/改群头像/撤回消息)。",
60
60
  permission: "admin",
61
61
  tools: [
62
62
  {
63
- name: "kick_member",
64
- description: "将群成员踢出群聊",
63
+ name: "manage_member",
64
+ description:
65
+ "管理群成员:踢人(kick)、禁言(mute)、解除禁言(unmute)、设为管理员(set_admin)、取消管理员(unset_admin)、设置某成员头衔(set_title)、设置自己头衔(set_self_title)。",
65
66
  parameters: {
66
67
  type: "object",
67
68
  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号" },
69
+ action: {
70
+ type: "string",
71
+ description: "要执行的动作",
72
+ enum: [
73
+ "kick",
74
+ "mute",
75
+ "unmute",
76
+ "set_admin",
77
+ "unset_admin",
78
+ "set_title",
79
+ "set_self_title",
80
+ ],
81
+ },
82
+ user_id: {
83
+ type: "number",
84
+ description:
85
+ "目标成员QQ号。set_self_title 不需要;其他动作必填。",
86
+ },
110
87
  duration: {
111
88
  type: "number",
112
- description: "禁言时长(秒),不提供或小于等于0时默认600秒",
89
+ description: "禁言时长(秒),仅 mute 生效;不提供或<=0时默认600秒",
113
90
  },
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取消管理",
91
+ title: {
92
+ type: "string",
93
+ description: "头衔内容,仅 set_title / set_self_title 需要",
180
94
  },
181
95
  },
182
- required: ["user_id", "enable"],
96
+ required: ["action"],
183
97
  },
184
98
  handler: async (args: any, runtimeCtx?: any) => {
185
99
  const runtime = await resolveGroupRuntime(runtimeCtx);
186
100
  if ("error" in runtime) return { error: runtime.error };
187
- const { bot, groupId } = runtime;
101
+ const { bot, selfId, groupId, event } = runtime;
102
+ const action = String(args?.action || "");
103
+
188
104
  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
- };
105
+ switch (action) {
106
+ case "kick": {
107
+ const userId = Number(args?.user_id);
108
+ if (!userId) return { error: "kick 需要提供 user_id" };
109
+ const permErr = await checkDangerousTargetPermission(
110
+ runtime,
111
+ userId,
112
+ );
113
+ if (permErr) return { error: permErr };
114
+ await bot.api("set_group_kick", {
115
+ group_id: groupId,
116
+ user_id: userId,
117
+ });
118
+ return { success: true, message: `已将 ${userId} 移出当前群` };
119
+ }
120
+ case "mute": {
121
+ const userId = Number(args?.user_id);
122
+ if (!userId) return { error: "mute 需要提供 user_id" };
123
+ const permErr = await checkDangerousTargetPermission(
124
+ runtime,
125
+ userId,
126
+ );
127
+ if (permErr) return { error: permErr };
128
+ const duration =
129
+ Number(args?.duration) > 0 ? Number(args.duration) : 10 * 60;
130
+ await bot.setGroupBan(groupId, userId, duration);
131
+ return {
132
+ success: true,
133
+ message: `已禁言 ${userId} ${duration}秒`,
134
+ };
135
+ }
136
+ case "unmute": {
137
+ const userId = Number(args?.user_id);
138
+ if (!userId) return { error: "unmute 需要提供 user_id" };
139
+ await bot.setGroupBan(groupId, userId, 0);
140
+ return { success: true, message: `已解除 ${userId} 的禁言` };
141
+ }
142
+ case "set_admin": {
143
+ const userId = Number(args?.user_id);
144
+ if (!userId) return { error: "set_admin 需要提供 user_id" };
145
+ await bot.api("set_group_admin", {
146
+ group_id: groupId,
147
+ user_id: userId,
148
+ enable: true,
149
+ });
150
+ return {
151
+ success: true,
152
+ message: `已将 ${userId} 设为当前群的管理员`,
153
+ };
154
+ }
155
+ case "unset_admin": {
156
+ const userId = Number(args?.user_id);
157
+ if (!userId) return { error: "unset_admin 需要提供 user_id" };
158
+ await bot.api("set_group_admin", {
159
+ group_id: groupId,
160
+ user_id: userId,
161
+ enable: false,
162
+ });
163
+ return {
164
+ success: true,
165
+ message: `已取消 ${userId} 在当前群的管理员`,
166
+ };
167
+ }
168
+ case "set_title": {
169
+ const userId = Number(args?.user_id);
170
+ const title = String(args?.title || "").trim();
171
+ if (!userId || !title) {
172
+ return { error: "set_title 需要提供 user_id 和 title" };
173
+ }
174
+ await (bot as any).setGroupSpecialTitle(groupId, userId, title);
175
+ return {
176
+ success: true,
177
+ message: `已将 ${userId} 的头衔设为 "${title}"`,
178
+ };
179
+ }
180
+ case "set_self_title": {
181
+ const userId = Number(event?.user_id);
182
+ if (!userId) return { error: "无法获取当前用户ID" };
183
+ const title = String(args?.title || "").trim();
184
+ if (!title) return { error: "set_self_title 需要提供 title" };
185
+ await (bot as any).setGroupSpecialTitle(groupId, userId, title);
186
+ return {
187
+ success: true,
188
+ message: `已将你的头衔设为 "${title}"`,
189
+ };
190
+ }
191
+ default:
192
+ return { error: `未知的 action: ${action}` };
193
+ }
200
194
  } catch (err) {
201
- logAdminSkillError(runtimeCtx, "admin_group.set_group_admin", err);
202
- return { error: `设管理失败: ${err}` };
195
+ logAdminSkillError(runtimeCtx, `admin_group.manage_member.${action}`, err);
196
+ return { error: `执行 ${action} 失败: ${err}` };
203
197
  }
204
198
  },
205
199
  },
206
200
  {
207
- name: "set_group_whole_ban",
208
- description: "开启或关闭全体禁言",
201
+ name: "manage_group",
202
+ description:
203
+ "管理群本身或批量撤回消息:开启/关闭全体禁言(set_whole_ban/unset_whole_ban)、改群名(set_group_name)、改Bot在群里的名片(set_self_card)、改群头像(set_group_avatar)、撤回一条或多条消息(recall_messages,需要 message_ids 数组)。",
209
204
  parameters: {
210
205
  type: "object",
211
206
  properties: {
212
- enable: {
213
- type: "boolean",
214
- description: "true开启全体禁言,false关闭",
207
+ action: {
208
+ type: "string",
209
+ description: "要执行的动作",
210
+ enum: [
211
+ "set_whole_ban",
212
+ "unset_whole_ban",
213
+ "set_group_name",
214
+ "set_self_card",
215
+ "set_group_avatar",
216
+ "recall_messages",
217
+ ],
218
+ },
219
+ group_name: {
220
+ type: "string",
221
+ description: "新群名,仅 set_group_name 需要",
222
+ },
223
+ card: {
224
+ type: "string",
225
+ description: "新的Bot群名片,仅 set_self_card 需要",
215
226
  },
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
227
  message_id: {
308
228
  type: "number",
309
- description: "包含图片的消息 message_id",
229
+ description:
230
+ "包含图片的消息 message_id,仅 set_group_avatar 需要",
231
+ },
232
+ message_ids: {
233
+ type: "array",
234
+ items: { type: "number" },
235
+ description:
236
+ "要撤回的消息 message_id 数组,仅 recall_messages 需要。一次调用可批量撤回。",
310
237
  },
311
238
  },
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"],
239
+ required: ["action"],
378
240
  },
379
241
  handler: async (args: any, runtimeCtx?: any) => {
380
242
  const runtime = await resolveGroupRuntime(runtimeCtx);
381
243
  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 不能为空" };
244
+ const { bot, selfId, groupId } = runtime;
245
+ const action = String(args?.action || "");
246
+
388
247
  try {
389
- await (bot as any).setGroupSpecialTitle(groupId, userId, title);
390
- return {
391
- success: true,
392
- message: `已将你的头衔设为 "${title}"`,
393
- };
248
+ switch (action) {
249
+ case "set_whole_ban":
250
+ await bot.api("set_group_whole_ban", {
251
+ group_id: groupId,
252
+ enable: true,
253
+ });
254
+ return { success: true, message: "当前群已开启全体禁言" };
255
+ case "unset_whole_ban":
256
+ await bot.api("set_group_whole_ban", {
257
+ group_id: groupId,
258
+ enable: false,
259
+ });
260
+ return { success: true, message: "当前群已关闭全体禁言" };
261
+ case "set_group_name": {
262
+ const groupName = String(args?.group_name || "").trim();
263
+ if (!groupName) return { error: "set_group_name 需要提供 group_name" };
264
+ await bot.api("set_group_name", {
265
+ group_id: groupId,
266
+ group_name: groupName,
267
+ });
268
+ return {
269
+ success: true,
270
+ message: `当前群名称已修改为 ${groupName}`,
271
+ };
272
+ }
273
+ case "set_self_card": {
274
+ const card = String(args?.card || "").trim();
275
+ if (!card) return { error: "set_self_card 需要提供 card" };
276
+ await bot.setGroupCard(groupId, selfId, card);
277
+ return {
278
+ success: true,
279
+ message: `Bot在当前群的群名片已修改为 ${card}`,
280
+ };
281
+ }
282
+ case "set_group_avatar": {
283
+ const messageId = Number(args?.message_id);
284
+ if (!Number.isFinite(messageId) || messageId <= 0) {
285
+ return { error: "set_group_avatar 需要提供有效的 message_id" };
286
+ }
287
+ const imageUrl = await getImageUrlByMessageId(bot, messageId);
288
+ if (!imageUrl) {
289
+ return { error: "指定 message_id 中未找到图片" };
290
+ }
291
+ await bot.api("set_group_portrait", {
292
+ group_id: groupId,
293
+ file: imageUrl,
294
+ });
295
+ return { success: true, message: "当前群头像已修改" };
296
+ }
297
+ case "recall_messages": {
298
+ const ids = Array.isArray(args?.message_ids)
299
+ ? args.message_ids
300
+ .map((v: any) => Number(v))
301
+ .filter((n: number) => Number.isFinite(n) && n > 0)
302
+ : [];
303
+ if (ids.length === 0) {
304
+ return { error: "recall_messages 需要提供至少一个 message_id" };
305
+ }
306
+ const results: Array<{
307
+ message_id: number;
308
+ success: boolean;
309
+ error?: string;
310
+ }> = [];
311
+ for (const id of ids) {
312
+ try {
313
+ await bot.api("delete_msg", { message_id: id });
314
+ results.push({ message_id: id, success: true });
315
+ } catch (err) {
316
+ results.push({
317
+ message_id: id,
318
+ success: false,
319
+ error: String(err),
320
+ });
321
+ }
322
+ }
323
+ const ok = results.filter((r) => r.success).length;
324
+ return {
325
+ success: ok === results.length,
326
+ message: `共尝试撤回 ${results.length} 条消息,成功 ${ok} 条,失败 ${results.length - ok} 条`,
327
+ results,
328
+ };
329
+ }
330
+ default:
331
+ return { error: `未知的 action: ${action}` };
332
+ }
394
333
  } catch (err) {
395
- logAdminSkillError(
396
- runtimeCtx,
397
- "admin_group.set_self_group_title",
398
- err,
399
- );
400
- return { error: `设置头衔失败: ${err}` };
334
+ logAdminSkillError(runtimeCtx, `admin_group.manage_group.${action}`, err);
335
+ return { error: `执行 ${action} 失败: ${err}` };
401
336
  }
402
337
  },
403
338
  },
@@ -21,352 +21,202 @@ function logAdminSkillError(runtimeCtx: any, toolName: string, err: unknown) {
21
21
  const personalSkill: AISkill = {
22
22
  name: "admin_personal",
23
23
  description:
24
- "个人账号管理工具,提供修改自己头像、昵称、个性签名、性别、发送消息、查看好友/群列表等操作",
24
+ "Bot个人账号与消息管理统一入口。action 决定行为:修改Bot资料(set_avatar/set_nickname/set_signature/set_gender)、发送消息(send_private/send_group)、获取列表(list_friends/list_groups)、管理关系(delete_friend/leave_group)。",
25
25
  permission: "owner",
26
26
  tools: [
27
27
  {
28
- name: "set_bot_avatar",
29
- description: "修改Bot头像",
28
+ name: "manage_personal",
29
+ description:
30
+ "Bot个人账号管理:修改头像/昵称/签名/性别、给指定用户或群发消息、查看好友/群列表、删除好友、退群。所有功能通过 action 字段区分。",
30
31
  parameters: {
31
32
  type: "object",
32
33
  properties: {
34
+ action: {
35
+ type: "string",
36
+ description: "要执行的动作",
37
+ enum: [
38
+ "set_avatar",
39
+ "set_nickname",
40
+ "set_signature",
41
+ "set_gender",
42
+ "send_private",
43
+ "send_group",
44
+ "list_friends",
45
+ "list_groups",
46
+ "delete_friend",
47
+ "leave_group",
48
+ ],
49
+ },
33
50
  message_id: {
34
51
  type: "number",
35
- description: "包含图片的消息 message_id",
52
+ description: "包含图片的消息 message_id,仅 set_avatar 需要",
53
+ },
54
+ nickname: {
55
+ type: "string",
56
+ description: "新昵称,仅 set_nickname 需要",
57
+ },
58
+ personal_note: {
59
+ type: "string",
60
+ description: "新个性签名,仅 set_signature 需要",
36
61
  },
37
- },
38
- required: ["message_id"],
39
- },
40
- handler: async (args: any, runtimeCtx?: any) => {
41
- const ctx = runtimeCtx?.ctx;
42
- if (!ctx) return { error: "无法获取上下文" };
43
- const selfId =
44
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
45
- if (!selfId) return { error: "无法获取Bot ID" };
46
- const bot = ctx.pickBot(selfId);
47
- if (!bot) return { error: "Bot不可用" };
48
- const messageId = Number(args?.message_id);
49
- if (!Number.isFinite(messageId) || messageId <= 0) {
50
- return { error: "请提供有效的 message_id" };
51
- }
52
- const imageUrl = await getImageUrlByMessageId(bot, messageId);
53
- if (!imageUrl) {
54
- return { error: "指定 message_id 中未找到图片" };
55
- }
56
- try {
57
- await bot.api("set_qq_avatar", { file: imageUrl });
58
- return { success: true, message: "Bot头像已修改" };
59
- } catch (err) {
60
- logAdminSkillError(runtimeCtx, "admin_personal.set_bot_avatar", err);
61
- return { error: `修改头像失败: ${err}` };
62
- }
63
- },
64
- },
65
- {
66
- name: "set_bot_nickname",
67
- description: "修改Bot昵称",
68
- parameters: {
69
- type: "object",
70
- properties: { nickname: { type: "string", description: "新昵称" } },
71
- required: ["nickname"],
72
- },
73
- handler: async (args: any, runtimeCtx?: any) => {
74
- const ctx = runtimeCtx?.ctx;
75
- if (!ctx) return { error: "无法获取上下文" };
76
- const selfId =
77
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
78
- if (!selfId) return { error: "无法获取Bot ID" };
79
- const bot = ctx.pickBot(selfId);
80
- if (!bot) return { error: "Bot不可用" };
81
- try {
82
- await bot.api("set_qq_profile", {
83
- nickname: String(args.nickname || "").trim(),
84
- });
85
- return {
86
- success: true,
87
- message: `Bot昵称已修改为: ${args.nickname}`,
88
- };
89
- } catch (err) {
90
- logAdminSkillError(
91
- runtimeCtx,
92
- "admin_personal.set_bot_nickname",
93
- err,
94
- );
95
- return { error: `修改昵称失败: ${err}` };
96
- }
97
- },
98
- },
99
- {
100
- name: "set_bot_signature",
101
- description: "修改Bot个性签名",
102
- parameters: {
103
- type: "object",
104
- properties: {
105
- personal_note: { type: "string", description: "新个性签名" },
106
- },
107
- required: ["personal_note"],
108
- },
109
- handler: async (args: any, runtimeCtx?: any) => {
110
- const ctx = runtimeCtx?.ctx;
111
- if (!ctx) return { error: "无法获取上下文" };
112
- const selfId =
113
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
114
- if (!selfId) return { error: "无法获取Bot ID" };
115
- const bot = ctx.pickBot(selfId);
116
- if (!bot) return { error: "Bot不可用" };
117
- const personalNote = String(args?.personal_note || "").trim();
118
- if (!personalNote) return { error: "personal_note 不能为空" };
119
- try {
120
- await bot.api("set_qq_profile", { personal_note: personalNote });
121
- return { success: true, message: "Bot个性签名已修改" };
122
- } catch (err) {
123
- logAdminSkillError(
124
- runtimeCtx,
125
- "admin_personal.set_bot_signature",
126
- err,
127
- );
128
- return { error: `修改个性签名失败: ${err}` };
129
- }
130
- },
131
- },
132
- {
133
- name: "set_bot_gender",
134
- description: "修改Bot性别",
135
- parameters: {
136
- type: "object",
137
- properties: {
138
62
  gender: {
139
63
  type: "string",
140
- description: "性别:男/女/无 或 male/female/unknown",
141
- enum: [
142
- "male",
143
- "female",
144
- "unknown",
145
- "男",
146
- "女",
147
- "无",
148
- "0",
149
- "1",
150
- "2",
151
- ],
64
+ description:
65
+ "性别:男/女/无 或 male/female/unknown 或 1/2/0,仅 set_gender 需要",
66
+ },
67
+ user_id: {
68
+ type: "number",
69
+ description:
70
+ "目标QQ号。send_private / delete_friend 需要。",
71
+ },
72
+ group_id: {
73
+ type: "number",
74
+ description: "目标群号。send_group / leave_group 需要。",
75
+ },
76
+ content: {
77
+ type: "string",
78
+ description: "消息内容。send_private / send_group 需要。",
152
79
  },
153
80
  },
154
- required: ["gender"],
155
- },
156
- handler: async (args: any, runtimeCtx?: any) => {
157
- const ctx = runtimeCtx?.ctx;
158
- if (!ctx) return { error: "无法获取上下文" };
159
- const selfId =
160
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
161
- if (!selfId) return { error: "无法获取Bot ID" };
162
- const bot = ctx.pickBot(selfId);
163
- if (!bot) return { error: "Bot不可用" };
164
- try {
165
- const sex = parseProfileSex(args.gender);
166
- await bot.api("set_qq_profile", { sex });
167
- const genderMap: Record<number, string> = {
168
- 0: "无",
169
- 1: "男",
170
- 2: "女",
171
- };
172
- return {
173
- success: true,
174
- message: `Bot性别已修改为: ${genderMap[sex]}`,
175
- };
176
- } catch (err) {
177
- logAdminSkillError(runtimeCtx, "admin_personal.set_bot_gender", err);
178
- return { error: `修改性别失败: ${err}` };
179
- }
180
- },
181
- },
182
- {
183
- name: "send_private_message",
184
- description: "给指定QQ号发送私聊消息",
185
- parameters: {
186
- type: "object",
187
- properties: {
188
- user_id: { type: "number", description: "目标QQ号" },
189
- content: { type: "string", description: "消息内容" },
190
- },
191
- required: ["user_id", "content"],
192
- },
193
- handler: async (args: any, runtimeCtx?: any) => {
194
- const ctx = runtimeCtx?.ctx;
195
- if (!ctx) return { error: "无法获取上下文" };
196
- const selfId =
197
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
198
- if (!selfId) return { error: "无法获取Bot ID" };
199
- const bot = ctx.pickBot(selfId);
200
- if (!bot) return { error: "Bot不可用" };
201
- try {
202
- await bot.sendPrivateMsg(args.user_id, [
203
- ctx.segment.text(args.content),
204
- ]);
205
- return { success: true, message: `已发送私聊消息给 ${args.user_id}` };
206
- } catch (err) {
207
- logAdminSkillError(
208
- runtimeCtx,
209
- "admin_personal.send_private_message",
210
- err,
211
- );
212
- return { error: `发送私聊失败: ${err}` };
213
- }
214
- },
215
- },
216
- {
217
- name: "send_group_message",
218
- description: "给指定群发送消息",
219
- parameters: {
220
- type: "object",
221
- properties: {
222
- group_id: { type: "number", description: "目标群号" },
223
- content: { type: "string", description: "消息内容" },
224
- },
225
- required: ["group_id", "content"],
81
+ required: ["action"],
226
82
  },
227
83
  handler: async (args: any, runtimeCtx?: any) => {
228
84
  const ctx = runtimeCtx?.ctx;
229
85
  if (!ctx) return { error: "无法获取上下文" };
230
- const selfId =
231
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
86
+ const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
87
+ const selfId = event?.self_id;
232
88
  if (!selfId) return { error: "无法获取Bot ID" };
233
89
  const bot = ctx.pickBot(selfId);
234
90
  if (!bot) return { error: "Bot不可用" };
91
+ const action = String(args?.action || "");
92
+
235
93
  try {
236
- await bot.sendGroupMsg(args.group_id, [
237
- ctx.segment.text(args.content),
238
- ]);
239
- return { success: true, message: `已发送群消息到 ${args.group_id}` };
94
+ switch (action) {
95
+ case "set_avatar": {
96
+ const messageId = Number(args?.message_id);
97
+ if (!Number.isFinite(messageId) || messageId <= 0) {
98
+ return { error: "set_avatar 需要提供有效的 message_id" };
99
+ }
100
+ const imageUrl = await getImageUrlByMessageId(bot, messageId);
101
+ if (!imageUrl) {
102
+ return { error: "指定 message_id 中未找到图片" };
103
+ }
104
+ await bot.api("set_qq_avatar", { file: imageUrl });
105
+ return { success: true, message: "Bot头像已修改" };
106
+ }
107
+ case "set_nickname": {
108
+ const nickname = String(args?.nickname || "").trim();
109
+ if (!nickname) {
110
+ return { error: "set_nickname 需要提供 nickname" };
111
+ }
112
+ await bot.api("set_qq_profile", { nickname });
113
+ return {
114
+ success: true,
115
+ message: `Bot昵称已修改为: ${nickname}`,
116
+ };
117
+ }
118
+ case "set_signature": {
119
+ const personalNote = String(args?.personal_note || "").trim();
120
+ if (!personalNote) {
121
+ return { error: "set_signature 需要提供 personal_note" };
122
+ }
123
+ await bot.api("set_qq_profile", { personal_note: personalNote });
124
+ return { success: true, message: "Bot个性签名已修改" };
125
+ }
126
+ case "set_gender": {
127
+ const sex = parseProfileSex(args?.gender);
128
+ await bot.api("set_qq_profile", { sex });
129
+ const genderMap: Record<number, string> = {
130
+ 0: "无",
131
+ 1: "男",
132
+ 2: "女",
133
+ };
134
+ return {
135
+ success: true,
136
+ message: `Bot性别已修改为: ${genderMap[sex]}`,
137
+ };
138
+ }
139
+ case "send_private": {
140
+ const userId = Number(args?.user_id);
141
+ const content = String(args?.content || "");
142
+ if (!userId || !content) {
143
+ return { error: "send_private 需要提供 user_id 和 content" };
144
+ }
145
+ await bot.sendPrivateMsg(userId, [ctx.segment.text(content)]);
146
+ return {
147
+ success: true,
148
+ message: `已发送私聊消息给 ${userId}`,
149
+ };
150
+ }
151
+ case "send_group": {
152
+ const groupId = Number(args?.group_id);
153
+ const content = String(args?.content || "");
154
+ if (!groupId || !content) {
155
+ return { error: "send_group 需要提供 group_id 和 content" };
156
+ }
157
+ await bot.sendGroupMsg(groupId, [ctx.segment.text(content)]);
158
+ return {
159
+ success: true,
160
+ message: `已发送群消息到 ${groupId}`,
161
+ };
162
+ }
163
+ case "list_friends": {
164
+ if (event?.message_type === "group") {
165
+ return { error: "在私聊使用试试看吧~" };
166
+ }
167
+ const friendList: any[] = await bot.api("get_friend_list");
168
+ if (!Array.isArray(friendList)) return { friends: [] };
169
+ return {
170
+ friends: friendList.map((f) => ({
171
+ user_id: f.user_id,
172
+ nickname: f.nickname,
173
+ remark: f.remark,
174
+ })),
175
+ };
176
+ }
177
+ case "list_groups": {
178
+ if (event?.message_type === "group") {
179
+ return { error: "在私聊使用试试看吧~" };
180
+ }
181
+ const groupList: any[] = await bot.api("get_group_list");
182
+ if (!Array.isArray(groupList)) return { groups: [] };
183
+ return {
184
+ groups: groupList.map((g) => ({
185
+ group_id: g.group_id,
186
+ group_name: g.group_name,
187
+ member_count: g.member_count,
188
+ })),
189
+ };
190
+ }
191
+ case "delete_friend": {
192
+ const userId = Number(args?.user_id);
193
+ if (!userId) {
194
+ return { error: "delete_friend 需要提供 user_id" };
195
+ }
196
+ await bot.api("delete_friend", { user_id: userId });
197
+ return { success: true, message: `已删除好友 ${userId}` };
198
+ }
199
+ case "leave_group": {
200
+ const groupId = Number(args?.group_id);
201
+ if (!groupId) {
202
+ return { error: "leave_group 需要提供 group_id" };
203
+ }
204
+ await bot.api("set_group_leave", {
205
+ group_id: groupId,
206
+ is_dismiss: false,
207
+ });
208
+ return { success: true, message: `已退出群 ${groupId}` };
209
+ }
210
+ default:
211
+ return { error: `未知的 action: ${action}` };
212
+ }
240
213
  } catch (err) {
241
214
  logAdminSkillError(
242
215
  runtimeCtx,
243
- "admin_personal.send_group_message",
216
+ `admin_personal.manage_personal.${action}`,
244
217
  err,
245
218
  );
246
- return { error: `发送群消息失败: ${err}` };
247
- }
248
- },
249
- },
250
- {
251
- name: "get_friend_list",
252
- description:
253
- "获取Bot的全部好友列表,对应/全部好友命令,仅适合在私聊场景调用",
254
- parameters: { type: "object", properties: {}, required: [] },
255
- handler: async (_args: any, runtimeCtx?: any) => {
256
- const ctx = runtimeCtx?.ctx;
257
- if (!ctx) return { error: "无法获取上下文" };
258
- const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
259
- if (event?.message_type === "group") {
260
- return { error: "在私聊使用试试看吧~" };
261
- }
262
- const selfId =
263
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
264
- if (!selfId) return { error: "无法获取Bot ID" };
265
- const bot = ctx.pickBot(selfId);
266
- if (!bot) return { error: "Bot不可用" };
267
- try {
268
- const friendList: any[] = await bot.api("get_friend_list");
269
- if (!Array.isArray(friendList)) return { friends: [] };
270
- return {
271
- friends: friendList.map((f) => ({
272
- user_id: f.user_id,
273
- nickname: f.nickname,
274
- remark: f.remark,
275
- })),
276
- };
277
- } catch (err) {
278
- logAdminSkillError(runtimeCtx, "admin_personal.get_friend_list", err);
279
- return { error: `获取好友列表失败: ${err}` };
280
- }
281
- },
282
- },
283
- {
284
- name: "get_group_list",
285
- description:
286
- "获取Bot的全部群聊列表,对应/全部群聊命令,仅适合在私聊场景调用",
287
- parameters: { type: "object", properties: {}, required: [] },
288
- handler: async (_args: any, runtimeCtx?: any) => {
289
- const ctx = runtimeCtx?.ctx;
290
- if (!ctx) return { error: "无法获取上下文" };
291
- const event = runtimeCtx?.event || runtimeCtx?.rawEvent;
292
- if (event?.message_type === "group") {
293
- return { error: "在私聊使用试试看吧~" };
294
- }
295
- const selfId =
296
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
297
- if (!selfId) return { error: "无法获取Bot ID" };
298
- const bot = ctx.pickBot(selfId);
299
- if (!bot) return { error: "Bot不可用" };
300
- try {
301
- const groupList: any[] = await bot.api("get_group_list");
302
- if (!Array.isArray(groupList)) return { groups: [] };
303
- return {
304
- groups: groupList.map((g) => ({
305
- group_id: g.group_id,
306
- group_name: g.group_name,
307
- member_count: g.member_count,
308
- })),
309
- };
310
- } catch (err) {
311
- logAdminSkillError(runtimeCtx, "admin_personal.get_group_list", err);
312
- return { error: `获取群聊列表失败: ${err}` };
313
- }
314
- },
315
- },
316
- {
317
- name: "delete_friend",
318
- description: "删除好友",
319
- parameters: {
320
- type: "object",
321
- properties: {
322
- user_id: { type: "number", description: "要删除的好友QQ号" },
323
- },
324
- required: ["user_id"],
325
- },
326
- handler: async (args: any, runtimeCtx?: any) => {
327
- const ctx = runtimeCtx?.ctx;
328
- if (!ctx) return { error: "无法获取上下文" };
329
- const selfId =
330
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
331
- if (!selfId) return { error: "无法获取Bot ID" };
332
- const bot = ctx.pickBot(selfId);
333
- if (!bot) return { error: "Bot不可用" };
334
- try {
335
- await bot.api("delete_friend", { user_id: args.user_id });
336
- return { success: true, message: `已删除好友 ${args.user_id}` };
337
- } catch (err) {
338
- logAdminSkillError(runtimeCtx, "admin_personal.delete_friend", err);
339
- return { error: `删除好友失败: ${err}` };
340
- }
341
- },
342
- },
343
- {
344
- name: "leave_group",
345
- description: "退出群聊",
346
- parameters: {
347
- type: "object",
348
- properties: {
349
- group_id: { type: "number", description: "要退出的群号" },
350
- },
351
- required: ["group_id"],
352
- },
353
- handler: async (args: any, runtimeCtx?: any) => {
354
- const ctx = runtimeCtx?.ctx;
355
- if (!ctx) return { error: "无法获取上下文" };
356
- const selfId =
357
- runtimeCtx?.event?.self_id || runtimeCtx?.rawEvent?.self_id;
358
- if (!selfId) return { error: "无法获取Bot ID" };
359
- const bot = ctx.pickBot(selfId);
360
- if (!bot) return { error: "Bot不可用" };
361
- try {
362
- await bot.api("set_group_leave", {
363
- group_id: args.group_id,
364
- is_dismiss: false,
365
- });
366
- return { success: true, message: `已退出群 ${args.group_id}` };
367
- } catch (err) {
368
- logAdminSkillError(runtimeCtx, "admin_personal.leave_group", err);
369
- return { error: `退群失败: ${err}` };
219
+ return { error: `执行 ${action} 失败: ${err}` };
370
220
  }
371
221
  },
372
222
  },