koishi-plugin-group-verification 1.0.27 → 1.0.29

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 {
@@ -41,6 +42,10 @@ export interface Config {
41
42
  defaultReminderMessage?: string;
42
43
  enableStrictGroupCheck?: boolean;
43
44
  logLevel?: 'debug' | 'info' | 'warn' | 'error';
45
+ permissionDeniedMessage?: string;
46
+ invalidGroupMessage?: string;
47
+ parameterConflictMessage?: string;
48
+ noKeywordsMessage?: string;
44
49
  }
45
50
  export declare const Config: Schema<Config>;
46
51
  export declare const inject: string[];
@@ -86,12 +91,19 @@ export declare function mergeReminder(existingConfig: any | null, cleanedOptions
86
91
  message?: string;
87
92
  enableMessage?: boolean;
88
93
  disableMessage?: boolean;
89
- }, hasRealMessageParam: boolean, hasRealEnableMessageParam: boolean, hasRealDisableMessageParam: boolean, logger: any): {
94
+ }, hasRealMessageParam: boolean, hasRealEnableMessageParam: boolean, hasRealDisableMessageParam: boolean, logger: any, defaultMessage?: string): {
90
95
  reminderEnabled: boolean;
91
96
  reminderMessage: string;
92
97
  };
93
98
  export declare function syncTotalStats(ctx: Context): Promise<void>;
94
99
  export declare function updateStats(ctx: Context, groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected'): Promise<void>;
100
+ export declare function incrementTotal(ctx: Context, groupId: string): Promise<void>;
101
+ export interface ThresholdResult {
102
+ reviewParameters: number;
103
+ error?: string;
104
+ autoInfo?: 'methodChange' | 'kwChange';
105
+ }
106
+ export declare function resolveThreshold(existingConfig: any | null, keywordList: string[], reviewMethod: 0 | 1 | 2 | 3, thresholdStr?: string, methodChanged?: boolean): ThresholdResult;
95
107
  export declare function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]>;
96
108
  export declare function handleGuildMemberRequestEvent(ctx: Context, session: any): Promise<void>;
97
109
  export declare function __getAutoQueue(): Map<string, Set<string>>;
package/lib/index.js CHANGED
@@ -26,10 +26,12 @@ __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,
32
33
  parseConfigArgs: () => parseConfigArgs,
34
+ resolveThreshold: () => resolveThreshold,
33
35
  syncTotalStats: () => syncTotalStats,
34
36
  tokenize: () => tokenize,
35
37
  updateStats: () => updateStats,
@@ -41,9 +43,13 @@ var import_koishi = require("koishi");
41
43
  var name = "group-verification";
42
44
  var logger = console;
43
45
  var Config = import_koishi.Schema.object({
44
- defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板(使用 \n 表示换行,可包含下方变量)").default("{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}\n使用 gva 同意或 gvr 拒绝申请"),
46
+ defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)").default("{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}\n使用 gva 同意或 gvr 拒绝申请"),
45
47
  enableStrictGroupCheck: import_koishi.Schema.boolean().description("是否启用严格的群号检查(检查群号长度)").default(false),
46
- logLevel: import_koishi.Schema.union(["debug", "info", "warn", "error"]).description("日志级别").default("info")
48
+ logLevel: import_koishi.Schema.union(["debug", "info", "warn", "error"]).description("日志级别").default("info"),
49
+ permissionDeniedMessage: import_koishi.Schema.string().description("权限不足时返回给调用者的提示").default("权限不足:需要群主/管理员权限或koishi三级以上权限"),
50
+ invalidGroupMessage: import_koishi.Schema.string().description("无效群号或机器人未在该群时的提示").default("群号 {group} 格式不合法或机器人不在该群中"),
51
+ parameterConflictMessage: import_koishi.Schema.string().description("参数冲突时提示").default("参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)"),
52
+ noKeywordsMessage: import_koishi.Schema.string().description("未提供关键词且无法从现有配置继承时的提示").default("请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置")
47
53
  }).description("群组验证插件配置");
48
54
  var inject = ["database"];
49
55
  var ESC_QUOTE = "\0";
