koishi-plugin-group-verification 1.0.27 → 1.0.28
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.d.ts +2 -0
- package/lib/index.js +83 -38
- package/package.json +1 -1
- package/readme.md +17 -5
- package/src/index.ts +89 -40
package/lib/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface GroupVerificationStats {
|
|
|
26
26
|
autoApproved: number;
|
|
27
27
|
manuallyApproved: number;
|
|
28
28
|
rejected: number;
|
|
29
|
+
totalJoined: number;
|
|
29
30
|
lastUpdated: string | Date;
|
|
30
31
|
}
|
|
31
32
|
export interface PendingVerification {
|
|
@@ -92,6 +93,7 @@ export declare function mergeReminder(existingConfig: any | null, cleanedOptions
|
|
|
92
93
|
};
|
|
93
94
|
export declare function syncTotalStats(ctx: Context): Promise<void>;
|
|
94
95
|
export declare function updateStats(ctx: Context, groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected'): Promise<void>;
|
|
96
|
+
export declare function incrementTotal(ctx: Context, groupId: string): Promise<void>;
|
|
95
97
|
export declare function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]>;
|
|
96
98
|
export declare function handleGuildMemberRequestEvent(ctx: Context, session: any): Promise<void>;
|
|
97
99
|
export declare function __getAutoQueue(): Map<string, Set<string>>;
|
package/lib/index.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(src_exports, {
|
|
|
26
26
|
checkPermission: () => checkPermission,
|
|
27
27
|
handleFailedVerification: () => handleFailedVerification,
|
|
28
28
|
handleGuildMemberRequestEvent: () => handleGuildMemberRequestEvent,
|
|
29
|
+
incrementTotal: () => incrementTotal,
|
|
29
30
|
inject: () => inject,
|
|
30
31
|
mergeReminder: () => mergeReminder,
|
|
31
32
|
name: () => name,
|
|
@@ -207,13 +208,15 @@ async function syncTotalStats(ctx) {
|
|
|
207
208
|
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0);
|
|
208
209
|
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0);
|
|
209
210
|
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0);
|
|
211
|
+
const totalJoined = allStats.reduce((sum, stat) => sum + (stat.totalJoined || 0), 0);
|
|
210
212
|
await ctx.database.set("group_verification_stats", { groupId: "TOTAL" }, {
|
|
211
213
|
autoApproved: totalAutoApproved,
|
|
212
214
|
manuallyApproved: totalManuallyApproved,
|
|
213
215
|
rejected: totalRejected,
|
|
216
|
+
totalJoined,
|
|
214
217
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
218
|
});
|
|
216
|
-
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`);
|
|
219
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`);
|
|
217
220
|
}
|
|
218
221
|
} catch (error) {
|
|
219
222
|
logger.error("同步总计统计时出错:", error);
|
|
@@ -234,12 +237,34 @@ async function updateStats(ctx, groupId, action) {
|
|
|
234
237
|
autoApproved: action === "autoApproved" ? 1 : 0,
|
|
235
238
|
manuallyApproved: action === "manuallyApproved" ? 1 : 0,
|
|
236
239
|
rejected: action === "rejected" ? 1 : 0,
|
|
240
|
+
totalJoined: 0,
|
|
237
241
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
238
242
|
});
|
|
239
243
|
}
|
|
240
244
|
await syncTotalStats(ctx);
|
|
241
245
|
}
|
|
242
246
|
__name(updateStats, "updateStats");
|
|
247
|
+
async function incrementTotal(ctx, groupId) {
|
|
248
|
+
const existingStats = await ctx.database.get("group_verification_stats", { groupId });
|
|
249
|
+
if (existingStats.length > 0) {
|
|
250
|
+
const stats = existingStats[0];
|
|
251
|
+
await ctx.database.set("group_verification_stats", { id: stats.id }, {
|
|
252
|
+
totalJoined: (stats.totalJoined || 0) + 1,
|
|
253
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
await ctx.database.create("group_verification_stats", {
|
|
257
|
+
groupId,
|
|
258
|
+
autoApproved: 0,
|
|
259
|
+
manuallyApproved: 0,
|
|
260
|
+
rejected: 0,
|
|
261
|
+
totalJoined: 1,
|
|
262
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
await syncTotalStats(ctx);
|
|
266
|
+
}
|
|
267
|
+
__name(incrementTotal, "incrementTotal");
|
|
243
268
|
async function checkPermission(session, targetGroupId) {
|
|
244
269
|
const groupId = targetGroupId || session.guildId;
|
|
245
270
|
if (!groupId) {
|
|
@@ -454,7 +479,8 @@ async function verifyApplication(config, message, session) {
|
|
|
454
479
|
const thresholdPct = config.reviewParameters !== void 0 && config.reviewParameters !== null ? config.reviewParameters : 100;
|
|
455
480
|
const ratio = matchedCount / config.keywords.length;
|
|
456
481
|
isValid = ratio >= thresholdPct / 100;
|
|
457
|
-
|
|
482
|
+
const needed = Math.ceil(config.keywords.length * thresholdPct / 100);
|
|
483
|
+
requiredThreshold = `${needed}`;
|
|
458
484
|
}
|
|
459
485
|
break;
|
|
460
486
|
case 3:
|
|
@@ -559,6 +585,7 @@ function apply(ctx, config) {
|
|
|
559
585
|
autoApproved: "integer",
|
|
560
586
|
manuallyApproved: "integer",
|
|
561
587
|
rejected: "integer",
|
|
588
|
+
totalJoined: "integer",
|
|
562
589
|
// store as string (ISO timestamp) to preserve full date+time;
|
|
563
590
|
// Koishi `date` type truncates to day which leads to 00:00:00.
|
|
564
591
|
lastUpdated: "string"
|
|
@@ -587,6 +614,7 @@ function apply(ctx, config) {
|
|
|
587
614
|
ctx.on("guild-member-added", async (session) => {
|
|
588
615
|
const groupId = session.guildId;
|
|
589
616
|
const userId = session.userId;
|
|
617
|
+
await incrementTotal(ctx, groupId);
|
|
590
618
|
const set = autoQueue.get(groupId);
|
|
591
619
|
if (set && set.has(userId)) {
|
|
592
620
|
await updateStats(ctx, groupId, "autoApproved");
|
|
@@ -978,33 +1006,38 @@ ${debugInfo}`];
|
|
|
978
1006
|
return "当前无待审核的加群申请";
|
|
979
1007
|
}
|
|
980
1008
|
let approvedCount = 0;
|
|
1009
|
+
let skippedCount = 0;
|
|
981
1010
|
for (const request2 of pendingRequests2) {
|
|
982
|
-
|
|
983
|
-
|
|
1011
|
+
if (request2.requestId) {
|
|
1012
|
+
try {
|
|
984
1013
|
await session.bot.handleGuildMemberRequest(request2.requestId, true);
|
|
1014
|
+
approvedCount++;
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
985
1017
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
approvedCount++;
|
|
989
|
-
} catch (error) {
|
|
990
|
-
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
1018
|
+
} else {
|
|
1019
|
+
skippedCount++;
|
|
991
1020
|
}
|
|
1021
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
992
1022
|
}
|
|
993
|
-
|
|
1023
|
+
let msg = `已处理 ${approvedCount} 个加群申请`;
|
|
1024
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1025
|
+
return msg;
|
|
994
1026
|
} else {
|
|
995
|
-
let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime"]);
|
|
1027
|
+
let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime", "requestId"]);
|
|
996
1028
|
if (pending.length === 0) {
|
|
997
1029
|
return "当前无待审核的加群申请";
|
|
998
1030
|
}
|
|
999
1031
|
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)));
|
|
1000
1032
|
const request2 = pending[0];
|
|
1033
|
+
if (!request2.requestId) {
|
|
1034
|
+
return `用户 ${request2.userId} 的申请缺少 requestId,无法自动同意`;
|
|
1035
|
+
}
|
|
1001
1036
|
try {
|
|
1002
|
-
|
|
1003
|
-
await session.bot.handleGuildMemberRequest(request2.requestId, true);
|
|
1004
|
-
}
|
|
1037
|
+
await session.bot.handleGuildMemberRequest(request2.requestId, true);
|
|
1005
1038
|
await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
|
|
1006
|
-
|
|
1007
|
-
return `已同意用户 ${
|
|
1039
|
+
const displayName = request2.userName && request2.userName !== request2.userId ? `${request2.userName}(${request2.userId})` : request2.userId;
|
|
1040
|
+
return `已同意用户 ${displayName} 的加群申请`;
|
|
1008
1041
|
} catch (error) {
|
|
1009
1042
|
return `处理申请时出错: ${error.message}`;
|
|
1010
1043
|
}
|
|
@@ -1018,13 +1051,14 @@ ${debugInfo}`];
|
|
|
1018
1051
|
return `未找到用户 ${userId} 的待审核申请`;
|
|
1019
1052
|
}
|
|
1020
1053
|
const request = pendingRequests[0];
|
|
1054
|
+
if (!request.requestId) {
|
|
1055
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动同意`;
|
|
1056
|
+
}
|
|
1021
1057
|
try {
|
|
1022
|
-
|
|
1023
|
-
await session.bot.handleGuildMemberRequest(request.requestId, true);
|
|
1024
|
-
}
|
|
1058
|
+
await session.bot.handleGuildMemberRequest(request.requestId, true);
|
|
1025
1059
|
await ctx.database.remove("group_verification_pending", { groupId, userId });
|
|
1026
|
-
|
|
1027
|
-
return `已同意用户 ${
|
|
1060
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId;
|
|
1061
|
+
return `已同意用户 ${displayName} 的加群申请`;
|
|
1028
1062
|
} catch (error) {
|
|
1029
1063
|
return `处理申请时出错: ${error.message}`;
|
|
1030
1064
|
}
|
|
@@ -1053,33 +1087,40 @@ ${debugInfo}`];
|
|
|
1053
1087
|
return "当前无待审核的加群申请";
|
|
1054
1088
|
}
|
|
1055
1089
|
let rejectedCount = 0;
|
|
1090
|
+
let skippedCount = 0;
|
|
1056
1091
|
for (const request2 of pendingRequests2) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1092
|
+
if (request2.requestId) {
|
|
1093
|
+
try {
|
|
1059
1094
|
await session.bot.handleGuildMemberRequest(request2.requestId, false);
|
|
1095
|
+
rejectedCount++;
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
1060
1098
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
rejectedCount++;
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
1099
|
+
} else {
|
|
1100
|
+
skippedCount++;
|
|
1066
1101
|
}
|
|
1102
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
1103
|
+
await updateStats(ctx, groupId, "rejected");
|
|
1067
1104
|
}
|
|
1068
|
-
|
|
1105
|
+
let msg = `已拒绝 ${rejectedCount} 个加群申请`;
|
|
1106
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1107
|
+
return msg;
|
|
1069
1108
|
} else {
|
|
1070
|
-
let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime"]);
|
|
1109
|
+
let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime", "requestId"]);
|
|
1071
1110
|
if (pending.length === 0) {
|
|
1072
1111
|
return "当前无待审核的加群申请";
|
|
1073
1112
|
}
|
|
1074
1113
|
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)));
|
|
1075
1114
|
const request2 = pending[0];
|
|
1115
|
+
if (!request2.requestId) {
|
|
1116
|
+
return `用户 ${request2.userId} 的申请缺少 requestId,无法自动拒绝`;
|
|
1117
|
+
}
|
|
1076
1118
|
try {
|
|
1077
|
-
|
|
1078
|
-
await session.bot.handleGuildMemberRequest(request2.requestId, false);
|
|
1079
|
-
}
|
|
1119
|
+
await session.bot.handleGuildMemberRequest(request2.requestId, false);
|
|
1080
1120
|
await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
|
|
1081
1121
|
await updateStats(ctx, groupId, "rejected");
|
|
1082
|
-
|
|
1122
|
+
const displayName = request2.userName && request2.userName !== request2.userId ? `${request2.userName}(${request2.userId})` : request2.userId;
|
|
1123
|
+
return `已拒绝用户 ${displayName} 的加群申请`;
|
|
1083
1124
|
} catch (error) {
|
|
1084
1125
|
return `处理申请时出错: ${error.message}`;
|
|
1085
1126
|
}
|
|
@@ -1093,13 +1134,15 @@ ${debugInfo}`];
|
|
|
1093
1134
|
return `未找到用户 ${userId} 的待审核申请`;
|
|
1094
1135
|
}
|
|
1095
1136
|
const request = pendingRequests[0];
|
|
1137
|
+
if (!request.requestId) {
|
|
1138
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动拒绝`;
|
|
1139
|
+
}
|
|
1096
1140
|
try {
|
|
1097
|
-
|
|
1098
|
-
await session.bot.handleGuildMemberRequest(request.requestId, false);
|
|
1099
|
-
}
|
|
1141
|
+
await session.bot.handleGuildMemberRequest(request.requestId, false);
|
|
1100
1142
|
await ctx.database.remove("group_verification_pending", { groupId, userId });
|
|
1101
1143
|
await updateStats(ctx, groupId, "rejected");
|
|
1102
|
-
|
|
1144
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId;
|
|
1145
|
+
return `已拒绝用户 ${displayName} 的加群申请`;
|
|
1103
1146
|
} catch (error) {
|
|
1104
1147
|
return `处理申请时出错: ${error.message}`;
|
|
1105
1148
|
}
|
|
@@ -1244,6 +1287,7 @@ ${debugInfo}`];
|
|
|
1244
1287
|
for (const st of stats) {
|
|
1245
1288
|
const updates = {};
|
|
1246
1289
|
if (st.lastUpdated instanceof Date) updates.lastUpdated = st.lastUpdated.toISOString();
|
|
1290
|
+
if (st.totalJoined === void 0) updates.totalJoined = 0;
|
|
1247
1291
|
if (Object.keys(updates).length) {
|
|
1248
1292
|
await ctx.database.set("group_verification_stats", { id: st.id }, updates);
|
|
1249
1293
|
}
|
|
@@ -1314,6 +1358,7 @@ __name(apply, "apply");
|
|
|
1314
1358
|
checkPermission,
|
|
1315
1359
|
handleFailedVerification,
|
|
1316
1360
|
handleGuildMemberRequestEvent,
|
|
1361
|
+
incrementTotal,
|
|
1317
1362
|
inject,
|
|
1318
1363
|
mergeReminder,
|
|
1319
1364
|
name,
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- **多关键词匹配**:支持多个关键词的灵活配置
|
|
8
8
|
- **四种审核方式**:全部同意、按数量同意、按比例同意、全部拒绝
|
|
9
9
|
- **完善的权限控制**:群主/管理员权限或 Koishi 三级以上权限
|
|
10
|
-
-
|
|
10
|
+
- **详细的统计功能**:自动记录审核统计和手动入群统计(含自动通过、手动通过、拒绝以及总入群人数)
|
|
11
11
|
- **灵活的消息配置**:支持自定义提醒消息和禁用功能
|
|
12
12
|
- **友好的命令系统**:丰富的别名和快捷命令
|
|
13
13
|
|
|
@@ -54,6 +54,12 @@ group-verify.config -i 123456789 关键词1,关键词2 -m 1 -t 1
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
### 审核命令
|
|
57
|
+
|
|
58
|
+
> **命令增强**
|
|
59
|
+
> - 未提供参数时 `gva`/`gvr` 会处理最近一条申请,`all` 可以批量处理。
|
|
60
|
+
> - 如果申请记录缺少 requestId,则无法通过机器人接口处理,会提示管理员请在客户端手动操作。
|
|
61
|
+
> - 输出结果会智能展示用户名和ID,避免出现 "12345(12345)" 这样的重复显示。
|
|
62
|
+
|
|
57
63
|
```
|
|
58
64
|
# 同意申请(处理最近一个)
|
|
59
65
|
group-verify.approve
|
|
@@ -103,6 +109,10 @@ group-verify.stats total
|
|
|
103
109
|
- 方式2(按比例):需要达到的百分比(1-100)
|
|
104
110
|
|
|
105
111
|
### 提醒消息配置
|
|
112
|
+
|
|
113
|
+
提醒消息里的 `{threshold}` 在“按比例审核”模式下会显示为需要匹配的关键词数量
|
|
114
|
+
(而非百分比),例如三条关键词、阈值60%时显示为“1/2”。
|
|
115
|
+
|
|
106
116
|
- `-msg "消息内容"` - 设置自定义提醒消息
|
|
107
117
|
- 消息内部可包含逗号:只要所有逗号前后都没有空格,它们将被视为同一消息内容。
|
|
108
118
|
- 若逗号之后仍需写关键词,请使用引号包裹整个消息或在逗号后加空格以分隔。
|
|
@@ -144,10 +154,12 @@ group-verify.stats total
|
|
|
144
154
|
## 📊 统计功能
|
|
145
155
|
|
|
146
156
|
插件会自动记录以下统计信息:
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
157
|
+
- **自动批准**:通过关键词验证自动入群的用户数(包括自动审批与手动 gva 同意)
|
|
158
|
+
- **手动批准**:通过 gva 指令手动同意的用户数
|
|
159
|
+
- **拒绝**:被拒绝的申请数(包括自动拒绝与 gvr 指令)
|
|
160
|
+
- **总入群人数**:无论通过哪种方式,只要检测到成员加入即增加
|
|
161
|
+
|
|
162
|
+
> ⚠️ 注意:绕过机器人手动在客户端同意/拒绝的操作不会计入自动批准/拒绝统计,但仍会反映在总入群人数中。
|
|
151
163
|
### ⚠️ OneBot/QQ 适配器注意
|
|
152
164
|
默认情况下插件会使用 `ctx.broadcast` 发送提醒消息,
|
|
153
165
|
为了兼容 OneBot、QQ 等需要同时指定频道和群号的协议,
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface GroupVerificationStats {
|
|
|
36
36
|
autoApproved: number
|
|
37
37
|
manuallyApproved: number
|
|
38
38
|
rejected: number
|
|
39
|
+
// 新增:总入群人数(不论方式,只要检测到成员加入则增加)
|
|
40
|
+
totalJoined: number
|
|
39
41
|
lastUpdated: string | Date
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -306,16 +308,18 @@ export async function syncTotalStats(ctx: Context) {
|
|
|
306
308
|
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0)
|
|
307
309
|
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0)
|
|
308
310
|
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0)
|
|
311
|
+
const totalJoined = allStats.reduce((sum, stat) => sum + (stat.totalJoined || 0), 0)
|
|
309
312
|
|
|
310
313
|
// 更新总计行
|
|
311
314
|
await ctx.database.set('group_verification_stats', { groupId: 'TOTAL' }, {
|
|
312
315
|
autoApproved: totalAutoApproved,
|
|
313
316
|
manuallyApproved: totalManuallyApproved,
|
|
314
317
|
rejected: totalRejected,
|
|
318
|
+
totalJoined,
|
|
315
319
|
lastUpdated: new Date().toISOString()
|
|
316
320
|
})
|
|
317
321
|
|
|
318
|
-
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`)
|
|
322
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`)
|
|
319
323
|
}
|
|
320
324
|
} catch (error) {
|
|
321
325
|
logger.error('同步总计统计时出错:', error)
|
|
@@ -338,6 +342,7 @@ export async function updateStats(ctx: Context, groupId: string, action: 'autoAp
|
|
|
338
342
|
autoApproved: action === 'autoApproved' ? 1 : 0,
|
|
339
343
|
manuallyApproved: action === 'manuallyApproved' ? 1 : 0,
|
|
340
344
|
rejected: action === 'rejected' ? 1 : 0,
|
|
345
|
+
totalJoined: 0,
|
|
341
346
|
lastUpdated: new Date().toISOString()
|
|
342
347
|
})
|
|
343
348
|
}
|
|
@@ -346,6 +351,28 @@ export async function updateStats(ctx: Context, groupId: string, action: 'autoAp
|
|
|
346
351
|
await syncTotalStats(ctx)
|
|
347
352
|
}
|
|
348
353
|
|
|
354
|
+
// 提取成独立函数:增加总入群计数,供事件统一调用
|
|
355
|
+
export async function incrementTotal(ctx: Context, groupId: string) {
|
|
356
|
+
const existingStats = await ctx.database.get('group_verification_stats', { groupId })
|
|
357
|
+
if (existingStats.length > 0) {
|
|
358
|
+
const stats = existingStats[0]
|
|
359
|
+
await ctx.database.set('group_verification_stats', { id: stats.id }, {
|
|
360
|
+
totalJoined: (stats.totalJoined || 0) + 1,
|
|
361
|
+
lastUpdated: new Date().toISOString()
|
|
362
|
+
})
|
|
363
|
+
} else {
|
|
364
|
+
await ctx.database.create('group_verification_stats', {
|
|
365
|
+
groupId,
|
|
366
|
+
autoApproved: 0,
|
|
367
|
+
manuallyApproved: 0,
|
|
368
|
+
rejected: 0,
|
|
369
|
+
totalJoined: 1,
|
|
370
|
+
lastUpdated: new Date().toISOString()
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
await syncTotalStats(ctx)
|
|
374
|
+
}
|
|
375
|
+
|
|
349
376
|
// 权限检查函数(也可用于命令)
|
|
350
377
|
export async function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]> {
|
|
351
378
|
const groupId = targetGroupId || session.guildId
|
|
@@ -599,7 +626,9 @@ export async function verifyApplication(config: GroupVerificationConfig, message
|
|
|
599
626
|
: 100
|
|
600
627
|
const ratio = matchedCount / config.keywords.length
|
|
601
628
|
isValid = ratio >= thresholdPct / 100
|
|
602
|
-
|
|
629
|
+
// 显示阈值为需要匹配的关键词数量,避免 "1/60%" 之类混淆
|
|
630
|
+
const needed = Math.ceil(config.keywords.length * thresholdPct / 100)
|
|
631
|
+
requiredThreshold = `${needed}`
|
|
603
632
|
}
|
|
604
633
|
break
|
|
605
634
|
case 3: // 全部拒绝
|
|
@@ -747,6 +776,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
747
776
|
autoApproved: 'integer',
|
|
748
777
|
manuallyApproved: 'integer',
|
|
749
778
|
rejected: 'integer',
|
|
779
|
+
totalJoined: 'integer',
|
|
750
780
|
// store as string (ISO timestamp) to preserve full date+time;
|
|
751
781
|
// Koishi `date` type truncates to day which leads to 00:00:00.
|
|
752
782
|
lastUpdated: 'string'
|
|
@@ -784,6 +814,9 @@ export function apply(ctx: Context, config: Config) {
|
|
|
784
814
|
const groupId = session.guildId
|
|
785
815
|
const userId = session.userId
|
|
786
816
|
|
|
817
|
+
// 无论什么情况只要检测到加入就累加总入群
|
|
818
|
+
await incrementTotal(ctx, groupId)
|
|
819
|
+
|
|
787
820
|
// 先检查 autoQueue
|
|
788
821
|
const set = autoQueue.get(groupId)
|
|
789
822
|
if (set && set.has(userId)) {
|
|
@@ -1318,36 +1351,41 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1318
1351
|
}
|
|
1319
1352
|
|
|
1320
1353
|
let approvedCount = 0
|
|
1354
|
+
let skippedCount = 0
|
|
1321
1355
|
for (const request of pendingRequests) {
|
|
1322
|
-
|
|
1323
|
-
|
|
1356
|
+
if (request.requestId) {
|
|
1357
|
+
try {
|
|
1324
1358
|
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1359
|
+
approvedCount++
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
1325
1362
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
approvedCount++
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
1363
|
+
} else {
|
|
1364
|
+
skippedCount++
|
|
1331
1365
|
}
|
|
1366
|
+
// 不论是否有 requestId,都清除记录,避免无限积累
|
|
1367
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1332
1368
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1369
|
+
let msg = `已处理 ${approvedCount} 个加群申请`
|
|
1370
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1371
|
+
return msg
|
|
1335
1372
|
} else {
|
|
1336
1373
|
// 处理最近的一个申请(按时间降序)
|
|
1337
|
-
let pending: any[] = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName', 'applyTime'])
|
|
1374
|
+
let pending: any[] = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName', 'applyTime', 'requestId'])
|
|
1338
1375
|
if (pending.length === 0) {
|
|
1339
1376
|
return '当前无待审核的加群申请'
|
|
1340
1377
|
}
|
|
1341
1378
|
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)))
|
|
1342
1379
|
const request: any = pending[0]
|
|
1380
|
+
if (!request.requestId) {
|
|
1381
|
+
return `用户 ${request.userId} 的申请缺少 requestId,无法自动同意`;
|
|
1382
|
+
}
|
|
1343
1383
|
try {
|
|
1344
|
-
|
|
1345
|
-
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1346
|
-
}
|
|
1384
|
+
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1347
1385
|
// 清除该用户的所有待审核记录
|
|
1348
1386
|
await ctx.database.remove('group_verification_pending', { groupId, userId: request.userId })
|
|
1349
|
-
|
|
1350
|
-
return `已同意用户 ${
|
|
1387
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1388
|
+
return `已同意用户 ${displayName} 的加群申请`
|
|
1351
1389
|
} catch (error) {
|
|
1352
1390
|
return `处理申请时出错: ${error.message}`
|
|
1353
1391
|
}
|
|
@@ -1365,14 +1403,15 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1365
1403
|
}
|
|
1366
1404
|
|
|
1367
1405
|
const request = pendingRequests[0]
|
|
1406
|
+
if (!request.requestId) {
|
|
1407
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动同意`
|
|
1408
|
+
}
|
|
1368
1409
|
try {
|
|
1369
|
-
|
|
1370
|
-
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1371
|
-
}
|
|
1410
|
+
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1372
1411
|
// 删除该用户的所有记录
|
|
1373
1412
|
await ctx.database.remove('group_verification_pending', { groupId, userId })
|
|
1374
|
-
|
|
1375
|
-
return `已同意用户 ${
|
|
1413
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1414
|
+
return `已同意用户 ${displayName} 的加群申请`
|
|
1376
1415
|
} catch (error) {
|
|
1377
1416
|
return `处理申请时出错: ${error.message}`
|
|
1378
1417
|
}
|
|
@@ -1408,36 +1447,42 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1408
1447
|
}
|
|
1409
1448
|
|
|
1410
1449
|
let rejectedCount = 0
|
|
1450
|
+
let skippedCount = 0
|
|
1411
1451
|
for (const request of pendingRequests) {
|
|
1412
|
-
|
|
1413
|
-
|
|
1452
|
+
if (request.requestId) {
|
|
1453
|
+
try {
|
|
1414
1454
|
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1455
|
+
rejectedCount++
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
1415
1458
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
rejectedCount++
|
|
1419
|
-
} catch (error) {
|
|
1420
|
-
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
1459
|
+
} else {
|
|
1460
|
+
skippedCount++
|
|
1421
1461
|
}
|
|
1462
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1463
|
+
await updateStats(ctx, groupId, 'rejected')
|
|
1422
1464
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1465
|
+
let msg = `已拒绝 ${rejectedCount} 个加群申请`
|
|
1466
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1467
|
+
return msg
|
|
1425
1468
|
} else {
|
|
1426
1469
|
// 拒绝最近的一个申请(按 applyTime 降序)
|
|
1427
|
-
let pending: any[] = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName', 'applyTime'])
|
|
1470
|
+
let pending: any[] = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName', 'applyTime', 'requestId'])
|
|
1428
1471
|
if (pending.length === 0) {
|
|
1429
1472
|
return '当前无待审核的加群申请'
|
|
1430
1473
|
}
|
|
1431
1474
|
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)))
|
|
1432
1475
|
const request: any = pending[0]
|
|
1476
|
+
if (!request.requestId) {
|
|
1477
|
+
return `用户 ${request.userId} 的申请缺少 requestId,无法自动拒绝`;
|
|
1478
|
+
}
|
|
1433
1479
|
try {
|
|
1434
|
-
|
|
1435
|
-
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1436
|
-
}
|
|
1480
|
+
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1437
1481
|
// 删除该用户的所有记录
|
|
1438
1482
|
await ctx.database.remove('group_verification_pending', { groupId, userId: request.userId })
|
|
1439
1483
|
await updateStats(ctx, groupId, 'rejected')
|
|
1440
|
-
|
|
1484
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1485
|
+
return `已拒绝用户 ${displayName} 的加群申请`
|
|
1441
1486
|
} catch (error) {
|
|
1442
1487
|
return `处理申请时出错: ${error.message}`
|
|
1443
1488
|
}
|
|
@@ -1455,14 +1500,16 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1455
1500
|
}
|
|
1456
1501
|
|
|
1457
1502
|
const request = pendingRequests[0]
|
|
1503
|
+
if (!request.requestId) {
|
|
1504
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动拒绝`
|
|
1505
|
+
}
|
|
1458
1506
|
try {
|
|
1459
|
-
|
|
1460
|
-
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1461
|
-
}
|
|
1507
|
+
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1462
1508
|
// 删除该用户的所有记录
|
|
1463
1509
|
await ctx.database.remove('group_verification_pending', { groupId, userId })
|
|
1464
1510
|
await updateStats(ctx, groupId, 'rejected')
|
|
1465
|
-
|
|
1511
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1512
|
+
return `已拒绝用户 ${displayName} 的加群申请`
|
|
1466
1513
|
} catch (error) {
|
|
1467
1514
|
return `处理申请时出错: ${error.message}`
|
|
1468
1515
|
}
|
|
@@ -1637,6 +1684,8 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1637
1684
|
for (const st of stats) {
|
|
1638
1685
|
const updates: any = {}
|
|
1639
1686
|
if (st.lastUpdated instanceof Date) updates.lastUpdated = st.lastUpdated.toISOString()
|
|
1687
|
+
// add missing totalJoined column default 0
|
|
1688
|
+
if (st.totalJoined === undefined) updates.totalJoined = 0
|
|
1640
1689
|
if (Object.keys(updates).length) {
|
|
1641
1690
|
await ctx.database.set('group_verification_stats', { id: st.id }, updates)
|
|
1642
1691
|
}
|