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 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
- requiredThreshold = `${thresholdPct}%`;
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
- try {
983
- if (request2.requestId) {
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
- await ctx.database.remove("group_verification_pending", { id: request2.id });
987
- await updateStats(ctx, groupId, "manuallyApproved");
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
- return `已处理 ${approvedCount} 个加群申请`;
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
- if (request2.requestId) {
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
- await updateStats(ctx, groupId, "manuallyApproved");
1007
- return `已同意用户 ${request2.userName}(${request2.userId}) 的加群申请`;
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
- if (request.requestId) {
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
- await updateStats(ctx, groupId, "manuallyApproved");
1027
- return `已同意用户 ${request.userName}(${userId}) 的加群申请`;
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
- try {
1058
- if (request2.requestId) {
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
- await ctx.database.remove("group_verification_pending", { id: request2.id });
1062
- await updateStats(ctx, groupId, "rejected");
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
- return `已拒绝 ${rejectedCount} 个加群申请`;
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
- if (request2.requestId) {
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
- return `已拒绝用户 ${request2.userName}(${request2.userId}) 的加群申请`;
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
- if (request.requestId) {
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
- return `已拒绝用户 ${request.userName}(${userId}) 的加群申请`;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-group-verification",
3
3
  "description": "[WIP] Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能(开发中)",
4
- "version": "1.0.27",
4
+ "version": "1.0.28",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
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
- requiredThreshold = `${thresholdPct}%`
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
- try {
1323
- if (request.requestId) {
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
- await ctx.database.remove('group_verification_pending', { id: request.id })
1327
- await updateStats(ctx, groupId, 'manuallyApproved')
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
- return `已处理 ${approvedCount} 个加群申请`
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
- if (request.requestId) {
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
- await updateStats(ctx, groupId, 'manuallyApproved')
1350
- return `已同意用户 ${request.userName}(${request.userId}) 的加群申请`
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
- if (request.requestId) {
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
- await updateStats(ctx, groupId, 'manuallyApproved')
1375
- return `已同意用户 ${request.userName}(${userId}) 的加群申请`
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
- try {
1413
- if (request.requestId) {
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
- await ctx.database.remove('group_verification_pending', { id: request.id })
1417
- await updateStats(ctx, groupId, 'rejected')
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
- return `已拒绝 ${rejectedCount} 个加群申请`
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
- if (request.requestId) {
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
- return `已拒绝用户 ${request.userName}(${request.userId}) 的加群申请`
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
- if (request.requestId) {
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
- return `已拒绝用户 ${request.userName}(${userId}) 的加群申请`
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
  }