@@ -174,24 +180,24 @@ function usageString() {
174
180
  使用 \\n 换行`;
175
181
  }
176
182
  __name(usageString, "usageString");
177
- function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger2) {
183
+ function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger2, defaultMessage) {
178
184
  let reminderEnabled = true;
179
- let reminderMessage = "{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}";
185
+ let reminderMessage = defaultMessage || "{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}";
180
186
  if (existingConfig) {
181
187
  reminderEnabled = existingConfig.reminderEnabled;
182
188
  reminderMessage = existingConfig.reminderMessage || reminderMessage;
183
189
  }
184
190
  if (hasRealDisableMessageParam) {
185
191
  reminderEnabled = false;
186
- logger2.info("禁用提醒消息功能 (保留现有内容)");
192
+ logger2.debug("禁用提醒消息功能 (保留现有内容)");
187
193
  } else if (hasRealEnableMessageParam) {
188
194
  reminderEnabled = true;
189
- logger2.info(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
195
+ logger2.debug(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
190
196
  } else if (hasRealMessageParam) {
191
197
  reminderEnabled = true;
192
198
  if (cleanedOptions.message !== void 0) {
193
199
  reminderMessage = cleanedOptions.message.replace(/\\n/g, "\n");
194
- logger2.info(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
200
+ logger2.debug(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
195
201
  }
196
202
  }
197
203
  return { reminderEnabled, reminderMessage };
@@ -207,13 +213,15 @@ async function syncTotalStats(ctx) {
207
213
  const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0);
208
214
  const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0);
209
215
  const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0);
216
+ const totalJoined = allStats.reduce((sum, stat) => sum + (stat.totalJoined || 0), 0);
210
217
  await ctx.database.set("group_verification_stats", { groupId: "TOTAL" }, {
211
218
  autoApproved: totalAutoApproved,
212
219
  manuallyApproved: totalManuallyApproved,
213
220
  rejected: totalRejected,
221
+ totalJoined,
214
222
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
215
223
  });
216
- logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`);
224
+ logger.debug(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`);
217
225
  }
218
226
  } catch (error) {
219
227
  logger.error("同步总计统计时出错:", error);
@@ -234,36 +242,100 @@ async function updateStats(ctx, groupId, action) {
234
242
  autoApproved: action === "autoApproved" ? 1 : 0,
235
243
  manuallyApproved: action === "manuallyApproved" ? 1 : 0,
236
244
  rejected: action === "rejected" ? 1 : 0,
245
+ totalJoined: 0,
237
246
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
238
247
  });
239
248
  }
240
249
  await syncTotalStats(ctx);
241
250
  }
242
251
  __name(updateStats, "updateStats");
252
+ async function incrementTotal(ctx, groupId) {
253
+ const existingStats = await ctx.database.get("group_verification_stats", { groupId });
254
+ if (existingStats.length > 0) {
255
+ const stats = existingStats[0];
256
+ await ctx.database.set("group_verification_stats", { id: stats.id }, {
257
+ totalJoined: (stats.totalJoined || 0) + 1,
258
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
259
+ });
260
+ } else {
261
+ await ctx.database.create("group_verification_stats", {
262
+ groupId,
263
+ autoApproved: 0,
264
+ manuallyApproved: 0,
265
+ rejected: 0,
266
+ totalJoined: 1,
267
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
268
+ });
269
+ }
270
+ await syncTotalStats(ctx);
271
+ }
272
+ __name(incrementTotal, "incrementTotal");
273
+ function resolveThreshold(existingConfig, keywordList, reviewMethod, thresholdStr, methodChanged = false) {
274
+ let reviewParameters = 0;
275
+ if (existingConfig) {
276
+ reviewParameters = existingConfig.reviewParameters || 0;
277
+ if (isNaN(reviewParameters)) reviewParameters = 0;
278
+ }
279
+ if (thresholdStr !== void 0) {
280
+ const thresholdNum = parseInt(thresholdStr);
281
+ if (isNaN(thresholdNum)) {
282
+ return { reviewParameters, error: "阈值参数必须为数字" };
283
+ }
284
+ if (reviewMethod === 1) {
285
+ if (thresholdNum < 0 || thresholdNum > keywordList.length) {
286
+ return { reviewParameters, error: `数量阈值必须在0-${keywordList.length}之间(0表示全部同意)` };
287
+ }
288
+ } else if (reviewMethod === 2) {
289
+ if (thresholdNum < 0 || thresholdNum > 100) {
290
+ return { reviewParameters, error: "比例阈值必须在0-100之间(0表示全部同意)" };
291
+ }
292
+ }
293
+ return { reviewParameters: thresholdNum };
294
+ }
295
+ if (methodChanged) {
296
+ if (reviewMethod === 1) {
297
+ reviewParameters = keywordList.length;
298
+ return { reviewParameters, autoInfo: "methodChange" };
299
+ }
300
+ if (reviewMethod === 2) {
301
+ reviewParameters = 100;
302
+ return { reviewParameters, autoInfo: "methodChange" };
303
+ }
304
+ }
305
+ if (existingConfig && reviewMethod === 1 && reviewParameters !== 0) {
306
+ const oldKeywordCount = existingConfig.keywords.length;
307
+ const newKeywordCount = keywordList.length;
308
+ if (oldKeywordCount !== newKeywordCount) {
309
+ reviewParameters = newKeywordCount;
310
+ return { reviewParameters, autoInfo: "kwChange" };
311
+ }
312
+ }
313
+ return { reviewParameters };
314
+ }
315
+ __name(resolveThreshold, "resolveThreshold");
243
316
  async function checkPermission(session, targetGroupId) {
244
317
  const groupId = targetGroupId || session.guildId;
245
318
  if (!groupId) {
246
319
  return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
247
320
  }
248
- logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
321
+ logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
249
322
  const koishiAuthority = session.author?.authority || session.user?.authority;
250
- logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
323
+ logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
251
324
  if (!session.author) {
252
- logger.info(`权限检查 - session中可能的权限字段:`, {
325
+ logger.debug(`权限检查 - session中可能的权限字段:`, {
253
326
  authority: session.authority,
254
327
  permission: session.permission,
255
328
  role: session.role
256
329
  });
257
330
  } else {
258
- logger.info(`权限检查 - author对象中的字段:`, {
259
- authority: session.author.authority,
331
+ logger.debug(`权限检查 - author对象中的字段:`, {
260
332
  permission: session.author.permission,
261
333
  role: session.author.role,
262
334
  permissions: session.author.permissions
263
335
  });
264
336
  }
265
337
  if (session.user) {
266
- logger.info(`权限检查 - user对象中的权限信息:`, {
338
+ logger.debug(`权限检查 - user对象中的权限信息:`, {
267
339
  authority: session.user.authority,
268
340
  permission: session.user.permission,
269
341
  role: session.user.role
@@ -321,12 +393,12 @@ async function handleGuildMemberRequestEvent(ctx, session) {
321
393
  return;
322
394
  }
323
395
  const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
324
- logger.info(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
396
+ logger.debug(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
325
397
  if (isValid) {
326
398
  if (requestId) {
327
399
  try {
328
400
  await session.bot.handleGuildMemberRequest(requestId, true);
329
- logger.info(`自动同意 requestId=${requestId}`);
401
+ logger.debug(`自动同意 requestId=${requestId}`);
330
402
  if (!autoQueue.has(guildId)) autoQueue.set(guildId, /* @__PURE__ */ new Set());
331
403
  autoQueue.get(guildId).add(userId);
332
404
  } catch (e) {
@@ -454,7 +526,8 @@ async function verifyApplication(config, message, session) {
454
526
  const thresholdPct = config.reviewParameters !== void 0 && config.reviewParameters !== null ? config.reviewParameters : 100;
455
527
  const ratio = matchedCount / config.keywords.length;
456
528
  isValid = ratio >= thresholdPct / 100;
457
- requiredThreshold = `${thresholdPct}%`;
529
+ const needed = Math.ceil(config.keywords.length * thresholdPct / 100);
530
+ requiredThreshold = `${needed}`;
458
531
  }
459
532
  break;
460
533
  case 3:
@@ -465,7 +538,7 @@ async function verifyApplication(config, message, session) {
465
538
  isValid = false;
466
539
  requiredThreshold = "null";
467
540
  }
468
- logger.info(`verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
541
+ logger.debug(`verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
469
542
  return { isValid, matchedCount, requiredThreshold };
470
543
  }
471
544
  __name(verifyApplication, "verifyApplication");
@@ -478,7 +551,7 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
478
551
  }
479
552
  const username = session.username || "未知用户";
480
553
  const message = session.content || "";
481
- logger.info(`处理失败验证 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold}`);
554
+ logger.debug(`处理失败验证 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold}`);
482
555
  if (matchedCount === void 0 || requiredThreshold === void 0) {
483
556
  const result = await verifyApplication(config, message, session);
484
557
  matchedCount = result.matchedCount;
@@ -501,7 +574,7 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
501
574
  applyTime: (/* @__PURE__ */ new Date()).toISOString()
502
575
  });
503
576
  if (!config.reminderEnabled || !config.reminderMessage || config.reminderMessage === "") {
504
- logger.info(`群 ${guildId} 的提醒消息已被禁用,跳过发送`);
577
+ logger.debug(`群 ${guildId} 的提醒消息已被禁用,跳过发送`);
505
578
  return;
506
579
  }
507
580
  let reminderMsg = config.reminderMessage;
@@ -549,6 +622,7 @@ function apply(ctx, config) {
549
622
  autoInc: true
550
623
  });
551
624
  logger = ctx.logger("group-verification");
625
+ if (config.logLevel) logger.level = config.logLevel;
552
626
  logger.info("群组验证插件已启动");
553
627
  logger.info(`默认提醒消息: ${config.defaultReminderMessage}`);
554
628
  logger.info(`严格群号检查: ${config.enableStrictGroupCheck ? "启用" : "禁用"}`);
@@ -559,6 +633,7 @@ function apply(ctx, config) {
559
633
  autoApproved: "integer",
560
634
  manuallyApproved: "integer",
561
635
  rejected: "integer",
636
+ totalJoined: "integer",
562
637
  // store as string (ISO timestamp) to preserve full date+time;
563
638
  // Koishi `date` type truncates to day which leads to 00:00:00.
564
639
  lastUpdated: "string"
@@ -587,6 +662,7 @@ function apply(ctx, config) {
587
662
  ctx.on("guild-member-added", async (session) => {
588
663
  const groupId = session.guildId;
589
664
  const userId = session.userId;
665
+ await incrementTotal(ctx, groupId);
590
666
  const set = autoQueue.get(groupId);
591
667
  if (set && set.has(userId)) {
592
668
  await updateStats(ctx, groupId, "autoApproved");
@@ -612,19 +688,19 @@ function apply(ctx, config) {
612
688
  async function checkPermission2(session, targetGroupId) {
613
689
  const groupId = targetGroupId || session.guildId;
614
690
  if (!groupId) {
615
- return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
691
+ return [false, config.invalidGroupMessage || "请在群聊中使用此命令或使用 -i 参数指定群号"];
616
692
  }
617
- logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
693
+ logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
618
694
  const koishiAuthority = session.author?.authority || session.user?.authority;
619
- logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
695
+ logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
620
696
  if (!session.author) {
621
- logger.info(`权限检查 - session中可能的权限字段:`, {
697
+ logger.debug(`权限检查 - session中可能的权限字段:`, {
622
698
  authority: session.authority,
623
699
  permission: session.permission,
624
700
  role: session.role
625
701
  });
626
702
  } else {
627
- logger.info(`权限检查 - author对象中的字段:`, {
703
+ logger.debug(`权限检查 - author对象中的字段:`, {
628
704
  authority: session.author.authority,
629
705
  permission: session.author.permission,
630
706
  role: session.author.role,
@@ -632,19 +708,19 @@ function apply(ctx, config) {
632
708
  });
633
709
  }
634
710
  if (session.user) {
635
- logger.info(`权限检查 - user对象中的权限信息:`, {
711
+ logger.debug(`权限检查 - user对象中的权限信息:`, {
636
712
  authority: session.user.authority,
637
713
  permission: session.user.permission,
638
714
  role: session.user.role
639
715
  });
640
716
  }
641
717
  if (koishiAuthority && koishiAuthority >= 3) {
642
- logger.info(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
718
+ logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
643
719
  return [true];
644
720
  }
645
721
  try {
646
722
  const member = await session.bot.getGuildMember(groupId, session.userId);
647
- logger.info(`权限检查 - 获取到成员信息:`, {
723
+ logger.debug(`权限检查 - 获取到成员信息:`, {
648
724
  roles: member?.roles,
649
725
  permissions: member?.permissions
650
726
  });
@@ -654,7 +730,7 @@ function apply(ctx, config) {
654
730
  return [true];
655
731
  }
656
732
  if (member.roles?.includes("admin") || member.permissions?.includes("ADMINISTRATOR")) {
657
- logger.info(`权限检查 - 用户是管理员`);
733
+ logger.debug(`权限检查 - 用户是管理员`);
658
734
  return [true];
659
735
  }
660
736
  }
@@ -662,9 +738,9 @@ function apply(ctx, config) {
662
738
  logger.warn(`权限检查 - 获取群成员信息失败:`, error);
663
739
  return [false, `无法获取群 ${groupId} 的成员信息,请确认机器人已在该群中`];
664
740
  }
665
- logger.info(`权限检查 - 权限不足`);
741
+ logger.debug(`权限检查 - 权限不足`);
666
742
  const debugInfo = `调试信息 - 用户ID:${session.userId}, 群号:${groupId}, 权限等级:${koishiAuthority || "未知"}`;
667
- return [false, `权限不足:需要群主/管理员权限或koishi三级以上权限
743
+ return [false, (config.permissionDeniedMessage || "权限不足:需要群主/管理员权限或koishi三级以上权限") + `
668
744
  ${debugInfo}`];
