koishi-plugin-onebot-verifier 1.1.9 → 1.2.1

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.
Files changed (3) hide show
  1. package/lib/index.js +42 -40
  2. package/package.json +2 -2
  3. package/readme.md +74 -36
package/lib/index.js CHANGED
@@ -101,7 +101,7 @@ function apply(ctx, config) {
101
101
  const activeTasks = /* @__PURE__ */ new Map();
102
102
  const activeCaptchas = /* @__PURE__ */ new Map();
103
103
  const inviterMap = /* @__PURE__ */ new Map();
104
- const requestMap = /* @__PURE__ */ new Map();
104
+ const historyMap = /* @__PURE__ */ new Map();
105
105
  const getComment = /* @__PURE__ */ __name((comment) => {
106
106
  if (!comment) return "";
107
107
  const lines = comment.split(/[\r\n]+/).map((s) => s.trim());
@@ -111,7 +111,7 @@ function apply(ctx, config) {
111
111
  const executeAction = /* @__PURE__ */ __name(async (session, kind, pass, reason = "", remark = "") => {
112
112
  try {
113
113
  const eventData = session.event?._data || {};
114
- if (config.debugMode) logger.info(`[操作] 类型:${kind} 结果:${pass ? "同意" : "拒绝"} 原因:${reason || "无"}`);
114
+ if (config.debugMode) logger.info(`[操作] 类型: ${kind} 结果: ${pass ? "同意" : "拒绝"} 原因: ${reason || "无"}`);
115
115
  if (pass && kind === "guild" && session.guildId && session.userId) inviterMap.set(session.guildId, session.userId);
116
116
  if (!pass && kind === "guild" && session.guildId && (session.event?.type === "guild-added" || eventData.notice_type === "group_increase")) {
117
117
  if (reason) await session.bot?.sendMessage(session.guildId, `${reason},将退出该群`).catch(() => {
@@ -199,24 +199,31 @@ function apply(ctx, config) {
199
199
  if (eventData.group_id) session.guildId = String(eventData.group_id);
200
200
  try {
201
201
  if (config.debugMode) logger.info(`[请求] 类型: ${kind} 数据: ${JSON.stringify(eventData)}`);
202
+ const curTime = eventData.time || 0;
203
+ for (const task of activeTasks.values()) {
204
+ const o = task.session.event?._data || {};
205
+ if (Math.abs(curTime - (o.time || 0)) < 300) {
206
+ if (task.kind === kind && o.self_id === eventData.self_id && o.user_id === eventData.user_id && o.group_id === eventData.group_id && o.comment === eventData.comment) {
207
+ task.session = session;
208
+ return;
209
+ }
210
+ }
211
+ }
202
212
  const verifyText = getComment(eventData.comment);
203
213
  if (kind === "member") {
204
- const rules = config.verifyRules?.filter((r) => String(r.guildId) === String(session.guildId)) || [];
214
+ const rules = config.verifyRules?.filter((r) => r.guildId === session.guildId) || [];
205
215
  for (const rule of rules) {
206
216
  const stats = (rule.minLevel ?? 0) > 0 && session.onebot && session.userId ? await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({})) : null;
207
217
  const levelMatch = (stats?.qqLevel ?? 0) >= (rule.minLevel ?? 0);
208
218
  const keywordMatch = !rule.keyword || new RegExp(rule.keyword, "i").test(verifyText);
209
219
  if (config.debugMode) {
210
- if ((rule.minLevel ?? 0) > 0) logger.info(`[加群请求] ${session.userId} 等级 ${stats?.qqLevel ?? 0} ${levelMatch ? ">" : "<"} "${rule.minLevel ?? 0}"`);
220
+ if ((rule.minLevel ?? 0) > 0) logger.info(`[加群请求] ${session.userId} 等级 ${stats?.qqLevel ?? 0} ${levelMatch ? ">" : "<"} ${rule.minLevel ?? 0}`);
211
221
  if (rule.keyword) logger.info(`[加群请求] ${session.userId} 内容 "${verifyText}" ${keywordMatch ? "=" : "≠"} "${rule.keyword}"`);
212
222
  }
213
223
  if (levelMatch && keywordMatch) {
214
- const historyKey = `${session.userId}:${session.guildId}`;
215
- const lastTime = requestMap.get(historyKey) || 0;
216
- const now = Date.now();
217
- const isFrequent = rule.frequency && now - lastTime < rule.frequency * 6e4;
224
+ const lastLeaveTime = historyMap.get(`${session.userId}:${session.guildId}`) || 0;
225
+ const isFrequent = rule.frequency && Date.now() - lastLeaveTime < rule.frequency * 6e4;
218
226
  if (isFrequent) {
219
- requestMap.set(historyKey, now);
220
227
  if (config.frequencyMode === "reject") {
221
228
  await executeAction(session, kind, false, "频繁申请,自动拒绝");
222
229
  await sendNotice(session, kind, "auto_reject");
@@ -227,7 +234,6 @@ function apply(ctx, config) {
227
234
  return await setupManual(session, kind, void 0, false, rule.action === "accept");
228
235
  }
229
236
  }
230
- requestMap.set(historyKey, now);
231
237
  if (rule.action) {
232
238
  await executeAction(session, kind, rule.action === "accept", rule.action === "accept" ? "" : "错误回答,自动拒绝");
233
239
  await sendNotice(session, kind, rule.action === "accept" ? "auto_pass" : "auto_reject");
@@ -235,7 +241,7 @@ function apply(ctx, config) {
235
241
  }
236
242
  }
237
243
  }
238
- const specialRule = config.specialRules?.find((r) => String(r.guildId) === String(session.guildId));
244
+ const specialRule = config.specialRules?.find((r) => r.guildId === session.guildId);
239
245
  if (specialRule) {
240
246
  if (specialRule.mode === "vote") return await setupManual(session, kind, "vote", config.voteInSitu);
241
247
  if (specialRule.mode === "captcha") {
@@ -252,7 +258,7 @@ function apply(ctx, config) {
252
258
  if (config.friendLevel && config.friendLevel > 0 && session.onebot && session.userId) {
253
259
  const stats = await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({}));
254
260
  levelPass = (stats.qqLevel ?? 0) >= config.friendLevel;
255
- if (config.debugMode) logger.info(`[好友验证] ${session.userId} 等级 ${stats.qqLevel ?? 0} ${levelPass ? ">" : "<"} "${config.friendLevel}"`);
261
+ if (config.debugMode) logger.info(`[好友验证] ${session.userId} 等级 ${stats.qqLevel ?? 0} ${levelPass ? ">" : "<"} ${config.friendLevel}`);
256
262
  }
257
263
  if (config.friendRegex) {
258
264
  regexPass = new RegExp(config.friendRegex, "i").test(verifyText);
@@ -272,8 +278,8 @@ function apply(ctx, config) {
272
278
  const minPass = (stats.member_count ?? 0) >= (config.minMembers ?? 0);
273
279
  const maxPass = (stats.max_member_count ?? 0) >= (config.maxCapacity ?? 0);
274
280
  if (config.debugMode) {
275
- if ((config.minMembers ?? 0) > 0) logger.info(`[群组邀请] ${session.guildId} 人数 ${stats.member_count ?? 0} ${minPass ? ">" : "<"} "${config.minMembers ?? 0}"`);
276
- if ((config.maxCapacity ?? 0) > 0) logger.info(`[群组邀请] ${session.guildId} 容量 ${stats.max_member_count ?? 0} ${maxPass ? ">" : "<"} "${config.maxCapacity ?? 0}"`);
281
+ if ((config.minMembers ?? 0) > 0) logger.info(`[群组邀请] ${session.guildId} 人数 ${stats.member_count ?? 0} ${minPass ? ">" : "<"} ${config.minMembers ?? 0}`);
282
+ if ((config.maxCapacity ?? 0) > 0) logger.info(`[群组邀请] ${session.guildId} 容量 ${stats.max_member_count ?? 0} ${maxPass ? ">" : "<"} ${config.maxCapacity ?? 0}`);
277
283
  }
278
284
  if (!minPass) verdict = `群人数不足 ${config.minMembers ?? 0} 人`;
279
285
  else if (!maxPass) verdict = `群容量不足 ${config.maxCapacity ?? 0} 人`;
@@ -326,9 +332,13 @@ function apply(ctx, config) {
326
332
  ctx.on("guild-request", hookEvent("guild"));
327
333
  ctx.on("guild-member-request", hookEvent("member"));
328
334
  ctx.on("guild-added", hookEvent("guild"));
335
+ ctx.on("guild-member-removed", async (session) => {
336
+ if (!session.guildId || !session.userId) return;
337
+ if (config.verifyRules?.some((r) => r.guildId === session.guildId)) historyMap.set(`${session.userId}:${session.guildId}`, Date.now());
338
+ });
329
339
  ctx.on("guild-member-added", async (session) => {
330
340
  if (!config.specialRules || !session.guildId || !session.userId) return;
331
- const rule = config.specialRules.find((r) => String(r.guildId) === String(session.guildId));
341
+ const rule = config.specialRules.find((r) => r.guildId === session.guildId);
332
342
  if (rule?.mode === "captcha") {
333
343
  let a, b, op = "+", answer;
334
344
  if (config.captchaDiff === "simple") {
@@ -353,22 +363,24 @@ function apply(ctx, config) {
353
363
  op = "×";
354
364
  answer = (a * b).toString();
355
365
  }
356
- const captchaKey = `${session.guildId}:${session.userId}`;
357
366
  await session.send(`<at id="${session.userId}"/> 请在 60 秒内回复计算结果,以进行验证:${a} ${op} ${b} =`);
358
367
  const timer = setTimeout(async () => {
359
- if (activeCaptchas.has(captchaKey)) {
360
- activeCaptchas.delete(captchaKey);
368
+ if (activeCaptchas.has(`${session.userId}:${session.guildId}`)) {
369
+ activeCaptchas.delete(`${session.userId}:${session.guildId}`);
361
370
  await session.send(`<at id="${session.userId}"/> 验证失败,将被移出本群。`);
362
371
  await session.onebot?.setGroupKick(session.guildId, session.userId, false);
363
372
  }
364
373
  }, 6e4);
365
- activeCaptchas.set(captchaKey, { guildId: session.guildId, userId: session.userId, answer, timer });
374
+ activeCaptchas.set(`${session.userId}:${session.guildId}`, { guildId: session.guildId, userId: session.userId, answer, timer });
366
375
  }
367
376
  });
368
377
  ctx.on("guild-removed", async (session) => {
369
378
  if (session.guildId) {
370
- if (config.debugMode) logger.info(`[事件] 退出: ${session.guildId} 数据: ${JSON.stringify(session.event?._data)}`);
371
379
  const eventData = session.event?._data || {};
380
+ const curTime = eventData.time || 0;
381
+ if (Math.abs(curTime - (historyMap.get(session.guildId) || 0)) < 300) return;
382
+ historyMap.set(session.guildId, curTime);
383
+ if (config.debugMode) logger.info(`[事件] 退出: ${session.guildId} 数据: ${JSON.stringify(eventData)}`);
372
384
  if (eventData.sub_type === "kick_me") {
373
385
  const inviterId = inviterMap.get(session.guildId);
374
386
  if (inviterId) {
@@ -387,46 +399,36 @@ function apply(ctx, config) {
387
399
  await session.execute(`analyse.clear -g ${session.guildId}`).catch(() => {
388
400
  });
389
401
  if (config.debugMode) logger.info(`[操作] 清理群组数据: ${session.guildId}`);
402
+ await sendNotice(session, "removed");
390
403
  }
391
- await sendNotice(session, "removed");
392
404
  });
393
405
  ctx.middleware(async (session, next) => {
394
406
  if (typeof session.content !== "string") return next();
395
407
  if (session.guildId && session.userId) {
396
- const captchaKey = `${session.guildId}:${session.userId}`;
397
- const captcha = activeCaptchas.get(captchaKey);
398
- if (captcha) {
399
- const input2 = session.content.trim();
400
- if (input2 === captcha.answer) {
401
- clearTimeout(captcha.timer);
402
- activeCaptchas.delete(captchaKey);
403
- await session.send(`<at id="${session.userId}"/> 验证成功,欢迎加入本群!`);
404
- return;
405
- }
408
+ const captcha = activeCaptchas.get(`${session.userId}:${session.guildId}`);
409
+ if (captcha && session.content.trim() === captcha.answer) {
410
+ clearTimeout(captcha.timer);
411
+ activeCaptchas.delete(`${session.userId}:${session.guildId}`);
412
+ await session.send(`<at id="${session.userId}"/> 验证成功,欢迎加入本群!`);
413
+ return;
406
414
  }
407
415
  }
408
416
  if (!session.quote?.id) return next();
409
417
  const task = activeTasks.get(session.quote.id);
410
418
  if (!task) return next();
411
419
  const [ntType, ntId] = (config.notifyTarget || "").split(":");
412
- const isGlobalNotifyTarget = ntType === "private" ? session.userId === ntId : session.guildId === ntId;
413
- const isInSituGuild = task.inSitu && session.guildId && session.guildId === task.session.guildId;
414
- if (!isGlobalNotifyTarget && !isInSituGuild) return next();
420
+ if ((ntType === "private" ? session.userId !== ntId : session.guildId !== ntId) && !(task.inSitu && session.guildId === task.session.guildId)) return next();
415
421
  const input = session.content.replace(/<(quote|at)\s+[^>]*\/>/gi, "").trim();
416
422
  const cmdMatch = input.match(/^(y|n|通过|拒绝)(?:\s+(.*))?$/i);
417
423
  if (!cmdMatch) return next();
418
424
  const isApprove = ["y", "通过"].includes(cmdMatch[1].toLowerCase());
419
425
  const extraInfo = cmdMatch[2]?.trim() || "";
420
426
  if (config.debugMode) logger.info(`[操作] 收到指令: ${isApprove ? "同意" : "拒绝"}`);
421
- if (task.specialMode === "vote") {
422
- await handleSpecialVote(session, task, isApprove, extraInfo);
423
- return;
424
- }
427
+ if (task.specialMode === "vote") return await handleSpecialVote(session, task, isApprove, extraInfo);
425
428
  if (task.timer) clearTimeout(task.timer);
426
429
  task.messages.forEach((msg) => activeTasks.delete(msg));
427
430
  const isSuccess = await executeAction(task.session, task.kind, isApprove, isApprove ? "" : extraInfo, isApprove && task.kind === "friend" ? extraInfo : "");
428
- const replyText = isSuccess ? `已${isApprove ? "通过" : "拒绝"}该请求` : `处理请求失败`;
429
- await session.send(replyText).catch(() => {
431
+ await session.send(isSuccess ? `已${isApprove ? "通过" : "拒绝"}该请求` : `处理请求失败`).catch(() => {
430
432
  });
431
433
  });
432
434
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-onebot-verifier",
3
- "description": "适用于 OneBot 的审核插件。不仅支持好友/加群自动审核,还支持入群验证码、投票表决、被踢自动清理等功能。",
4
- "version": "1.1.9",
3
+ "description": "[仅支持 Onebot 平台]支持好友申请/群组邀请/加群请求等的自动审核,可根据等级与正则进行筛选,并有入群验证码、投票表决、被踢清理等功能。",
4
+ "version": "1.2.1",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],
package/readme.md CHANGED
@@ -2,63 +2,101 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/koishi-plugin-onebot-verifier?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-onebot-verifier)
4
4
 
5
- 适用于 OneBot 的审核插件。不仅支持好友/加群自动审核,还支持**入群验证码**、**投票表决**、**被踢自动清理**等功能。
5
+ [仅支持 Onebot 平台]支持好友申请/群组邀请/加群请求等的自动审核,可根据等级与正则进行筛选,并有入群验证码、投票表决、被踢清理等功能。
6
+
7
+ ---
6
8
 
7
9
  ## ✨ 核心特性
8
10
 
9
- - 🛡️ **全场景覆盖**:好友申请、用户加群、机器人受邀进群、被踢后续处理。
10
- - 🧩 **多种验证模式**:
11
- - **自动规则**:基于 QQ 等级、正则关键词、群规模(成员数/容量)的秒级自动化处理。
12
- - **入群验证码**:新成员进群后触发数学运算挑战,失败自动移出。
13
- - **民主投票**:将加群申请转为群内投票,达到指定票数自动通过/拒绝。
14
- - 交互式人工干预:引用通知消息回复 `y/n` 即可操作,支持超时自动决策。
15
- - 🧹 **智能清理**:机器人被恶意踢出时,可自动删除邀请者或操作者的好友,并清理数据统计。
16
- - 📍 **就地审核**:支持在发生申请的群内直接发起审核,无需跳转管理群。
11
+ - 🛡️ **多维度自动化**:基于 QQ 等级、正则匹配、群员规模(成员数/总容量)自动处理请求。
12
+ - 🧩 **高级验证模式**:
13
+ - **数学验证码 (Captcha)**:进群后自动触发数学计算挑战,限时未通过自动移出。
14
+ - **民主投票 (Vote)**:加群申请可转为群内投票,支持设置通过/拒绝的票数阈值。
15
+ - 交互式人工干预:引用通知消息回复 `y/n` 即可操作,支持设置超时后的默认决策。
16
+ - 🧹 **智能清理与反制**:
17
+ - 被移出群组时,自动清理 `analyse` 插件的相关统计数据。
18
+ - **被踢反制**:自动删除将机器人移出群组的操作者或原始邀请者的好友。
19
+ - 📍 **就地审核逻辑**:支持“原群投票”模式,无需在管理私聊间跳转,直接在申请发生的群内解决。
20
+
21
+ ---
17
22
 
18
- ## ⚙️ 配置项深度解析
23
+ ## ⚙️ 配置项详解
19
24
 
20
25
  ### 1. 基础配置
21
26
 
22
- - **通知目标 (`notifyTarget`)**: 必填。格式为 `private:QQ号` 或 `guild:群号`。
23
- - **被踢自动处理 (`kickBan`)**: 开启后,若机器人被移出群组,将自动尝试删除邀请者和操作者的好友。
27
+ - **通知目标 (`notifyTarget`)**: 必填。格式为 `private:QQ号` 或 `guild:群号`。用于接收各类审核申请通知。
28
+ - **调试模式 (`debugMode`)**: 开启后在日志中输出详细的匹配逻辑和操作记录。
29
+ - **被踢自动处理 (`kickBan`)**: 开启后,若机器人被移出群组,会自动尝试删除相关管理员/邀请者的好友关系。
24
30
 
25
- ### 2. 好友与邀群 (机器人被邀)
31
+ ### 2. 机器人被邀请与好友申请
26
32
 
27
- - **超时处理 (`friendTimeout`)**: 数字表示分钟。正数表示超时自动同意,负数表示超时自动拒绝,`false` 表示永久等待。
33
+ - **超时处理 (`friendTimeout`)**:
34
+ - `false`: 永久等待人工处理。
35
+ - `正数`: X 分钟后自动**通过**。
36
+ - `负数`: X 分钟后自动**拒绝**。
28
37
  - **验证维度**:
29
- - `friendLevel`: 申请人最低 QQ 等级。
30
- - `friendRegex`: 匹配好友申请说明。
31
- - `minMembers` / `maxCapacity`: 校验目标群的活跃程度与规模。
38
+ - `friendLevel`: 申请人的最低 QQ 等级要求。
39
+ - `friendRegex`: 匹配好友申请或进群邀请的文字信息。
40
+ - `minMembers`: 机器人受邀进群时,目标群的最低人数门槛。
41
+ - `maxCapacity`: 目标群的最低群容量(如要求必须是 500 人以上规模的群)。
32
42
 
33
- ### 3. 加群请求 (用户入群)
43
+ ### 3. 用户加群请求 (普通验证)
34
44
 
35
- - **普通验证规则 (`verifyRules`)**: 表格化配置。可针对不同群设置不同的关键词正则和等级要求。
36
- - **特殊验证模式 (`specialRules`)**:
37
- - **投票模式 (vote)**: 引用通知回复 `y/n` 累计票数。
38
- - **验证码模式 (captcha)**: 机器人自动先通过申请,用户进群后需在 60s 内回复计算题。
45
+ - **频率限制模式 (`frequencyMode`)**: 当用户频繁申请时的策略:
46
+ - `delay`: 强制转为人工手动审核。
47
+ - `ignore`: 直接忽略该请求。
48
+ - `reject`: 自动拒绝。
49
+ - **普通验证规则 (`verifyRules`)**: 表格配置,可针对特定群号设置:
50
+ - 关键词正则匹配、最低等级要求、申请频率限制、以及匹配后的预设动作(接受/拒绝)。
39
51
 
40
- ### 4. 进阶安全
52
+ ### 4. 高级验证 (特殊模式)
41
53
 
42
- - **投票比例 (`voteRatio`)**: 格式如 `3:2`,表示 3 票赞成则通过,2 票反对则拒绝。
43
- - **就地投票 (`voteInSitu`)**: 开启后,投票通知将直接发往“该申请所属的群”而非全局通知目标。
44
- - **验证码难度 (`captchaDiff`)**: 简单(100以内加减)、中等(两位数乘一位数)、困难(两位数乘两位数)。
54
+ - **特殊验证模式 (`specialRules`)**:
55
+ - **投票模式 (`vote`)**: 申请信息发至通知目标(或原群),群员投票决定。
56
+ - **验证码模式 (`captcha`)**: 机器人先自动通过申请,用户入群后需完成挑战。
57
+ - **模式细节**:
58
+ - `voteRatio`: 投票通过/拒绝比例(如 `3:2` 表示 3 人赞成通过,2 人反对则拒绝)。
59
+ - `voteInSitu`: 是否在发生申请的群内直接发起投票。
60
+ - `captchaDiff`: 验证码难度(简单:加减法;中等:两位数乘一位数;困难:两位数乘法)。
61
+
62
+ ---
45
63
 
46
64
  ## 🎮 指令交互指南
47
65
 
48
- ### 人工审核 / 投票
66
+ ### 人工审核与投票
67
+
68
+ 当收到插件推送的审核通知时,**通过引用(回复)该消息**来进行操作:
69
+
70
+ | 动作 | 指令 | 效果 |
71
+ | :--- | :--- | :--- |
72
+ | **通过申请** | `y` 或 `通过` | 同意该请求 |
73
+ | **拒绝申请** | `n` 或 `拒绝 [理由]` | 拒绝请求并(可选)发送拒绝理由 |
74
+ | **好友备注** | `y [备注名]` | 通过好友申请并直接设置备注(仅限好友类型) |
75
+ | **参与投票** | 引用消息回复 `y` 或 `n` | 累加赞成或反对票数,达到阈值自动执行 |
76
+
77
+ ### 验证码挑战
78
+
79
+ 在开启了 `captcha` 模式的群组中:
80
+
81
+ 1. 用户进群后,机器人会 `@` 该用户并发出题目。
82
+ 2. 用户需在 **60 秒** 内直接回复正确数字。
83
+ 3. **超时或错误**:机器人将自动将该用户移出群组(需机器人拥有管理员权限)。
84
+
85
+ ---
86
+
87
+ ## 🛠️ 进阶功能说明
49
88
 
50
- 当收到审核通知时,**引用该消息**并回复:
89
+ ### 自动化数据清理
51
90
 
52
- - **通过**:回复 `y` 或 `通过`。
53
- - **拒绝**:回复 `n` 或 `拒绝 [理由]`。
54
- - **备注**:好友申请通过时,`y 备注名` 可直接修改好友备注。
91
+ 当机器人退出群组或被踢出时,插件会自动触发以下逻辑:
55
92
 
56
- ### 自动清理逻辑
93
+ 1. **统计清理**:自动执行 `analyse.clear -g [群号]`,确保数据准确。
94
+ 2. **好友切断**:如果开启了 `kickBan`,插件会识别操作者 ID,并自动执行 `delete_friend`,防止被恶意刷群后残留大量垃圾好友。
57
95
 
58
- 1. **数据清理**:当机器人退出或被踢出群组时,插件会自动执行 `analyse.clear`清理相关统计。
59
- 2. **反制措施**:若开启 `kickBan`,被踢后会自动切断与相关管理员的好友关系。
96
+ ---
60
97
 
61
98
  ## ⚠️ 注意事项
62
99
 
63
- - 使用验证码模式时,请确保机器人拥有**管理员权限**,否则无法在验证失败时踢出成员。
64
- - `notifyTarget` 必须符合 `platform:id` 格式,且机器人必须与该目标存在好友或群成员关系。
100
+ 1. **权限要求**:使用“验证码”或“自动踢人”功能,请务必保证机器人在该群拥有**管理员或群主**权限。
101
+ 2. **通知目标**:`notifyTarget` 必须符合 `platform:id` 格式,且机器人必须与该目标存在好友关系或身处该群内。
102
+ 3. **优先级**:普通验证规则 (`verifyRules`) 的优先级高于默认的手动审核逻辑。