koishi-plugin-group-verification 1.0.28 → 1.0.30

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
@@ -42,6 +42,10 @@ export interface Config {
42
42
  defaultReminderMessage?: string;
43
43
  enableStrictGroupCheck?: boolean;
44
44
  logLevel?: 'debug' | 'info' | 'warn' | 'error';
45
+ permissionDeniedMessage?: string;
46
+ invalidGroupMessage?: string;
47
+ parameterConflictMessage?: string;
48
+ noKeywordsMessage?: string;
45
49
  }
46
50
  export declare const Config: Schema<Config>;
47
51
  export declare const inject: string[];
@@ -87,13 +91,19 @@ export declare function mergeReminder(existingConfig: any | null, cleanedOptions
87
91
  message?: string;
88
92
  enableMessage?: boolean;
89
93
  disableMessage?: boolean;
90
- }, hasRealMessageParam: boolean, hasRealEnableMessageParam: boolean, hasRealDisableMessageParam: boolean, logger: any): {
94
+ }, hasRealMessageParam: boolean, hasRealEnableMessageParam: boolean, hasRealDisableMessageParam: boolean, logger: any, defaultMessage?: string): {
91
95
  reminderEnabled: boolean;
92
96
  reminderMessage: string;
93
97
  };
94
98
  export declare function syncTotalStats(ctx: Context): Promise<void>;
95
99
  export declare function updateStats(ctx: Context, groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected'): Promise<void>;
96
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;
97
107
  export declare function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]>;
98
108
  export declare function handleGuildMemberRequestEvent(ctx: Context, session: any): Promise<void>;
99
109
  export declare function __getAutoQueue(): Map<string, Set<string>>;