669
745
  }
670
746
  __name(checkPermission2, "checkPermission");
@@ -701,7 +777,7 @@ ${debugInfo}`];
701
777
  };
702
778
  logger.info(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}`);
703
779
  if ((cleanedOptions.query || cleanedOptions.remove) && (parsedKeywords.length > 0 || cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || cleanedOptions.message !== void 0 || cleanedOptions.enableMessage || cleanedOptions.disableMessage)) {
704
- return "参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)";
780
+ return config.parameterConflictMessage || "参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)";
705
781
  }
706
782
  const hasRealMessageParam = cleanedOptions.message !== void 0;
707
783
  const hasRealEnableMessageParam = cleanedOptions.enableMessage === true;
@@ -813,7 +889,7 @@ ${debugInfo}`];
813
889
  }
814
890
  if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
815
891
  if (!existingConfig) {
816
- return "请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置";
892
+ return config.noKeywordsMessage || "请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置";
817
893
  }
818
894
  keywordList = existingConfig.keywords;
819
895
  }
@@ -823,7 +899,8 @@ ${debugInfo}`];
823
899
  hasRealMessageParam,
824
900
  hasRealEnableMessageParam,
825
901
  hasRealDisableMessageParam,
826
- logger
902
+ logger,
903
+ config.defaultReminderMessage
827
904
  );
