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.
- package/lib/index.js +42 -40
- package/package.json +2 -2
- 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
|
|
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(`[操作]
|
|
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) =>
|
|
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 ? ">" : "<"}
|
|
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
|
|
215
|
-
const
|
|
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) =>
|
|
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 ? ">" : "<"}
|
|
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 ? ">" : "<"}
|
|
276
|
-
if ((config.maxCapacity ?? 0) > 0) logger.info(`[群组邀请] ${session.guildId} 容量 ${stats.max_member_count ?? 0} ${maxPass ? ">" : "<"}
|
|
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) =>
|
|
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(
|
|
360
|
-
activeCaptchas.delete(
|
|
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(
|
|
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
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
4
|
-
"version": "1.1
|
|
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
|
[](https://www.npmjs.com/package/koishi-plugin-onebot-verifier)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[仅支持 Onebot 平台]支持好友申请/群组邀请/加群请求等的自动审核,可根据等级与正则进行筛选,并有入群验证码、投票表决、被踢清理等功能。
|
|
6
|
+
|
|
7
|
+
---
|
|
6
8
|
|
|
7
9
|
## ✨ 核心特性
|
|
8
10
|
|
|
9
|
-
- 🛡️
|
|
10
|
-
- 🧩
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
-
|
|
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
|
-
-
|
|
27
|
+
- **通知目标 (`notifyTarget`)**: 必填。格式为 `private:QQ号` 或 `guild:群号`。用于接收各类审核申请通知。
|
|
28
|
+
- **调试模式 (`debugMode`)**: 开启后在日志中输出详细的匹配逻辑和操作记录。
|
|
29
|
+
- **被踢自动处理 (`kickBan`)**: 开启后,若机器人被移出群组,会自动尝试删除相关管理员/邀请者的好友关系。
|
|
24
30
|
|
|
25
|
-
### 2.
|
|
31
|
+
### 2. 机器人被邀请与好友申请
|
|
26
32
|
|
|
27
|
-
- **超时处理 (`friendTimeout`)**:
|
|
33
|
+
- **超时处理 (`friendTimeout`)**:
|
|
34
|
+
- `false`: 永久等待人工处理。
|
|
35
|
+
- `正数`: X 分钟后自动**通过**。
|
|
36
|
+
- `负数`: X 分钟后自动**拒绝**。
|
|
28
37
|
- **验证维度**:
|
|
29
|
-
- `friendLevel`:
|
|
30
|
-
- `friendRegex`:
|
|
31
|
-
- `minMembers
|
|
38
|
+
- `friendLevel`: 申请人的最低 QQ 等级要求。
|
|
39
|
+
- `friendRegex`: 匹配好友申请或进群邀请的文字信息。
|
|
40
|
+
- `minMembers`: 机器人受邀进群时,目标群的最低人数门槛。
|
|
41
|
+
- `maxCapacity`: 目标群的最低群容量(如要求必须是 500 人以上规模的群)。
|
|
32
42
|
|
|
33
|
-
### 3.
|
|
43
|
+
### 3. 用户加群请求 (普通验证)
|
|
34
44
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
45
|
+
- **频率限制模式 (`frequencyMode`)**: 当用户频繁申请时的策略:
|
|
46
|
+
- `delay`: 强制转为人工手动审核。
|
|
47
|
+
- `ignore`: 直接忽略该请求。
|
|
48
|
+
- `reject`: 自动拒绝。
|
|
49
|
+
- **普通验证规则 (`verifyRules`)**: 表格配置,可针对特定群号设置:
|
|
50
|
+
- 关键词正则匹配、最低等级要求、申请频率限制、以及匹配后的预设动作(接受/拒绝)。
|
|
39
51
|
|
|
40
|
-
### 4.
|
|
52
|
+
### 4. 高级验证 (特殊模式)
|
|
41
53
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
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
|
-
|
|
53
|
-
- **拒绝**:回复 `n` 或 `拒绝 [理由]`。
|
|
54
|
-
- **备注**:好友申请通过时,`y 备注名` 可直接修改好友备注。
|
|
91
|
+
当机器人退出群组或被踢出时,插件会自动触发以下逻辑:
|
|
55
92
|
|
|
56
|
-
|
|
93
|
+
1. **统计清理**:自动执行 `analyse.clear -g [群号]`,确保数据准确。
|
|
94
|
+
2. **好友切断**:如果开启了 `kickBan`,插件会识别操作者 ID,并自动执行 `delete_friend`,防止被恶意刷群后残留大量垃圾好友。
|
|
57
95
|
|
|
58
|
-
|
|
59
|
-
2. **反制措施**:若开启 `kickBan`,被踢后会自动切断与相关管理员的好友关系。
|
|
96
|
+
---
|
|
60
97
|
|
|
61
98
|
## ⚠️ 注意事项
|
|
62
99
|
|
|
63
|
-
|
|
64
|
-
|
|
100
|
+
1. **权限要求**:使用“验证码”或“自动踢人”功能,请务必保证机器人在该群拥有**管理员或群主**权限。
|
|
101
|
+
2. **通知目标**:`notifyTarget` 必须符合 `platform:id` 格式,且机器人必须与该目标存在好友关系或身处该群内。
|
|
102
|
+
3. **优先级**:普通验证规则 (`verifyRules`) 的优先级高于默认的手动审核逻辑。
|