package/lib/index.js CHANGED
@@ -31,6 +31,7 @@ __export(src_exports, {
31
31
  mergeReminder: () => mergeReminder,
32
32
  name: () => name,
33
33
  parseConfigArgs: () => parseConfigArgs,
34
+ resolveThreshold: () => resolveThreshold,
34
35
  syncTotalStats: () => syncTotalStats,
35
36
  tokenize: () => tokenize,
36
37
  updateStats: () => updateStats,
@@ -42,9 +43,13 @@ var import_koishi = require("koishi");
42
43
  var name = "group-verification";
43
44
  var logger = console;
44
45
  var Config = import_koishi.Schema.object({
45
- 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 拒绝申请"),
46
47
  enableStrictGroupCheck: import_koishi.Schema.boolean().description("是否启用严格的群号检查(检查群号长度)").default(false),
47
- 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 删除配置")
48
53
  }).description("群组验证插件配置");
49
54
  var inject = ["database"];
50
55
  var ESC_QUOTE = "\0";
@@ -175,24 +180,24 @@ function usageString() {
175
180
  使用 \\n 换行`;
176
181
  }
177
182
  __name(usageString, "usageString");
178
- function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger2) {
183
+ function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger2, defaultMessage) {
179
184
  let reminderEnabled = true;
180
- let reminderMessage = "{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}";
185
+ let reminderMessage = defaultMessage || "{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}";
181
186
  if (existingConfig) {
182
187
  reminderEnabled = existingConfig.reminderEnabled;
183
188
  reminderMessage = existingConfig.reminderMessage || reminderMessage;
184
189
  }
185
190
  if (hasRealDisableMessageParam) {
186
191
  reminderEnabled = false;
187
- logger2.info("禁用提醒消息功能 (保留现有内容)");
192
+ logger2.debug("禁用提醒消息功能 (保留现有内容)");
188
193
  } else if (hasRealEnableMessageParam) {
189
194
  reminderEnabled = true;
190
- logger2.info(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
195
+ logger2.debug(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
191
196
  } else if (hasRealMessageParam) {
192
197
  reminderEnabled = true;
193
198
  if (cleanedOptions.message !== void 0) {
194
199
  reminderMessage = cleanedOptions.message.replace(/\\n/g, "\n");
195
- logger2.info(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
200
+ logger2.debug(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
196
201
  }
197
202
  }
198
203
  return { reminderEnabled, reminderMessage };
@@ -216,7 +221,7 @@ async function syncTotalStats(ctx) {
216
221
  totalJoined,
217
222
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
218
223
  });
219
- logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`);
224
+ logger.debug(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`);
220
225
  }
221
226
  } catch (error) {
222
227
  logger.error("同步总计统计时出错:", error);
@@ -265,30 +270,72 @@ async function incrementTotal(ctx, groupId) {
265
270
  await syncTotalStats(ctx);
266
271
  }
267
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");
268
316
  async function checkPermission(session, targetGroupId) {
269
317
  const groupId = targetGroupId || session.guildId;
270
318
  if (!groupId) {
271
319
  return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
272
320
  }
273
- logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
321
+ logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
274
322
  const koishiAuthority = session.author?.authority || session.user?.authority;
275
- logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
323
+ logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
276
324
  if (!session.author) {
277
- logger.info(`权限检查 - session中可能的权限字段:`, {
325
+ logger.debug(`权限检查 - session中可能的权限字段:`, {
278
326
  authority: session.authority,
279
327
  permission: session.permission,
280
328
  role: session.role
281
329
  });
282
330
  } else {
283
- logger.info(`权限检查 - author对象中的字段:`, {
284
- authority: session.author.authority,
331
+ logger.debug(`权限检查 - author对象中的字段:`, {
285
332
  permission: session.author.permission,
286
333
  role: session.author.role,
287
334
  permissions: session.author.permissions
288
335
  });
289
336
  }
290
337
  if (session.user) {
291
- logger.info(`权限检查 - user对象中的权限信息:`, {
338
+ logger.debug(`权限检查 - user对象中的权限信息:`, {
292
339
  authority: session.user.authority,
293
340
  permission: session.user.permission,
294
341
  role: session.user.role
@@ -346,12 +393,12 @@ async function handleGuildMemberRequestEvent(ctx, session) {
346
393
  return;
347
394
  }
348
395
  const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
349
- 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}`);
350
397
  if (isValid) {
351
398
  if (requestId) {
352
399
  try {
353
400
  await session.bot.handleGuildMemberRequest(requestId, true);
354
- logger.info(`自动同意 requestId=${requestId}`);
401
+ logger.debug(`自动同意 requestId=${requestId}`);
355
402
  if (!autoQueue.has(guildId)) autoQueue.set(guildId, /* @__PURE__ */ new Set());
356
403
  autoQueue.get(guildId).add(userId);
357
404
  } catch (e) {
@@ -491,7 +538,7 @@ async function verifyApplication(config, message, session) {
491
538
  isValid = false;
492
539
  requiredThreshold = "null";
493
540
  }
494
- 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}`);
495
542
  return { isValid, matchedCount, requiredThreshold };
496
543
  }
497
544
  __name(verifyApplication, "verifyApplication");
@@ -504,7 +551,7 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
504
551
  }
505
552
  const username = session.username || "未知用户";
506
553
  const message = session.content || "";
507
- 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}`);
508
555
  if (matchedCount === void 0 || requiredThreshold === void 0) {
509
556
  const result = await verifyApplication(config, message, session);
510
557
  matchedCount = result.matchedCount;
@@ -527,7 +574,7 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
527
574
  applyTime: (/* @__PURE__ */ new Date()).toISOString()
528
575
  });
529
576
  if (!config.reminderEnabled || !config.reminderMessage || config.reminderMessage === "") {
530
- logger.info(`群 ${guildId} 的提醒消息已被禁用,跳过发送`);
577
+ logger.debug(`群 ${guildId} 的提醒消息已被禁用,跳过发送`);
531
578
  return;
532
579
  }
533
580
  let reminderMsg = config.reminderMessage;
@@ -575,6 +622,7 @@ function apply(ctx, config) {
575
622
  autoInc: true
576
623
  });
577
624
  logger = ctx.logger("group-verification");
625
+ if (config.logLevel) logger.level = config.logLevel;
578
626
  logger.info("群组验证插件已启动");
579
627
  logger.info(`默认提醒消息: ${config.defaultReminderMessage}`);
580
628
  logger.info(`严格群号检查: ${config.enableStrictGroupCheck ? "启用" : "禁用"}`);
@@ -640,19 +688,19 @@ function apply(ctx, config) {
640
688
  async function checkPermission2(session, targetGroupId) {
641
689
  const groupId = targetGroupId || session.guildId;
642
690
  if (!groupId) {
643
- return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
691
+ return [false, config.invalidGroupMessage || "请在群聊中使用此命令或使用 -i 参数指定群号"];
644
692
  }
645
- logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
693
+ logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
646
694
  const koishiAuthority = session.author?.authority || session.user?.authority;
647
- logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
695
+ logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
648
696
  if (!session.author) {
649
- logger.info(`权限检查 - session中可能的权限字段:`, {
697
+ logger.debug(`权限检查 - session中可能的权限字段:`, {
650
698
  authority: session.authority,
651
699
  permission: session.permission,
652
700
  role: session.role
653
701
  });
654
702
  } else {
655
- logger.info(`权限检查 - author对象中的字段:`, {
703
+ logger.debug(`权限检查 - author对象中的字段:`, {
656
704
  authority: session.author.authority,
657
705
  permission: session.author.permission,
658
706
  role: session.author.role,
@@ -660,19 +708,19 @@ function apply(ctx, config) {
660
708
  });
661
709
  }
662
710
  if (session.user) {
663
- logger.info(`权限检查 - user对象中的权限信息:`, {
711
+ logger.debug(`权限检查 - user对象中的权限信息:`, {
664
712
  authority: session.user.authority,
665
713
  permission: session.user.permission,
666
714
  role: session.user.role
667
715
  });
668
716
  }
669
717
  if (koishiAuthority && koishiAuthority >= 3) {
670
- logger.info(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
718
+ logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
671
719
  return [true];
672
720
  }
673
721
  try {
674
722
  const member = await session.bot.getGuildMember(groupId, session.userId);
675
- logger.info(`权限检查 - 获取到成员信息:`, {
723
+ logger.debug(`权限检查 - 获取到成员信息:`, {
676
724
  roles: member?.roles,
677
725
  permissions: member?.permissions
678
726
  });
@@ -682,7 +730,7 @@ function apply(ctx, config) {
682
730
  return [true];
683
731
  }
684
732
  if (member.roles?.includes("admin") || member.permissions?.includes("ADMINISTRATOR")) {
685
- logger.info(`权限检查 - 用户是管理员`);
733
+ logger.debug(`权限检查 - 用户是管理员`);
686
734
  return [true];
687
735
  }
688
736
  }
@@ -690,9 +738,9 @@ function apply(ctx, config) {
690
738
  logger.warn(`权限检查 - 获取群成员信息失败:`, error);
691
739
  return [false, `无法获取群 ${groupId} 的成员信息,请确认机器人已在该群中`];
692
740
  }
693
- logger.info(`权限检查 - 权限不足`);
741
+ logger.debug(`权限检查 - 权限不足`);
694
742
  const debugInfo = `调试信息 - 用户ID:${session.userId}, 群号:${groupId}, 权限等级:${koishiAuthority || "未知"}`;
695
- return [false, `权限不足:需要群主/管理员权限或koishi三级以上权限
743
+ return [false, (config.permissionDeniedMessage || "权限不足:需要群主/管理员权限或koishi三级以上权限") + `
696
744
  ${debugInfo}`];
697
745
  }
698
746
  __name(checkPermission2, "checkPermission");
@@ -729,7 +777,7 @@ ${debugInfo}`];
729
777
  };
730
778
  logger.info(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}`);
731
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)) {
732
- return "参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)";
780
+ return config.parameterConflictMessage || "参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)";
733
781
  }
734
782
  const hasRealMessageParam = cleanedOptions.message !== void 0;
735
783
  const hasRealEnableMessageParam = cleanedOptions.enableMessage === true;
@@ -817,7 +865,8 @@ ${debugInfo}`];
817
865
  审核方式: ${methodDesc}
818
866
  阈值: ${thresholdInfo}
819
867
  提醒消息: ${reminderStatus}
820
- 自定义消息: ${config2.reminderMessage || "无"}
868
+ 自定义消息:
869
+ ${config2.reminderMessage || "无"}
821
870
  创建时间: ${createTime}
822
871
  更新时间: ${updateTime}
823
872
  创建者: ${config2.createdBy}
@@ -841,7 +890,7 @@ ${debugInfo}`];
841
890
  }
842
891
  if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
843
892
  if (!existingConfig) {
844
- return "请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置";
893
+ return config.noKeywordsMessage || "请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置";
845
894
  }
846
895
  keywordList = existingConfig.keywords;
847
896
  }
@@ -851,7 +900,8 @@ ${debugInfo}`];
851
900
  hasRealMessageParam,
852
901
  hasRealEnableMessageParam,
853
902
  hasRealDisableMessageParam,
854
- logger
903
+ logger,
904
+ config.defaultReminderMessage
855
905
  );
856
906
  let reviewMethod = 0;
857
907
  let reviewParameters = 0;
@@ -869,44 +919,45 @@ ${debugInfo}`];
869
919
  if (isNaN(methodNum) || methodNum < 0 || methodNum > 3) {
870
920
  return "审核方式参数错误:0-全部同意, 1-按数量同意, 2-按比例同意, 3-全部拒绝";
871
921
  }
922
+ const oldMethod = reviewMethod;
872
923
  reviewMethod = methodNum;
873
- logger.info(`审核方式明确指定为: ${reviewMethod}`);
924
+ logger.debug(`审核方式明确指定为: ${reviewMethod}`);
925
+ var methodChanged = oldMethod !== reviewMethod;
874
926
  } else {
875
- logger.info(`未指定审核方式,保持原有值: ${reviewMethod}`);
927
+ logger.debug(`未指定审核方式,保持原有值: ${reviewMethod}`);
928
+ var methodChanged = false;
876
929
  }
877
- if (cleanedOptions.threshold !== void 0) {
878
- const thresholdNum = parseInt(cleanedOptions.threshold);
879
- if (isNaN(thresholdNum)) {
880
- return "阈值参数必须为数字";
881
- }
930
+ const thresholdResult = resolveThreshold(
931
+ existingConfig,
932
+ keywordList,
933
+ reviewMethod,
934
+ cleanedOptions.threshold,
935
+ methodChanged
936
+ );
937
+ if (thresholdResult.error) {
938
+ return thresholdResult.error;
939
+ }
940
+ reviewParameters = thresholdResult.reviewParameters;
941
+ if (existingConfig && reviewParameters !== existingConfig.reviewParameters) {
942
+ await ctx.database.set("group_verification_config", { id: existingConfig.id }, {
943
+ reviewParameters,
944
+ updatedBy: session.username || session.userId,
945
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
946
+ });
947
+ logger.debug(`自动调整并更新数据库阈值为: ${reviewParameters}`);
948
+ }
949
+ let autoAdjustNote = "";
950
+ if (thresholdResult.autoInfo === "methodChange") {
882
951
  if (reviewMethod === 1) {
883
- if (thresholdNum < 0 || thresholdNum > keywordList.length) {
884
- return `数量阈值必须在0-${keywordList.length}之间(0表示全部同意)`;
885
- }
886
- reviewParameters = thresholdNum;
887
- logger.info(`明确指定阈值: ${thresholdNum}`);
952
+ autoAdjustNote = `⚠️ 审核方式由 ${existingConfig?.reviewMethod} 改为 ${reviewMethod},阈值自动设为 ${reviewParameters}
953
+ `;
888
954
  } else if (reviewMethod === 2) {
889
- if (thresholdNum < 0 || thresholdNum > 100) {
890
- return "比例阈值必须在0-100之间(0表示全部同意)";
891
- }
892
- reviewParameters = thresholdNum;
893
- logger.info(`明确指定阈值: ${thresholdNum}%`);
894
- }
895
- } else if (existingConfig && reviewMethod === 1 && reviewParameters !== 0) {
896
- const oldKeywordCount = existingConfig.keywords.length;
897
- const newKeywordCount = keywordList.length;
898
- if (oldKeywordCount !== newKeywordCount) {
899
- reviewParameters = newKeywordCount;
900
- logger.info(`关键词数量从${oldKeywordCount}变为${newKeywordCount},自动调整阈值`);
901
- await ctx.database.set("group_verification_config", { id: existingConfig.id }, {
902
- reviewParameters,
903
- updatedBy: session.username || session.userId,
904
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
905
- });
906
- logger.info(`已更新数据库阈值为: ${reviewParameters}`);
907
- } else {
908
- logger.info(`关键词数量未变化(${newKeywordCount}),保持原阈值: ${reviewParameters}`);
955
+ autoAdjustNote = `⚠️ 审核方式由 ${existingConfig?.reviewMethod} 改为 ${reviewMethod},阈值自动设为 ${reviewParameters}%
956
+ `;
909
957
  }
958
+ } else if (thresholdResult.autoInfo === "kwChange") {
959
+ autoAdjustNote = `⚠️ 关键词数量从${existingConfig?.keywords.length}变为${keywordList.length},阈值已自动调整为${reviewParameters}
960
+ `;
910
961
  }
911
962
  const encodedKeywords = keywordList.map((keyword) => {
912
963
  return keyword.replace(/,/g, "[[COMMA]]");
@@ -940,6 +991,7 @@ ${debugInfo}`];
940
991
  const decodedKeywords = keywordList;
941
992
  let feedbackMessage = `群 ${targetGroupId} 配置已更新:
942
993
  `;
994
+ if (autoAdjustNote) feedbackMessage += autoAdjustNote;
943
995
  const displayKeywords = keywordList.map((k) => k.replace(/\[\[COMMA\]\]/g, ","));
944
996
  feedbackMessage += `关键词: ${displayKeywords.map((k) => `"${k}"`).join(", ")}
945
997
  `;
@@ -954,11 +1006,8 @@ ${debugInfo}`];
954
1006
  feedbackMessage += `提醒状态: ${reminderEnabled ? "启用" : "禁用"}
955
1007
  `;
956
1008
  if (reminderMessage && reminderEnabled) {
957
- feedbackMessage += `提醒消息: ${reminderMessage.substring(0, 30)}${reminderMessage.length > 30 ? "..." : ""}
958
- `;
959
- }
960
- if (existingConfig && reviewMethod === 1 && keywordList.length !== existingConfig.keywords.length) {
961
- feedbackMessage += `⚠️ 关键词数量从${existingConfig.keywords.length}变为${keywordList.length},阈值已自动调整为${keywordList.length}
1009
+ feedbackMessage += `提醒消息:
1010
+ ${reminderMessage}
962
1011
  `;
963
1012
  }
964
1013
  logger.info(`准备存储到数据库的关键词: ${JSON.stringify(encodedKeywords)}`);
@@ -1363,6 +1412,7 @@ __name(apply, "apply");
1363
1412
  mergeReminder,
1364
1413
  name,
1365
1414
  parseConfigArgs,
1415
+ resolveThreshold,
1366
1416
  syncTotalStats,
1367
1417
  tokenize,
1368
1418
  updateStats,
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "koishi-plugin-group-verification",
3
- "description": "[WIP] Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能(开发中)",
4
- "version": "1.0.28",
3
+ "description": "Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能。",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/LHDyx/koishi-plugin-group-verification.git"
7
+ },
8
+ "bugs": {
9
+ "url": "https://github.com/LHDyx/koishi-plugin-group-verification/issues"
10
+ },
11
+ "version": "1.0.30",
5
12
  "main": "lib/index.js",
6
13
  "typings": "lib/index.d.ts",
7
14
  "files": [
package/readme.md CHANGED
@@ -1,7 +1,10 @@
1
- # Koishi 群组验证插件 [WIP]
1
+ # Koishi 群组验证插件
2
2
 
3
3
  一个功能完整的 Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能。
4
4
 
5
+ **仓库**: https://github.com/LHDyx/koishi-plugin-group-verification
6
+ **问题反馈**: https://github.com/LHDyx/koishi-plugin-group-verification/issues
7
+
5
8
  ## 🌟 主要特性
6
9
 
7
10
  - **多关键词匹配**:支持多个关键词的灵活配置
@@ -13,6 +16,8 @@
13
16
 
14
17
  ## 🚀 安装使用
15
18
 
19
+ > 本插件已脱离开发阶段,可在 Koishi 插件管理页直接安装并配置。部分提示词可在管理界面自定义。
20
+
16
21
  ```
17
22
  # 在 Koishi 控制台中搜索并安装
18
23
  koishi-plugin-group-verification
@@ -99,6 +104,8 @@ group-verify.stats total
99
104
  ## ⚙️ 参数说明
100
105
 
101
106
  ### 审核方式 (-m)
107
+
108
+ *如果改变审核方式而未提供 `-t`,阈值会自动设置为最大值(方式1为关键词数量,方式2为100)。*
102
109
  - `0` - 全部同意(默认)
103
110
  - `1` - 按数量同意(需配合 -t 使用)
104
111
  - `2` - 按比例同意(需配合 -t 使用)
package/src/index.ts CHANGED
@@ -57,15 +57,25 @@ export interface Config {
57
57
  defaultReminderMessage?: string
58
58
  enableStrictGroupCheck?: boolean // 群号合法性检查配置
59
59
  logLevel?: 'debug' | 'info' | 'warn' | 'error' // 日志等级配置
60
+ // 以下为可自定义的命令反馈提示词,可在插件管理界面调整
61
+ permissionDeniedMessage?: string
62
+ invalidGroupMessage?: string
63
+ parameterConflictMessage?: string
64
+ noKeywordsMessage?: string
60
65
  }
61
66
 
62
67
  export const Config: Schema<Config> = Schema.object({
63
68
  defaultReminderMessage: Schema.string()
64
- .description('默认提醒消息模板(使用 \n 表示换行,可包含下方变量)')
69
+ .description('默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)')
65
70
  .default('{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}\n使用 gva 同意或 gvr 拒绝申请'),
66
71
  enableStrictGroupCheck: Schema.boolean().description('是否启用严格的群号检查(检查群号长度)').default(false),
67
- logLevel: Schema.union(['debug', 'info', 'warn', 'error']).description('日志级别').default('info')
68
- }).description('群组验证插件配置')
72
+ logLevel: Schema.union(['debug', 'info', 'warn', 'error']).description('日志级别').default('info'),
73
+ permissionDeniedMessage: Schema.string().description('权限不足时返回给调用者的提示').default('权限不足:需要群主/管理员权限或koishi三级以上权限'),
74
+ invalidGroupMessage: Schema.string().description('无效群号或机器人未在该群时的提示').default('群号 {group} 格式不合法或机器人不在该群中'),
75
+ parameterConflictMessage: Schema.string().description('参数冲突时提示').default('参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)'),
76
+ noKeywordsMessage: Schema.string().description('未提供关键词且无法从现有配置继承时的提示').default('请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置'),
77
+ })
78
+ .description('群组验证插件配置')
69
79
 
70
80
  export const inject = ['database']
71
81
 
@@ -257,10 +267,12 @@ export function mergeReminder(
257
267
  hasRealMessageParam: boolean,
258
268
  hasRealEnableMessageParam: boolean,
259
269
  hasRealDisableMessageParam: boolean,
260
- logger: any
270
+ logger: any,
271
+ defaultMessage?: string
261
272
  ) {
262
273
  let reminderEnabled = true;
263
- let reminderMessage = '{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}';
274
+ // 优先使用传入的默认模板,其次使用已有配置,再 fallback 到老写死的样式
275
+ let reminderMessage = defaultMessage || '{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}';
264
276
 
265
277
  if (existingConfig) {
266
278
  reminderEnabled = existingConfig.reminderEnabled;
@@ -271,15 +283,15 @@ export function mergeReminder(
271
283
  // 优先级:disable > bare enable > new message content
272
284
  if (hasRealDisableMessageParam) {
273
285
  reminderEnabled = false;
274
- logger.info('禁用提醒消息功能 (保留现有内容)');
286
+ logger.debug('禁用提醒消息功能 (保留现有内容)');
275
287
  } else if (hasRealEnableMessageParam) {
276
288
  reminderEnabled = true;
277
- logger.info(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
289
+ logger.debug(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
278
290
  } else if (hasRealMessageParam) {
279
291
  reminderEnabled = true;
280
292
  if (cleanedOptions.message !== undefined) {
281
293
  reminderMessage = cleanedOptions.message.replace(/\\n/g, '\n');
282
- logger.info(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
294
+ logger.debug(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
283
295
  }
284
296
  }
285
297
  return { reminderEnabled, reminderMessage };
@@ -319,7 +331,7 @@ export async function syncTotalStats(ctx: Context) {
319
331
  lastUpdated: new Date().toISOString()
320
332
  })
321
333
 
322
- logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`)
334
+ logger.debug(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`)
323
335
  }
324
336
  } catch (error) {
325
337
  logger.error('同步总计统计时出错:', error)
@@ -373,6 +385,65 @@ export async function incrementTotal(ctx: Context, groupId: string) {
373
385
  await syncTotalStats(ctx)
374
386
  }
375
387
 
388
+ // helper to decide reviewParameters based on existing configuration, keyword list, user inputs,
389
+ // and whether the audit method has been changed by the command.
390
+ export interface ThresholdResult {
391
+ reviewParameters: number
392
+ error?: string
393
+ autoInfo?: 'methodChange' | 'kwChange'
394
+ }
395
+
396
+ export function resolveThreshold(
397
+ existingConfig: any | null,
398
+ keywordList: string[],
399
+ reviewMethod: 0 | 1 | 2 | 3,
400
+ thresholdStr?: string,
401
+ methodChanged: boolean = false
402
+ ): ThresholdResult {
403
+ let reviewParameters = 0
404
+ if (existingConfig) {
405
+ reviewParameters = existingConfig.reviewParameters || 0
406
+ if (isNaN(reviewParameters)) reviewParameters = 0
407
+ }
408
+ // explicit threshold provided by user
409
+ if (thresholdStr !== undefined) {
410
+ const thresholdNum = parseInt(thresholdStr)
411
+ if (isNaN(thresholdNum)) {
412
+ return { reviewParameters, error: '阈值参数必须为数字' }
413
+ }
414
+ if (reviewMethod === 1) {
415
+ if (thresholdNum < 0 || thresholdNum > keywordList.length) {
416
+ return { reviewParameters, error: `数量阈值必须在0-${keywordList.length}之间(0表示全部同意)` }
417
+ }
418
+ } else if (reviewMethod === 2) {
419
+ if (thresholdNum < 0 || thresholdNum > 100) {
420
+ return { reviewParameters, error: '比例阈值必须在0-100之间(0表示全部同意)' }
421
+ }
422
+ }
423
+ return { reviewParameters: thresholdNum }
424
+ }
425
+ // no threshold specified by user
426
+ if (methodChanged) {
427
+ if (reviewMethod === 1) {
428
+ reviewParameters = keywordList.length
429
+ return { reviewParameters, autoInfo: 'methodChange' }
430
+ }
431
+ if (reviewMethod === 2) {
432
+ reviewParameters = 100
433
+ return { reviewParameters, autoInfo: 'methodChange' }
434
+ }
435
+ }
436
+ if (existingConfig && reviewMethod === 1 && reviewParameters !== 0) {
437
+ const oldKeywordCount = existingConfig.keywords.length
438
+ const newKeywordCount = keywordList.length
439
+ if (oldKeywordCount !== newKeywordCount) {
440
+ reviewParameters = newKeywordCount
441
+ return { reviewParameters, autoInfo: 'kwChange' }
442
+ }
443
+ }
444
+ return { reviewParameters }
445
+ }
446
+
376
447
  // 权限检查函数(也可用于命令)
377
448
  export async function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]> {
378
449
  const groupId = targetGroupId || session.guildId
@@ -382,19 +453,18 @@ export async function checkPermission(session: any, targetGroupId?: string): Pro
382
453
  return [false, '请在群聊中使用此命令或使用 -i 参数指定群号']
383
454
  }
384
455
 
385
- logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
456
+ logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
386
457
  const koishiAuthority = session.author?.authority || session.user?.authority
387
- logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
458
+ logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
388
459
 
389
460
  if (!session.author) {
390
- logger.info(`权限检查 - session中可能的权限字段:`, {
461
+ logger.debug(`权限检查 - session中可能的权限字段:`, {
391
462
  authority: session.authority,
392
463
  permission: session.permission,
393
464
  role: session.role
394
465
  })
395
466
  } else {
396
- logger.info(`权限检查 - author对象中的字段:`, {
397
- authority: session.author.authority,
467
+ logger.debug(`权限检查 - author对象中的字段:`, {
398
468
  permission: session.author.permission,
399
469
  role: session.author.role,
400
470
  permissions: session.author.permissions
@@ -402,7 +472,7 @@ export async function checkPermission(session: any, targetGroupId?: string): Pro
402
472
  }
403
473
 
404
474
  if (session.user) {
405
- logger.info(`权限检查 - user对象中的权限信息:`, {
475
+ logger.debug(`权限检查 - user对象中的权限信息:`, {
406
476
  authority: session.user.authority,
407
477
  permission: session.user.permission,
408
478
  role: session.user.role
@@ -464,13 +534,13 @@ export async function handleGuildMemberRequestEvent(ctx: Context, session: any)
464
534
  }
465
535
 
466
536
  const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
467
- logger.info(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
537
+ logger.debug(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
468
538
 
469
539
  if (isValid) {
470
540
  if (requestId) {
471
541
  try {
472
542
  await session.bot.handleGuildMemberRequest(requestId, true);
473
- logger.info(`自动同意 requestId=${requestId}`);
543
+ logger.debug(`自动同意 requestId=${requestId}`);
474
544
  if (!autoQueue.has(guildId)) autoQueue.set(guildId, new Set());
475
545
  autoQueue.get(guildId)!.add(userId);
476
546
  } catch (e) {
@@ -640,7 +710,7 @@ export async function verifyApplication(config: GroupVerificationConfig, message
640
710
  requiredThreshold = 'null'
641
711
  }
642
712
 
643
- logger.info(`verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`)
713
+ logger.debug(`verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`)
644
714
  return { isValid, matchedCount, requiredThreshold }
645
715
  }
646
716
 
@@ -661,7 +731,7 @@ export async function handleFailedVerification(
661
731
  }
662
732
  const username = session.username || '未知用户'
663
733
  const message = session.content || ''
664
- logger.info(`处理失败验证 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold}`)
734
+ logger.debug(`处理失败验证 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold}`)
665
735
  // 如果未传入匹配信息,则重新计算一次(老调用)
666
736
  if (matchedCount === undefined || requiredThreshold === undefined) {
667
737
  const result = await verifyApplication(config, message, session)
@@ -695,7 +765,7 @@ export async function handleFailedVerification(
695
765
  })
696
766
  // 如果提醒消息被禁用,直接返回
697
767
  if (!config.reminderEnabled || !config.reminderMessage || config.reminderMessage === '') {
698
- logger.info(`群 ${guildId} 的提醒消息已被禁用,跳过发送`)
768
+ logger.debug(`群 ${guildId} 的提醒消息已被禁用,跳过发送`)
699
769
  return
700
770
  }
701
771
 
@@ -760,6 +830,8 @@ export function apply(ctx: Context, config: Config) {
760
830
 
761
831
  // 获取logger实例并保存到模块级变量
762
832
  logger = ctx.logger('group-verification')
833
+ // 根据配置调整日志等级
834
+ if (config.logLevel) logger.level = config.logLevel
763
835
 
764
836
  // 设置日志级别
765
837
  // 注意:Koishi logger的level设置可能需要不同的方式
@@ -857,26 +929,26 @@ export function apply(ctx: Context, config: Config) {
857
929
 
858
930
  // 私聊情况下必须指定群号
859
931
  if (!groupId) {
860
- return [false, '请在群聊中使用此命令或使用 -i 参数指定群号']
932
+ return [false, config.invalidGroupMessage || '请在群聊中使用此命令或使用 -i 参数指定群号']
861
933
  }
862
934
 
863
935
  // 使用Koishi logger输出调试信息
864
- logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
936
+ logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
865
937
 
866
938
  // 检查koishi权限等级(最高优先级)
867
939
  const koishiAuthority = session.author?.authority || session.user?.authority
868
- logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
940
+ logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
869
941
 
870
942
  // 尝试其他可能的权限字段
871
943
  if (!session.author) {
872
- logger.info(`权限检查 - session中可能的权限字段:`, {
944
+ logger.debug(`权限检查 - session中可能的权限字段:`, {
873
945
  authority: session.authority,
874
946
  permission: session.permission,
875
947
  role: session.role
876
948
  })
877
949
  } else {
878
950
  // 检查author对象中的其他权限字段
879
- logger.info(`权限检查 - author对象中的字段:`, {
951
+ logger.debug(`权限检查 - author对象中的字段:`, {
880
952
  authority: session.author.authority,
881
953
  permission: session.author.permission,
882
954
  role: session.author.role,
@@ -886,7 +958,7 @@ export function apply(ctx: Context, config: Config) {
886
958
 
887
959
  // 尝试从user对象获取权限信息
888
960
  if (session.user) {
889
- logger.info(`权限检查 - user对象中的权限信息:`, {
961
+ logger.debug(`权限检查 - user对象中的权限信息:`, {
890
962
  authority: session.user.authority,
891
963
  permission: session.user.permission,
892
964
  role: session.user.role
@@ -895,14 +967,14 @@ export function apply(ctx: Context, config: Config) {
895
967
 
896
968
  // 先检查koishi权限等级(最高优先级)
897
969
  if (koishiAuthority && koishiAuthority >= 3) {
898
- logger.info(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
970
+ logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
899
971
  return [true]
900
972
  }
901
973
 
902
974
  // 再检查是否为群主或管理员(次优先级)
903
975
  try {
904
976
  const member = await session.bot.getGuildMember(groupId, session.userId)
905
- logger.info(`权限检查 - 获取到成员信息:`, {
977
+ logger.debug(`权限检查 - 获取到成员信息:`, {
906
978
  roles: member?.roles,
907
979
  permissions: member?.permissions
908
980
  })
@@ -915,7 +987,7 @@ export function apply(ctx: Context, config: Config) {
915
987
  }
916
988
  // 检查管理员权限
917
989
  if (member.roles?.includes('admin') || member.permissions?.includes('ADMINISTRATOR')) {
918
- logger.info(`权限检查 - 用户是管理员`)
990
+ logger.debug(`权限检查 - 用户是管理员`)
919
991
  return [true]
920
992
  }
921
993
  }
@@ -924,9 +996,9 @@ export function apply(ctx: Context, config: Config) {
924
996
  return [false, `无法获取群 ${groupId} 的成员信息,请确认机器人已在该群中`]
925
997
  }
926
998
 
927
- logger.info(`权限检查 - 权限不足`)
999
+ logger.debug(`权限检查 - 权限不足`)
928
1000
  const debugInfo = `调试信息 - 用户ID:${session.userId}, 群号:${groupId}, 权限等级:${koishiAuthority || '未知'}`
929
- return [false, `权限不足:需要群主/管理员权限或koishi三级以上权限\n${debugInfo}`]
1001
+ return [false, (config.permissionDeniedMessage || '权限不足:需要群主/管理员权限或koishi三级以上权限') + `\n${debugInfo}`]
930
1002
  }
931
1003
 
932
1004
  // Create main command with aliases
@@ -985,7 +1057,7 @@ export function apply(ctx: Context, config: Config) {
985
1057
  if ((cleanedOptions.query || cleanedOptions.remove) &&
986
1058
  (parsedKeywords.length > 0 || cleanedOptions.method !== undefined || cleanedOptions.threshold !== undefined ||
987
1059
  cleanedOptions.message !== undefined || cleanedOptions.enableMessage || cleanedOptions.disableMessage)) {
988
- return '参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)'
1060
+ return config.parameterConflictMessage || '参数冲突:-? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)'
989
1061
  }
990
1062
 
991
1063
  // 检查消息参数冲突
@@ -1100,7 +1172,7 @@ export function apply(ctx: Context, config: Config) {
1100
1172
  审核方式: ${methodDesc}
1101
1173
  阈值: ${thresholdInfo}
1102
1174
  提醒消息: ${reminderStatus}
1103
- 自定义消息: ${config.reminderMessage || '无'}
1175
+ 自定义消息:\n${config.reminderMessage || '无'}
1104
1176
  创建时间: ${createTime}
1105
1177
  更新时间: ${updateTime}
1106
1178
  创建者: ${config.createdBy}
@@ -1138,7 +1210,7 @@ export function apply(ctx: Context, config: Config) {
1138
1210
  // 处理关键词:如果没有关键词但有其他参数,从现有配置中获取
1139
1211
  if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
1140
1212
  if (!existingConfig) {
1141
- return '请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置'
1213
+ return config.noKeywordsMessage || '请先提供关键词创建配置,或使用 -? 查询配置,-r 删除配置'
1142
1214
  }
1143
1215
  keywordList = existingConfig.keywords
1144
1216
  }
@@ -1150,7 +1222,8 @@ export function apply(ctx: Context, config: Config) {
1150
1222
  hasRealMessageParam,
1151
1223
  hasRealEnableMessageParam,
1152
1224
  hasRealDisableMessageParam,
1153
- logger
1225
+ logger,
1226
+ config.defaultReminderMessage
1154
1227
  )
1155
1228
 
1156
1229
  // 解析审核方式和阈值(基于新的数据库模型设计)
@@ -1178,54 +1251,48 @@ export function apply(ctx: Context, config: Config) {
1178
1251
  if (isNaN(methodNum) || methodNum < 0 || methodNum > 3) {
1179
1252
  return '审核方式参数错误:0-全部同意, 1-按数量同意, 2-按比例同意, 3-全部拒绝'
1180
1253
  }
1254
+ const oldMethod = reviewMethod
1181
1255
  reviewMethod = methodNum as 0 | 1 | 2 | 3
1182
- logger.info(`审核方式明确指定为: ${reviewMethod}`)
1256
+ logger.debug(`审核方式明确指定为: ${reviewMethod}`)
1257
+ var methodChanged = oldMethod !== reviewMethod
1183
1258
  } else {
1184
- logger.info(`未指定审核方式,保持原有值: ${reviewMethod}`)
1259
+ logger.debug(`未指定审核方式,保持原有值: ${reviewMethod}`)
1260
+ var methodChanged = false
1185
1261
  }
1186
-
1187
- // 处理阈值参数
1188
- if (cleanedOptions.threshold !== undefined) {
1189
- // 用户明确指定了阈值
1190
- const thresholdNum = parseInt(cleanedOptions.threshold)
1191
- if (isNaN(thresholdNum)) {
1192
- return '阈值参数必须为数字'
1193
- }
1194
-
1195
- if (reviewMethod === 1) { // 按数量同意
1196
- if (thresholdNum < 0 || thresholdNum > keywordList.length) {
1197
- return `数量阈值必须在0-${keywordList.length}之间(0表示全部同意)`
1198
- }
1199
- reviewParameters = thresholdNum
1200
- logger.info(`明确指定阈值: ${thresholdNum}`)
1201
- } else if (reviewMethod === 2) { // 按比例同意
1202
- if (thresholdNum < 0 || thresholdNum > 100) {
1203
- return '比例阈值必须在0-100之间(0表示全部同意)'
1204
- }
1205
- reviewParameters = thresholdNum
1206
- logger.info(`明确指定阈值: ${thresholdNum}%`)
1207
- }
1208
- } else if (existingConfig && reviewMethod === 1 && reviewParameters !== 0) {
1209
- // 未指定阈值但审核方式为按数量,检查是否需要自动调整
1210
- const oldKeywordCount = existingConfig.keywords.length
1211
- const newKeywordCount = keywordList.length
1212
-
1213
- if (oldKeywordCount !== newKeywordCount) {
1214
- // 关键词数量变化,自动调整阈值
1215
- reviewParameters = newKeywordCount
1216
- logger.info(`关键词数量从${oldKeywordCount}变为${newKeywordCount},自动调整阈值`)
1217
-
1218
- // 立即更新数据库中的阈值
1219
- await ctx.database.set('group_verification_config', { id: existingConfig.id }, {
1220
- reviewParameters: reviewParameters,
1221
- updatedBy: session.username || session.userId,
1222
- updatedAt: new Date().toISOString()
1223
- })
1224
- logger.info(`已更新数据库阈值为: ${reviewParameters}`)
1225
- } else {
1226
- // 关键词数量相同,保持原阈值
1227
- logger.info(`关键词数量未变化(${newKeywordCount}),保持原阈值: ${reviewParameters}`)
1262
+
1263
+ // 决定最终的阈值,可能会触发自动调整
1264
+ const thresholdResult = resolveThreshold(
1265
+ existingConfig,
1266
+ keywordList,
1267
+ reviewMethod,
1268
+ cleanedOptions.threshold,
1269
+ methodChanged
1270
+ )
1271
+ if (thresholdResult.error) {
1272
+ return thresholdResult.error
1273
+ }
1274
+ reviewParameters = thresholdResult.reviewParameters
1275
+
1276
+ // 如果参数通过自动逻辑发生变化,回写数据库并记录提示
1277
+ if (existingConfig && reviewParameters !== existingConfig.reviewParameters) {
1278
+ await ctx.database.set('group_verification_config', { id: existingConfig.id }, {
1279
+ reviewParameters,
1280
+ updatedBy: session.username || session.userId,
1281
+ updatedAt: new Date().toISOString()
1282
+ })
1283
+ logger.debug(`自动调整并更新数据库阈值为: ${reviewParameters}`)
1284
+ }
1285
+
1286
+ // 自动调整说明,用于反馈消息
1287
+ let autoAdjustNote = ''
1288
+ if (thresholdResult.autoInfo === 'methodChange') {
1289
+ if (reviewMethod === 1) {
1290
+ autoAdjustNote = `⚠️ 审核方式由 ${existingConfig?.reviewMethod} 改为 ${reviewMethod},阈值自动设为 ${reviewParameters}\n`
1291
+ } else if (reviewMethod === 2) {
1292
+ autoAdjustNote = `⚠️ 审核方式由 ${existingConfig?.reviewMethod} 改为 ${reviewMethod},阈值自动设为 ${reviewParameters}%\n`
1228
1293
  }
1294
+ } else if (thresholdResult.autoInfo === 'kwChange') {
1295
+ autoAdjustNote = `⚠️ 关键词数量从${existingConfig?.keywords.length}变为${keywordList.length},阈值已自动调整为${reviewParameters}\n`
1229
1296
  }
1230
1297
 
1231
1298
  // 数据库存储前的关键词编码处理
@@ -1270,6 +1337,7 @@ export function apply(ctx: Context, config: Config) {
1270
1337
 
1271
1338
  // 构建完整的配置反馈信息
1272
1339
  let feedbackMessage = `群 ${targetGroupId} 配置已更新:\n`
1340
+ if (autoAdjustNote) feedbackMessage += autoAdjustNote
1273
1341
  // 使用原始输入的关键词显示,而不是处理后的keywordList
1274
1342
  const displayKeywords = keywordList.map(k => k.replace(/\[\[COMMA\]\]/g, ','))
1275
1343
  feedbackMessage += `关键词: ${displayKeywords.map(k => `"${k}"`).join(', ')}\n`
@@ -1285,14 +1353,10 @@ export function apply(ctx: Context, config: Config) {
1285
1353
 
1286
1354
  feedbackMessage += `提醒状态: ${reminderEnabled ? '启用' : '禁用'}\n`
1287
1355
  if (reminderMessage && reminderEnabled) {
1288
- feedbackMessage += `提醒消息: ${reminderMessage.substring(0, 30)}${reminderMessage.length > 30 ? '...' : ''}\n`
1356
+ // show full message on its own lines so users can see everything
1357
+ feedbackMessage += `提醒消息:\n${reminderMessage}\n`
1289
1358
  }
1290
1359
 
1291
- // 添加阈值自动调整提醒(如果有)
1292
- if (existingConfig && reviewMethod === 1 &&
1293
- keywordList.length !== existingConfig.keywords.length) {
1294
- feedbackMessage += `⚠️ 关键词数量从${existingConfig.keywords.length}变为${keywordList.length},阈值已自动调整为${keywordList.length}\n`
1295
- }
1296
1360
 
1297
1361
  // 同时更新数据库存储时也要确保正确格式
1298
1362
  logger.info(`准备存储到数据库的关键词: ${JSON.stringify(encodedKeywords)}`)