828
905
  let reviewMethod = 0;
829
906
  let reviewParameters = 0;
@@ -841,44 +918,45 @@ ${debugInfo}`];
841
918
  if (isNaN(methodNum) || methodNum < 0 || methodNum > 3) {
842
919
  return "审核方式参数错误:0-全部同意, 1-按数量同意, 2-按比例同意, 3-全部拒绝";
843
920
  }
921
+ const oldMethod = reviewMethod;
844
922
  reviewMethod = methodNum;
845
- logger.info(`审核方式明确指定为: ${reviewMethod}`);
923
+ logger.debug(`审核方式明确指定为: ${reviewMethod}`);
924
+ var methodChanged = oldMethod !== reviewMethod;
846
925
  } else {
847
- logger.info(`未指定审核方式,保持原有值: ${reviewMethod}`);
926
+ logger.debug(`未指定审核方式,保持原有值: ${reviewMethod}`);
927
+ var methodChanged = false;
848
928
  }
849
- if (cleanedOptions.threshold !== void 0) {
850
- const thresholdNum = parseInt(cleanedOptions.threshold);
851
- if (isNaN(thresholdNum)) {
852
- return "阈值参数必须为数字";
853
- }
929
+ const thresholdResult = resolveThreshold(
930
+ existingConfig,
931
+ keywordList,
932
+ reviewMethod,
933
+ cleanedOptions.threshold,
934
+ methodChanged
935
+ );
936
+ if (thresholdResult.error) {
937
+ return thresholdResult.error;
938
+ }
939
+ reviewParameters = thresholdResult.reviewParameters;
940
+ if (existingConfig && reviewParameters !== existingConfig.reviewParameters) {
941
+ await ctx.database.set("group_verification_config", { id: existingConfig.id }, {
942
+ reviewParameters,
943
+ updatedBy: session.username || session.userId,
944
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
945
+ });
946
+ logger.debug(`自动调整并更新数据库阈值为: ${reviewParameters}`);
947
+ }
948
+ let autoAdjustNote = "";
949
+ if (thresholdResult.autoInfo === "methodChange") {
854
950
  if (reviewMethod === 1) {
855
- if (thresholdNum < 0 || thresholdNum > keywordList.length) {
856
- return `数量阈值必须在0-${keywordList.length}之间(0表示全部同意)`;
857
- }
858
- reviewParameters = thresholdNum;
859
- logger.info(`明确指定阈值: ${thresholdNum}`);
951
+ autoAdjustNote = `⚠️ 审核方式由 ${existingConfig?.reviewMethod} 改为 ${reviewMethod},阈值自动设为 ${reviewParameters}
952
+ `;
860
953
  } else if (reviewMethod === 2) {
861
- if (thresholdNum < 0 || thresholdNum > 100) {
862
- return "比例阈值必须在0-100之间(0表示全部同意)";
863
- }
864
- reviewParameters = thresholdNum;
865
- logger.info(`明确指定阈值: ${thresholdNum}%`);
866
- }
867
- } else if (existingConfig && reviewMethod === 1 && reviewParameters !== 0) {
868
- const oldKeywordCount = existingConfig.keywords.length;
869
- const newKeywordCount = keywordList.length;
870
- if (oldKeywordCount !== newKeywordCount) {
871
- reviewParameters = newKeywordCount;
872
- logger.info(`关键词数量从${oldKeywordCount}变为${newKeywordCount},自动调整阈值`);
873
- await ctx.database.set("group_verification_config", { id: existingConfig.id }, {
874
- reviewParameters,
875
- updatedBy: session.username || session.userId,
876
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
877
- });
878
- logger.info(`已更新数据库阈值为: ${reviewParameters}`);
879
- } else {
880
- logger.info(`关键词数量未变化(${newKeywordCount}),保持原阈值: ${reviewParameters}`);
954
+ autoAdjustNote = `⚠️ 审核方式由 ${existingConfig?.reviewMethod} 改为 ${reviewMethod},阈值自动设为 ${reviewParameters}%
955
+ `;
881
956
  }
957
+ } else if (thresholdResult.autoInfo === "kwChange") {
958
+ autoAdjustNote = `⚠️ 关键词数量从${existingConfig?.keywords.length}变为${keywordList.length},阈值已自动调整为${reviewParameters}
959
+ `;
882
960
  }
883
961
  const encodedKeywords = keywordList.map((keyword) => {
884
962
  return keyword.replace(/,/g, "[[COMMA]]");
@@ -912,6 +990,7 @@ ${debugInfo}`];
912
990
  const decodedKeywords = keywordList;
