mioku-plugin-admin 3.0.0 → 3.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/commands/group.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { MiokuContext } from "mioku";
1
+ import type {
2
+ Bot,
3
+ CommandDefinition,
4
+ CommandExecutionContext,
5
+ MiokuContext,
6
+ } from "mioku";
2
7
 
3
8
  import {
4
9
  extractImageUrl,
@@ -14,497 +19,502 @@ function formatGroupRole(role: string): string {
14
19
  return "群成员";
15
20
  }
16
21
 
17
- export function registerGroupAdminCommands(ctx: MiokuContext) {
18
- ctx.handle("message", async (event) => {
19
- const text = ctx.text(event)?.trim();
20
- if (!text) return;
21
- if (event.user_id === event.self_id) return;
22
-
23
- const isGroupAdminCommand = [
24
- "改头衔",
25
- "/改头衔",
26
- "我要头衔",
27
- "踢",
28
- "/踢",
29
- "禁",
30
- "/禁",
31
- "禁言",
32
- "/禁言",
33
- "解",
34
- "/解",
35
- "解禁",
36
- "/解禁",
37
- "设管理",
38
- "/设管理",
39
- "/全体禁言",
40
- "/全体解禁",
41
- "改群名片",
42
- "/改群名片",
43
- "/改群昵称",
44
- "/改群头像",
45
- "/撤回",
46
- "撤回",
47
- ].some((prefix) => text.startsWith(prefix));
48
-
49
- try {
50
- const selfId = event.self_id;
51
- const bot = event.bot;
52
- if (!bot) return;
53
-
54
- const isGroup = event.message_type === "group";
55
- const groupIdNum = isGroup && event.group_id ? Number(event.group_id) : undefined;
56
- if (!isGroup || !groupIdNum) return;
57
-
58
- const isMaster = ctx.isOwner?.(event) ?? false;
59
- const senderRole = await getMemberRole(bot, groupIdNum, Number(event.user_id));
60
- const hasAdminPermission =
61
- isMaster || senderRole === "owner" || senderRole === "admin";
62
-
63
- const replyDone = async () => {
64
- await event.reply("done");
65
- };
66
- const ensureAdminPermission = async (instruction?: string) => {
67
- if (hasAdminPermission) return true;
68
- await replyAdminErrorNotice({
69
- ctx,
70
- event,
71
- instruction:
72
- instruction ||
73
- "用户想让你执行群管操作但用户不是群主或管理员,无权限让操作",
74
- fallbackMessage: "你得是群主或管理员才行哦~",
75
- });
76
- return false;
77
- };
78
- const ensureDangerousTargetPermission = async (
79
- targetUserId: number,
80
- actionName: string,
81
- ) => {
82
- const targetRole = await getMemberRole(bot, groupIdNum, targetUserId);
83
- if (senderRole !== targetRole) return true;
84
-
85
- await replyAdminErrorNotice({
86
- ctx,
87
- event,
88
- instruction: `用户想${actionName}目标成员,但操作人与被操作人群身份相同,均为${formatGroupRole(senderRole)},群管危险操作无权操作同等级成员。请明确告诉用户无权操作同级成员。`,
89
- fallbackMessage: "无权操作同级成员哦~",
90
- });
91
- return false;
92
- };
93
-
94
- if (text.startsWith("我要头衔")) {
95
- const atUser = getAtUserId(event.message);
96
- if (atUser != null && atUser !== Number(event.user_id)) {
97
- await event.reply("管好自己呗~", true);
98
- return;
99
- }
100
-
101
- const title = text.replace(/^我要头衔\s*/, "").trim();
102
- if (!title) {
103
- await event.reply("想要什么头衔呀~", true);
104
- return;
105
- }
22
+ type GroupContext = CommandExecutionContext & {
23
+ bot: Bot;
24
+ groupIdNum: number;
25
+ senderRole: string;
26
+ };
106
27
 
28
+ export function registerGroupAdminCommands(ctx: MiokuContext) {
29
+ const register = (
30
+ def: Omit<CommandDefinition, "handler">,
31
+ run: (c: GroupContext) => Promise<void>,
32
+ ) => {
33
+ ctx.command({
34
+ ...def,
35
+ prefixes: false,
36
+ handler: async (c) => {
37
+ const event = c.event;
38
+ if (event.user_id === event.self_id) return;
39
+ if (event.message_type !== "group") return;
40
+ const bot = event.bot;
41
+ if (!bot) return;
42
+ const groupIdNum = event.group_id ? Number(event.group_id) : 0;
43
+ if (!groupIdNum) return;
44
+ const senderRole = await getMemberRole(
45
+ bot,
46
+ groupIdNum,
47
+ Number(event.user_id),
48
+ );
107
49
  try {
108
- await bot.setMemberTitle(groupIdNum, event.user_id!, title);
109
- await replyDone();
50
+ await run({ ...c, bot, groupIdNum, senderRole });
110
51
  } catch (err) {
52
+ ctx.logger.error(`[admin group] 未捕获异常: ${String(err)}`);
111
53
  await replyAdminErrorNotice({
112
54
  ctx,
113
55
  event,
114
- instruction: `为用户设置个人头衔执行失败:${String(err)},请简要说明失败并建议稍后重试`,
56
+ instruction: `群管理指令执行时发生未捕获异常:${String(err)},请简要说明失败并建议稍后重试`,
115
57
  fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
116
58
  error: err,
117
59
  });
118
60
  }
61
+ },
62
+ });
63
+ };
64
+
65
+ const ensureDangerousTargetPermission = async (
66
+ c: GroupContext,
67
+ targetUserId: number,
68
+ actionName: string,
69
+ ): Promise<boolean> => {
70
+ const { bot, groupIdNum, senderRole, event } = c;
71
+ const targetRole = await getMemberRole(bot, groupIdNum, targetUserId);
72
+ if (senderRole !== targetRole) return true;
73
+ await replyAdminErrorNotice({
74
+ ctx,
75
+ event,
76
+ instruction: `用户想${actionName}目标成员,但操作人与被操作人群身份相同,均为${formatGroupRole(senderRole)},群管危险操作无权操作同等级成员。请明确告诉用户无权操作同级成员。`,
77
+ fallbackMessage: "无权操作同级成员哦~",
78
+ });
79
+ return false;
80
+ };
81
+
82
+ register(
83
+ {
84
+ name: "我要头衔",
85
+ match: /^我要头衔/,
86
+ description: "普通群员给自己设置专属头衔",
87
+ usage: "我要头衔 头衔内容",
88
+ },
89
+ async (c) => {
90
+ const { ctx, event, bot, groupIdNum, body } = c;
91
+ const atUser = getAtUserId(event.message);
92
+ if (atUser != null && atUser !== Number(event.user_id)) {
93
+ await event.reply("管好自己呗~", true);
119
94
  return;
120
95
  }
121
-
122
- if (text.startsWith("改头衔") || text.startsWith("/改头衔")) {
123
- if (!(await ensureAdminPermission())) return;
124
-
125
- const rest = text.replace(/^\/?改头衔\s*/, "").trim();
126
- const atUser = getAtUserId(event.message);
127
- let targetUser: number | undefined = atUser;
128
- let title: string;
129
-
130
- const isSlashCommand = text.startsWith("/");
131
-
132
- if (atUser) {
133
- title = rest.replace(/@\d+\s*/, "").trim();
134
- } else {
135
- const parts = rest.split(/\s+/);
136
- if (parts.length < 2) {
137
- if (!isSlashCommand) return;
138
- await replyAdminErrorNotice({
139
- ctx,
140
- event,
141
- instruction:
142
- "用户在让你修改别人的头衔时缺少参数,请提示用户命令后需要加qq号或@人",
143
- fallbackMessage: "想改谁的头衔呀~",
144
- });
145
- return;
146
- }
147
- targetUser = parseInt(parts[0], 10);
148
- title = parts.slice(1).join(" ");
149
- }
150
-
151
- if (!targetUser || !title) {
96
+ const title = body.replace(/^我要头衔\s*/, "").trim();
97
+ if (!title) {
98
+ await event.reply("想要什么头衔呀~", true);
99
+ return;
100
+ }
101
+ try {
102
+ await bot.setMemberTitle(groupIdNum, event.user_id!, title);
103
+ await event.reply("done");
104
+ } catch (err) {
105
+ await replyAdminErrorNotice({
106
+ ctx,
107
+ event,
108
+ instruction: `为用户设置个人头衔执行失败:${String(err)},请简要说明失败并建议稍后重试`,
109
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
110
+ error: err,
111
+ });
112
+ }
113
+ },
114
+ );
115
+
116
+ register(
117
+ {
118
+ name: "/改头衔",
119
+ match: /^\/?改头衔(?:\s|$)/,
120
+ permission: "admin",
121
+ description: "设置群成员专属头衔",
122
+ usage: "/改头衔 qq号或@人 头衔",
123
+ },
124
+ async (c) => {
125
+ const { ctx, event, bot, groupIdNum, body } = c;
126
+ const rest = body.replace(/^\/?改头衔\s*/, "").trim();
127
+ const atUser = getAtUserId(event.message);
128
+ let targetUser: number | undefined = atUser;
129
+ let title: string;
130
+
131
+ const isSlashCommand = body.startsWith("/");
132
+
133
+ if (atUser) {
134
+ title = rest.replace(/@\d+\s*/, "").trim();
135
+ } else {
136
+ const parts = rest.split(/\s+/);
137
+ if (parts.length < 2) {
152
138
  if (!isSlashCommand) return;
153
139
  await replyAdminErrorNotice({
154
140
  ctx,
155
141
  event,
156
142
  instruction:
157
- "用户触发改头衔时参数无效,请提示用户命令后需要加qq号或@人",
143
+ "用户在让你修改别人的头衔时缺少参数,请提示用户命令后需要加qq号或@人",
158
144
  fallbackMessage: "想改谁的头衔呀~",
159
145
  });
160
146
  return;
161
147
  }
162
-
163
- try {
164
- await bot.setMemberTitle(groupIdNum, targetUser, title);
165
- await replyDone();
166
- } catch (err) {
167
- await replyAdminErrorNotice({
168
- ctx,
169
- event,
170
- instruction: `改头衔执行失败: ${String(err)},请简要说明失败并建议稍后重试`,
171
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
172
- error: err,
173
- });
174
- }
175
- return;
148
+ targetUser = parseInt(parts[0], 10);
149
+ title = parts.slice(1).join(" ");
176
150
  }
177
151
 
178
- //
179
- if (text.startsWith("踢") || text.startsWith("/踢")) {
180
- if (!(await ensureAdminPermission())) return;
181
- const atUser = getAtUserId(event.message);
182
-
183
- const isSlashCommand = text.startsWith("/");
184
- if (!atUser && !isSlashCommand) {
185
- return;
186
- }
187
- if (!atUser) {
188
- await replyAdminErrorNotice({
189
- ctx,
190
- event,
191
- instruction:
192
- "用户希望从群聊中踢出一个人,请提醒要在命令后@要踢出的人",
193
- fallbackMessage: "你想踢掉谁呀~",
194
- });
195
- return;
196
- }
197
- if (!(await ensureDangerousTargetPermission(atUser, "踢出"))) return;
198
- try {
199
- await bot.kickMember(groupIdNum, atUser);
200
- await replyDone();
201
- } catch (err) {
202
- await replyAdminErrorNotice({
203
- ctx,
204
- event,
205
- instruction: `踢执行失败${String(err)},请简要说明失败`,
206
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
207
- error: err,
208
- });
209
- }
152
+ if (!targetUser || !title) {
153
+ if (!isSlashCommand) return;
154
+ await replyAdminErrorNotice({
155
+ ctx,
156
+ event,
157
+ instruction:
158
+ "用户触发改头衔时参数无效,请提示用户命令后需要加qq号或@人",
159
+ fallbackMessage: "想改谁的头衔呀~",
160
+ });
210
161
  return;
211
162
  }
212
163
 
213
- if (
214
- text === "禁" ||
215
- text === "/禁" ||
216
- text === "禁言" ||
217
- text === "/禁言" ||
218
- text.startsWith("禁 ") ||
219
- text.startsWith("/禁 ") ||
220
- text.startsWith("禁言 ") ||
221
- text.startsWith("/禁言 ")
222
- ) {
223
- if (!(await ensureAdminPermission())) return;
224
- const atUser = getAtUserId(event.message);
225
-
226
- // 不带 / 的命令必须有@才触发,否则静默忽略
227
- const isSlashCommand = text.startsWith("/");
228
- if (!atUser && !isSlashCommand) {
229
- return; // 静默忽略,不走 chat runtime
230
- }
231
- if (!atUser) {
232
- await replyAdminErrorNotice({
233
- ctx,
234
- event,
235
- instruction:
236
- "用户希望禁言一个人,但是没有目标,请提醒要在命令后@要禁言的人",
237
- fallbackMessage: "想把谁关小黑屋呀~",
238
- });
239
- return;
240
- }
241
- if (!(await ensureDangerousTargetPermission(atUser, "禁言"))) return;
242
- const rest = text.replace(/^\/?禁言\s*/, "").trim();
243
- const durationStr = rest.replace(/@\d+\s*/, "").trim();
244
- const durationSec = parseDuration(durationStr) || 10 * 60;
245
- try {
246
- await bot.banMember(groupIdNum, atUser, durationSec);
247
- await replyDone();
248
- } catch (err) {
249
- await replyAdminErrorNotice({
250
- ctx,
251
- event,
252
- instruction: `禁言执行失败:${String(err)},请简要说明失败并建议稍后重试`,
253
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
254
- error: err,
255
- });
256
- }
257
- return;
164
+ try {
165
+ await bot.setMemberTitle(groupIdNum, targetUser, title);
166
+ await event.reply("done");
167
+ } catch (err) {
168
+ await replyAdminErrorNotice({
169
+ ctx,
170
+ event,
171
+ instruction: `改头衔执行失败: ${String(err)},请简要说明失败并建议稍后重试`,
172
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
173
+ error: err,
174
+ });
258
175
  }
259
-
260
- if (
261
- text === "解" ||
262
- text === "/解" ||
263
- text === "解禁" ||
264
- text === "/解禁" ||
265
- text.startsWith("解 ") ||
266
- text.startsWith("/解 ") ||
267
- text.startsWith("解禁 ") ||
268
- text.startsWith("/解禁 ")
269
- ) {
270
- if (!(await ensureAdminPermission())) return;
271
- const atUser = getAtUserId(event.message);
272
-
273
- const isSlashCommand = text.startsWith("/");
274
- if (!atUser && !isSlashCommand) {
275
- return; // 静默忽略,不走 chat runtime
276
- }
277
- if (!atUser) {
278
- await replyAdminErrorNotice({
279
- ctx,
280
- event,
281
- instruction:
282
- "用户想让你解除一个人的禁言,请提醒要在命令后@要解禁的人",
283
- fallbackMessage: "你想拉谁出来呀~",
284
- });
285
- return;
286
- }
287
- try {
288
- await bot.banMember(groupIdNum, atUser, 0);
289
- await replyDone();
290
- } catch (err) {
291
- await replyAdminErrorNotice({
292
- ctx,
293
- event,
294
- instruction: `解禁执行失败:${String(err)},请简要说明失败`,
295
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
296
- error: err,
297
- });
298
- }
176
+ },
177
+ );
178
+
179
+ register(
180
+ {
181
+ name: "/踢",
182
+ match: /^\/?踢(?:\s|$)/,
183
+ permission: "admin",
184
+ description: "踢出群成员",
185
+ },
186
+ async (c) => {
187
+ const { ctx, event, bot, groupIdNum, body } = c;
188
+ const atUser = getAtUserId(event.message);
189
+ const isSlashCommand = body.startsWith("/");
190
+ if (!atUser && !isSlashCommand) return;
191
+ if (!atUser) {
192
+ await replyAdminErrorNotice({
193
+ ctx,
194
+ event,
195
+ instruction:
196
+ "用户希望从群聊中踢出一个人,请提醒要在命令后@要踢出的人",
197
+ fallbackMessage: "你想踢掉谁呀~",
198
+ });
299
199
  return;
300
200
  }
301
-
302
- if (text.startsWith("设管理") || text.startsWith("/设管理")) {
303
- if (!(await ensureAdminPermission())) return;
304
- const atUser = getAtUserId(event.message);
305
-
306
- const isSlashCommand = text.startsWith("/");
307
- if (!atUser && !isSlashCommand) {
308
- return;
309
- }
310
- if (!atUser) {
311
- await replyAdminErrorNotice({
312
- ctx,
313
- event,
314
- instruction:
315
- "用户希望你把一个人设置成群管理员,但是没有目标,请提醒要在命令后@要设为管理的人",
316
- fallbackMessage: "你想给谁管理呀~",
317
- });
318
- return;
319
- }
320
- try {
321
- await bot.setMemberAdmin(groupIdNum, atUser, true);
322
- await replyDone();
323
- } catch (err) {
324
- await replyAdminErrorNotice({
325
- ctx,
326
- event,
327
- instruction: `设管理执行失败:${String(err)},请简要说明失败`,
328
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
329
- error: err,
330
- });
331
- }
201
+ if (!(await ensureDangerousTargetPermission(c, atUser, "踢出"))) return;
202
+ try {
203
+ await bot.kickMember(groupIdNum, atUser);
204
+ await event.reply("done");
205
+ } catch (err) {
206
+ await replyAdminErrorNotice({
207
+ ctx,
208
+ event,
209
+ instruction: `踢执行失败${String(err)},请简要说明失败`,
210
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
211
+ error: err,
212
+ });
213
+ }
214
+ },
215
+ );
216
+
217
+ register(
218
+ {
219
+ name: "/禁言",
220
+ match: /^\/?禁(?:言)?(?:\s|$)/,
221
+ permission: "admin",
222
+ description: "禁言群成员(支持分钟/小时/天)",
223
+ usage: "/禁言 @人 10分钟",
224
+ },
225
+ async (c) => {
226
+ const { ctx, event, bot, groupIdNum, body } = c;
227
+ const atUser = getAtUserId(event.message);
228
+ const isSlashCommand = body.startsWith("/");
229
+ if (!atUser && !isSlashCommand) return;
230
+ if (!atUser) {
231
+ await replyAdminErrorNotice({
232
+ ctx,
233
+ event,
234
+ instruction:
235
+ "用户希望禁言一个人,但是没有目标,请提醒要在命令后@要禁言的人",
236
+ fallbackMessage: "想把谁关小黑屋呀~",
237
+ });
332
238
  return;
333
239
  }
334
-
335
- if (text === "/全体禁言") {
336
- if (!(await ensureAdminPermission())) return;
337
- try {
338
- await bot.setGroupWholeBan(groupIdNum, true);
339
- await replyDone();
340
- } catch (err) {
341
- await replyAdminErrorNotice({
342
- ctx,
343
- event,
344
- instruction: `全体禁言执行失败:${String(err)},请简要说明失败`,
345
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
346
- error: err,
347
- });
348
- }
240
+ if (!(await ensureDangerousTargetPermission(c, atUser, "禁言"))) return;
241
+ const rest = body.replace(/^\/?禁言\s*/, "").trim();
242
+ const durationStr = rest.replace(/@\d+\s*/, "").trim();
243
+ const durationSec = parseDuration(durationStr) || 10 * 60;
244
+ try {
245
+ await bot.banMember(groupIdNum, atUser, durationSec);
246
+ await event.reply("done");
247
+ } catch (err) {
248
+ await replyAdminErrorNotice({
249
+ ctx,
250
+ event,
251
+ instruction: `禁言执行失败:${String(err)},请简要说明失败并建议稍后重试`,
252
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
253
+ error: err,
254
+ });
255
+ }
256
+ },
257
+ );
258
+
259
+ register(
260
+ {
261
+ name: "/解禁",
262
+ match: /^\/?解(?:禁)?(?:\s|$)/,
263
+ permission: "admin",
264
+ description: "解除群成员禁言",
265
+ },
266
+ async (c) => {
267
+ const { ctx, event, bot, groupIdNum, body } = c;
268
+ const atUser = getAtUserId(event.message);
269
+ const isSlashCommand = body.startsWith("/");
270
+ if (!atUser && !isSlashCommand) return;
271
+ if (!atUser) {
272
+ await replyAdminErrorNotice({
273
+ ctx,
274
+ event,
275
+ instruction:
276
+ "用户想让你解除一个人的禁言,请提醒要在命令后@要解禁的人",
277
+ fallbackMessage: "你想拉谁出来呀~",
278
+ });
349
279
  return;
350
280
  }
351
-
352
- if (text === "/全体解禁") {
353
- if (!(await ensureAdminPermission())) return;
354
- try {
355
- await bot.setGroupWholeBan(groupIdNum, false);
356
- await replyDone();
357
- } catch (err) {
358
- await replyAdminErrorNotice({
359
- ctx,
360
- event,
361
- instruction: `全体解禁执行失败:${String(err)},请简要说明失败`,
362
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
363
- error: err,
364
- });
365
- }
281
+ try {
282
+ await bot.banMember(groupIdNum, atUser, 0);
283
+ await event.reply("done");
284
+ } catch (err) {
285
+ await replyAdminErrorNotice({
286
+ ctx,
287
+ event,
288
+ instruction: `解禁执行失败:${String(err)},请简要说明失败`,
289
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
290
+ error: err,
291
+ });
292
+ }
293
+ },
294
+ );
295
+
296
+ register(
297
+ {
298
+ name: "/设管理",
299
+ match: /^\/?设管理(?:\s|$)/,
300
+ permission: "admin",
301
+ description: "设置群管理员",
302
+ },
303
+ async (c) => {
304
+ const { ctx, event, bot, groupIdNum, body } = c;
305
+ const atUser = getAtUserId(event.message);
306
+ const isSlashCommand = body.startsWith("/");
307
+ if (!atUser && !isSlashCommand) return;
308
+ if (!atUser) {
309
+ await replyAdminErrorNotice({
310
+ ctx,
311
+ event,
312
+ instruction:
313
+ "用户希望你把一个人设置成群管理员,但是没有目标,请提醒要在命令后@要设为管理的人",
314
+ fallbackMessage: "你想给谁管理呀~",
315
+ });
366
316
  return;
367
317
  }
368
-
369
- if (text.startsWith("改群名片") || text.startsWith("/改群名片")) {
370
- if (!isGroup || !groupIdNum) {
371
- await event.reply("这个要在群里用哦~", true);
372
- return;
373
- }
374
- if (!(await ensureAdminPermission())) return;
375
- const card = text.replace(/^\/?改群名片\s*/, "").trim();
376
- if (!card) {
377
- await replyAdminErrorNotice({
378
- ctx,
379
- event,
380
- instruction: "用户希望修改你在群里的昵称,但是没有指定名称。",
381
- fallbackMessage: "名片内容呢~",
382
- });
383
- return;
384
- }
385
- try {
386
- await bot.setMemberCard(groupIdNum, selfId, card);
387
- await event.reply("done");
388
- } catch (err) {
389
- await replyAdminErrorNotice({
390
- ctx,
391
- event,
392
- instruction: `改群名片执行失败,${String(err)}`,
393
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
394
- error: err,
395
- });
396
- }
318
+ try {
319
+ await bot.setMemberAdmin(groupIdNum, atUser, true);
320
+ await event.reply("done");
321
+ } catch (err) {
322
+ await replyAdminErrorNotice({
323
+ ctx,
324
+ event,
325
+ instruction: `设管理执行失败:${String(err)},请简要说明失败`,
326
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
327
+ error: err,
328
+ });
329
+ }
330
+ },
331
+ );
332
+
333
+ register(
334
+ {
335
+ name: "/全体禁言",
336
+ match: /^\/全体禁言$/,
337
+ permission: "admin",
338
+ description: "开启全体禁言",
339
+ },
340
+ async (c) => {
341
+ const { ctx, event, bot, groupIdNum } = c;
342
+ try {
343
+ await bot.setGroupWholeBan(groupIdNum, true);
344
+ await event.reply("done");
345
+ } catch (err) {
346
+ await replyAdminErrorNotice({
347
+ ctx,
348
+ event,
349
+ instruction: `全体禁言执行失败:${String(err)},请简要说明失败`,
350
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
351
+ error: err,
352
+ });
353
+ }
354
+ },
355
+ );
356
+
357
+ register(
358
+ {
359
+ name: "/全体解禁",
360
+ match: /^\/全体解禁$/,
361
+ permission: "admin",
362
+ description: "关闭全体禁言",
363
+ },
364
+ async (c) => {
365
+ const { ctx, event, bot, groupIdNum } = c;
366
+ try {
367
+ await bot.setGroupWholeBan(groupIdNum, false);
368
+ await event.reply("done");
369
+ } catch (err) {
370
+ await replyAdminErrorNotice({
371
+ ctx,
372
+ event,
373
+ instruction: `全体解禁执行失败:${String(err)},请简要说明失败`,
374
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
375
+ error: err,
376
+ });
377
+ }
378
+ },
379
+ );
380
+
381
+ register(
382
+ {
383
+ name: "/改群名片",
384
+ match: /^\/?改群名片(?:\s|$)/,
385
+ permission: "admin",
386
+ description: "修改Bot在群里的名片",
387
+ },
388
+ async (c) => {
389
+ const { ctx, event, bot, groupIdNum, body } = c;
390
+ const card = body.replace(/^\/?改群名片\s*/, "").trim();
391
+ if (!card) {
392
+ await replyAdminErrorNotice({
393
+ ctx,
394
+ event,
395
+ instruction: "用户希望修改你在群里的昵称,但是没有指定名称。",
396
+ fallbackMessage: "名片内容呢~",
397
+ });
397
398
  return;
398
399
  }
399
-
400
- if (text.startsWith("/改群昵称")) {
401
- if (!isGroup || !groupIdNum) {
402
- await event.reply("这个要在群里用哦~", true);
403
- return;
404
- }
405
- if (!(await ensureAdminPermission())) return;
406
- const groupName = text.replace(/^\/改群昵称\s*/, "").trim();
407
- if (!groupName) {
408
- await replyAdminErrorNotice({
409
- ctx,
410
- event,
411
- instruction:
412
- "用户希望修改群聊的名称,但是没有告诉你新的名称是啥,请提醒输入群聊名称",
413
- fallbackMessage: "群名想改成什么呀~",
414
- });
415
- return;
416
- }
417
- try {
418
- await bot.setGroupName(groupIdNum, groupName);
419
- await event.reply("done");
420
- } catch (err) {
421
- await replyAdminErrorNotice({
422
- ctx,
423
- event,
424
- instruction: `改群昵称执行失败${String(err)}`,
425
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
426
- error: err,
427
- });
428
- }
400
+ try {
401
+ await bot.setMemberCard(groupIdNum, event.self_id ?? "", card);
402
+ await event.reply("done");
403
+ } catch (err) {
404
+ await replyAdminErrorNotice({
405
+ ctx,
406
+ event,
407
+ instruction: `改群名片执行失败,${String(err)}`,
408
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
409
+ error: err,
410
+ });
411
+ }
412
+ },
413
+ );
414
+
415
+ register(
416
+ {
417
+ name: "/改群昵称",
418
+ match: /^\/改群昵称/,
419
+ permission: "admin",
420
+ description: "修改群聊名称",
421
+ },
422
+ async (c) => {
423
+ const { ctx, event, bot, groupIdNum, body } = c;
424
+ const groupName = body.replace(/^\/改群昵称\s*/, "").trim();
425
+ if (!groupName) {
426
+ await replyAdminErrorNotice({
427
+ ctx,
428
+ event,
429
+ instruction:
430
+ "用户希望修改群聊的名称,但是没有告诉你新的名称是啥,请提醒输入群聊名称",
431
+ fallbackMessage: "群名想改成什么呀~",
432
+ });
429
433
  return;
430
434
  }
431
-
432
- if (text.startsWith("/改群头像")) {
433
- if (!isGroup || !groupIdNum) {
434
- await event.reply("这个要在群里用哦~", true);
435
- return;
436
- }
437
- if (!(await ensureAdminPermission())) return;
438
- const imageUrl = extractImageUrl(event.message);
439
- if (!imageUrl) {
440
- await replyAdminErrorNotice({
441
- ctx,
442
- event,
443
- instruction:
444
- "用户希望修改群聊的群头像,但是没有发你图片,请提醒附带图片或引用图片。",
445
- fallbackMessage: "图片呢图片呢~",
446
- });
447
- return;
448
- }
449
- try {
450
- await bot.setGroupPortrait(groupIdNum, imageUrl);
451
- await event.reply("done");
452
- } catch (err) {
453
- await replyAdminErrorNotice({
454
- ctx,
455
- event,
456
- instruction: `改群头像执行失败 ${String(err)}`,
457
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
458
- error: err,
459
- });
460
- }
435
+ try {
436
+ await bot.setGroupName(groupIdNum, groupName);
437
+ await event.reply("done");
438
+ } catch (err) {
439
+ await replyAdminErrorNotice({
440
+ ctx,
441
+ event,
442
+ instruction: `改群昵称执行失败${String(err)}`,
443
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
444
+ error: err,
445
+ });
446
+ }
447
+ },
448
+ );
449
+
450
+ register(
451
+ {
452
+ name: "/改群头像",
453
+ match: /^\/改群头像/,
454
+ permission: "admin",
455
+ description: "修改群头像",
456
+ },
457
+ async (c) => {
458
+ const { ctx, event, bot, groupIdNum } = c;
459
+ const imageUrl = extractImageUrl(event.message);
460
+ if (!imageUrl) {
461
+ await replyAdminErrorNotice({
462
+ ctx,
463
+ event,
464
+ instruction:
465
+ "用户希望修改群聊的群头像,但是没有发你图片,请提醒附带图片或引用图片。",
466
+ fallbackMessage: "图片呢图片呢~",
467
+ });
461
468
  return;
462
469
  }
463
-
464
- // 撤回 — 引用一条消息后,bot 撤回该消息
465
- if (text === "/撤回" || text === "撤回") {
466
- if (!isGroup || !groupIdNum) {
467
- await event.reply("这个要在群里用哦~", true);
468
- return;
469
- }
470
- if (!(await ensureAdminPermission())) return;
471
-
472
- const quotedId = event.quote_id;
473
- if (!quotedId) {
474
- await replyAdminErrorNotice({
475
- ctx,
476
- event,
477
- instruction:
478
- "用户希望撤回一条消息,但命令中没有引用具体消息,请提醒用户引用要撤回的消息后输入 /撤回",
479
- fallbackMessage: "要撤回哪条消息呀~先引用一下~",
480
- });
481
- return;
482
- }
483
-
484
- try {
485
- await bot.recallMessage(quotedId);
486
- await replyDone();
487
- } catch (err) {
488
- await replyAdminErrorNotice({
489
- ctx,
490
- event,
491
- instruction: `撤回消息执行失败:${String(err)}。失败原因通常是Bot没有该消息的撤回权限(需要Bot为群主或管理员,且消息发出不超过2分钟),管理员无法撤回管理员的消息或群主的消息。`,
492
- fallbackMessage: `撤回失败啦~可能是没权限或消息超过2分钟了:${String(err)}`,
493
- error: err,
494
- });
495
- }
470
+ try {
471
+ await bot.setGroupPortrait(groupIdNum, imageUrl);
472
+ await event.reply("done");
473
+ } catch (err) {
474
+ await replyAdminErrorNotice({
475
+ ctx,
476
+ event,
477
+ instruction: `改群头像执行失败 ${String(err)}`,
478
+ fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
479
+ error: err,
480
+ });
481
+ }
482
+ },
483
+ );
484
+
485
+ register(
486
+ {
487
+ name: "/撤回",
488
+ match: /^\/?撤回(?:\s|$)/,
489
+ permission: "admin",
490
+ description: "撤回别人的消息",
491
+ usage: "引用一条消息后输入 /撤回",
492
+ },
493
+ async (c) => {
494
+ const { ctx, event, bot } = c;
495
+ const quotedId = event.quote_id;
496
+ if (!quotedId) {
497
+ await replyAdminErrorNotice({
498
+ ctx,
499
+ event,
500
+ instruction:
501
+ "用户希望撤回一条消息,但命令中没有引用具体消息,请提醒用户引用要撤回的消息后输入 /撤回",
502
+ fallbackMessage: "要撤回哪条消息呀~先引用一下~",
503
+ });
496
504
  return;
497
505
  }
498
- } catch (err) {
499
- ctx.logger.error(`[admin group] 未捕获异常: ${String(err)}`);
500
- if (!isGroupAdminCommand) return;
501
- await replyAdminErrorNotice({
502
- ctx,
503
- event,
504
- instruction: `群管理指令执行时发生未捕获异常:${String(err)},请简要说明失败并建议稍后重试`,
505
- fallbackMessage: `出错了,笨蛋~ ${String(err)}`,
506
- error: err,
507
- });
508
- }
509
- });
506
+ try {
507
+ await bot.recallMessage(quotedId);
508
+ await event.reply("done");
509
+ } catch (err) {
510
+ await replyAdminErrorNotice({
511
+ ctx,
512
+ event,
513
+ instruction: `撤回消息执行失败:${String(err)}。失败原因通常是Bot没有该消息的撤回权限(需要Bot为群主或管理员,且消息发出不超过2分钟),管理员无法撤回管理员的消息或群主的消息。`,
514
+ fallbackMessage: `撤回失败啦~可能是没权限或消息超过2分钟了:${String(err)}`,
515
+ error: err,
516
+ });
517
+ }
518
+ },
519
+ );
510
520
  }