913
991
  let feedbackMessage = `群 ${targetGroupId} 配置已更新:
914
992
  `;
993
+ if (autoAdjustNote) feedbackMessage += autoAdjustNote;
915
994
  const displayKeywords = keywordList.map((k) => k.replace(/\[\[COMMA\]\]/g, ","));
916
995
  feedbackMessage += `关键词: ${displayKeywords.map((k) => `"${k}"`).join(", ")}
917
996
  `;
@@ -927,10 +1006,6 @@ ${debugInfo}`];
927
1006
  `;
928
1007
  if (reminderMessage && reminderEnabled) {
929
1008
  feedbackMessage += `提醒消息: ${reminderMessage.substring(0, 30)}${reminderMessage.length > 30 ? "..." : ""}
930
- `;
931
- }
932
- if (existingConfig && reviewMethod === 1 && keywordList.length !== existingConfig.keywords.length) {
933
- feedbackMessage += `⚠️ 关键词数量从${existingConfig.keywords.length}变为${keywordList.length},阈值已自动调整为${keywordList.length}
934
1009
  `;
935
1010
  }
936
1011
  logger.info(`准备存储到数据库的关键词: ${JSON.stringify(encodedKeywords)}`);
@@ -978,33 +1053,38 @@ ${debugInfo}`];
978
1053
  return "当前无待审核的加群申请";
979
1054
  }
980
1055
  let approvedCount = 0;
1056
+ let skippedCount = 0;
981
1057
  for (const request2 of pendingRequests2) {
982
- try {
983
- if (request2.requestId) {
1058
+ if (request2.requestId) {
1059
+ try {
984
1060
  await session.bot.handleGuildMemberRequest(request2.requestId, true);
1061
+ approvedCount++;
1062
+ } catch (error) {
1063
+ logger.warn(`处理申请 ${request2.id} 时出错:`, error);
985
1064
  }
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);
1065
+ } else {
1066
+ skippedCount++;
991
1067
  }
1068
+ await ctx.database.remove("group_verification_pending", { id: request2.id });
992
1069
  }
993
- return `已处理 ${approvedCount} 个加群申请`;
1070
+ let msg = `已处理 ${approvedCount} 个加群申请`;
1071
+ if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
1072
+ return msg;
994
1073
  } else {
995
- let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime"]);
1074
+ let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime", "requestId"]);
996
1075
  if (pending.length === 0) {
997
1076
  return "当前无待审核的加群申请";
998
1077
  }
999
1078
  pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)));
1000
1079
  const request2 = pending[0];
1080
+ if (!request2.requestId) {
1081
+ return `用户 ${request2.userId} 的申请缺少 requestId,无法自动同意`;
1082
+ }
1001
1083
  try {
1002
- if (request2.requestId) {
1003
- await session.bot.handleGuildMemberRequest(request2.requestId, true);
1004
- }
1084
+ await session.bot.handleGuildMemberRequest(request2.requestId, true);
1005
1085
  await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
1006
- await updateStats(ctx, groupId, "manuallyApproved");
1007
- return `已同意用户 ${request2.userName}(${request2.userId}) 的加群申请`;
1086
+ const displayName = request2.userName && request2.userName !== request2.userId ? `${request2.userName}(${request2.userId})` : request2.userId;
1087
+ return `已同意用户 ${displayName} 的加群申请`;
1008
1088
  } catch (error) {
1009
1089
  return `处理申请时出错: ${error.message}`;
1010
1090
  }
@@ -1018,13 +1098,14 @@ ${debugInfo}`];
1018
1098
  return `未找到用户 ${userId} 的待审核申请`;
1019
1099
  }
1020
1100
  const request = pendingRequests[0];
1101
+ if (!request.requestId) {
1102
+ return `用户 ${userId} 的申请缺少 requestId,无法自动同意`;
1103
+ }
1021
1104
  try {
1022
- if (request.requestId) {
1023
- await session.bot.handleGuildMemberRequest(request.requestId, true);
1024
- }
1105
+ await session.bot.handleGuildMemberRequest(request.requestId, true);
1025
1106
  await ctx.database.remove("group_verification_pending", { groupId, userId });
1026
- await updateStats(ctx, groupId, "manuallyApproved");
1027
- return `已同意用户 ${request.userName}(${userId}) 的加群申请`;
1107
+ const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId;
1108
+ return `已同意用户 ${displayName} 的加群申请`;
1028
1109
  } catch (error) {
1029
1110
  return `处理申请时出错: ${error.message}`;
1030
1111
  }
@@ -1053,33 +1134,40 @@ ${debugInfo}`];
1053
1134
  return "当前无待审核的加群申请";
1054
1135
  }
1055
1136
  let rejectedCount = 0;
1137
+ let skippedCount = 0;
1056
1138
  for (const request2 of pendingRequests2) {
1057
- try {
1058
- if (request2.requestId) {
1139
+ if (request2.requestId) {
1140
+ try {
1059
1141
  await session.bot.handleGuildMemberRequest(request2.requestId, false);
1142
+ rejectedCount++;
1143
+ } catch (error) {
1144
+ logger.warn(`处理申请 ${request2.id} 时出错:`, error);
1060
1145
  }
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);
1146
+ } else {
1147
+ skippedCount++;
1066
1148
  }
1149
+ await ctx.database.remove("group_verification_pending", { id: request2.id });
1150
+ await updateStats(ctx, groupId, "rejected");
1067
1151
  }
1068
- return `已拒绝 ${rejectedCount} 个加群申请`;
1152
+ let msg = `已拒绝 ${rejectedCount} 个加群申请`;
1153
+ if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
1154
+ return msg;
1069
1155
  } else {
1070
- let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime"]);
1156
+ let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime", "requestId"]);
1071
1157
  if (pending.length === 0) {
1072
1158
  return "当前无待审核的加群申请";
1073
1159
  }
1074
1160
  pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)));
1075
1161
  const request2 = pending[0];
1162
+ if (!request2.requestId) {
1163
+ return `用户 ${request2.userId} 的申请缺少 requestId,无法自动拒绝`;
1164
+ }
1076
1165
  try {
1077
- if (request2.requestId) {
1078
- await session.bot.handleGuildMemberRequest(request2.requestId, false);
1079
- }
1166
+ await session.bot.handleGuildMemberRequest(request2.requestId, false);
1080
1167
  await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
1081
1168
  await updateStats(ctx, groupId, "rejected");
1082
- return `已拒绝用户 ${request2.userName}(${request2.userId}) 的加群申请`;
1169
+ const displayName = request2.userName && request2.userName !== request2.userId ? `${request2.userName}(${request2.userId})` : request2.userId;
1170
+ return `已拒绝用户 ${displayName} 的加群申请`;
1083
1171
  } catch (error) {
1084
1172
  return `处理申请时出错: ${error.message}`;
1085
1173
  }
@@ -1093,13 +1181,15 @@ ${debugInfo}`];
1093
1181
  return `未找到用户 ${userId} 的待审核申请`;
1094
1182
  }
1095
1183
  const request = pendingRequests[0];
1184
+ if (!request.requestId) {
1185
+ return `用户 ${userId} 的申请缺少 requestId,无法自动拒绝`;
1186
+ }
1096
1187
  try {
1097
- if (request.requestId) {
1098
- await session.bot.handleGuildMemberRequest(request.requestId, false);
1099
- }
1188
+ await session.bot.handleGuildMemberRequest(request.requestId, false);
1100
1189
  await ctx.database.remove("group_verification_pending", { groupId, userId });
1101
1190
  await updateStats(ctx, groupId, "rejected");
1102
- return `已拒绝用户 ${request.userName}(${userId}) 的加群申请`;
1191
+ const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId;
1192
+ return `已拒绝用户 ${displayName} 的加群申请`;
1103
1193
  } catch (error) {
1104
1194
  return `处理申请时出错: ${error.message}`;
1105
1195
  }
@@ -1244,6 +1334,7 @@ ${debugInfo}`];
1244
1334
  for (const st of stats) {
1245
1335
  const updates = {};
1246
1336
  if (st.lastUpdated instanceof Date) updates.lastUpdated = st.lastUpdated.toISOString();
1337
+ if (st.totalJoined === void 0) updates.totalJoined = 0;
1247
1338
  if (Object.keys(updates).length) {
1248
1339
  await ctx.database.set("group_verification_stats", { id: st.id }, updates);
1249
1340
  }
@@ -1314,10 +1405,12 @@ __name(apply, "apply");
1314
1405
  checkPermission,
1315
1406
  handleFailedVerification,
1316
1407
  handleGuildMemberRequestEvent,
1408
+ incrementTotal,
1317
1409
  inject,
1318
1410
  mergeReminder,
1319
1411
  name,
1320
1412
  parseConfigArgs,
1413
+ resolveThreshold,
1321
1414
  syncTotalStats,
1322
1415
  tokenize,
1323
1416
  updateStats,