koishi-plugin-group-verification 1.0.35 → 1.0.36

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
@@ -47,7 +47,7 @@ export interface PendingVerification {
47
47
  export interface Config {
48
48
  defaultReminderMessage?: string;
49
49
  enableStrictGroupCheck?: boolean;
50
- logLevel?: 'debug' | 'info' | 'warn' | 'error';
50
+ logLevel?: 'debug' | 'info' | 'warn';
51
51
  permissionDeniedMessage?: string;
52
52
  invalidGroupMessage?: string;
53
53
  parameterConflictMessage?: string;
package/lib/index.js CHANGED
@@ -44,10 +44,27 @@ module.exports = __toCommonJS(src_exports);
44
44
  var import_koishi = require("koishi");
45
45
  var name = "group-verification";
46
46
  var logger = console;
47
+ var pluginLogLevel = "中等";
48
+ function clog(lvl, mediumMsg, verboseMsg) {
49
+ if (pluginLogLevel === "简洁") {
50
+ if (lvl === "warn" || lvl === "error") logger[lvl](mediumMsg);
51
+ } else if (pluginLogLevel === "详细") {
52
+ logger[lvl](verboseMsg ?? mediumMsg);
53
+ } else {
54
+ const outputLvl = lvl === "debug" ? "info" : lvl;
55
+ logger[outputLvl](mediumMsg);
56
+ }
57
+ }
58
+ __name(clog, "clog");
59
+ function clogV(msg, ...args) {
60
+ if (pluginLogLevel === "详细") logger.debug(msg, ...args);
61
+ }
62
+ __name(clogV, "clogV");
47
63
  var Config = import_koishi.Schema.object({
48
64
  defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)").default("{user}({id}) 申请入群\\n申请理由: {question}\\n匹配情况: {answer}/{threshold}\\n使用 gva 同意或 gvr 拒绝申请"),
49
65
  enableStrictGroupCheck: import_koishi.Schema.boolean().description("是否启用严格的群号检查(检查群号长度)").default(false),
50
- logLevel: import_koishi.Schema.union(["debug", "info", "warn", "error"]).description("日志级别").default("info"),
66
+ logLevel: import_koishi.Schema.union(["debug", "info", "warn"]).description("日志详细程度").default("info"),
67
+ // debug=详细, info=中等, warn=简洁
51
68
  permissionDeniedMessage: import_koishi.Schema.string().description("权限不足时返回给调用者的提示").default("权限不足: 需要群主/管理员权限或koishi三级以上权限"),
52
69
  invalidGroupMessage: import_koishi.Schema.string().description("无效群号或机器人未在该群时的提示").default("群号 {group} 格式不合法或机器人不在该群中"),
53
70
  parameterConflictMessage: import_koishi.Schema.string().description("参数冲突时提示").default("参数冲突: -? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)"),
@@ -201,15 +218,15 @@ function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasR
201
218
  }
202
219
  if (hasRealDisableMessageParam) {
203
220
  reminderEnabled = false;
204
- logger2.debug("禁用提醒消息功能 (保留现有内容)");
221
+ clog("debug", "禁用提醒消息功能 (保留现有内容)");
205
222
  } else if (hasRealEnableMessageParam) {
206
223
  reminderEnabled = true;
207
- logger2.debug(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
224
+ clog("debug", `启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
208
225
  } else if (hasRealMessageParam) {
209
226
  reminderEnabled = true;
210
227
  if (cleanedOptions.message !== void 0) {
211
228
  reminderMessage = cleanedOptions.message;
212
- logger2.debug(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
229
+ clog("debug", `设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
213
230
  }
214
231
  }
215
232
  return { reminderEnabled, reminderMessage };
@@ -233,7 +250,7 @@ async function syncTotalStats(ctx) {
233
250
  totalJoined,
234
251
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
235
252
  });
236
- logger.debug(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`);
253
+ clog("debug", `总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`);
237
254
  }
238
255
  } catch (error) {
239
256
  logger.error("同步总计统计时出错:", error);
@@ -330,46 +347,49 @@ async function checkPermission(session, targetGroupId) {
330
347
  if (!groupId) {
331
348
  return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
332
349
  }
333
- logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
334
350
  const koishiAuthority = session.author?.authority || session.user?.authority;
335
- logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
351
+ clogV(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
352
+ clogV(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
336
353
  if (!session.author) {
337
- logger.debug(`权限检查 - session中可能的权限字段:`, {
354
+ clogV(`权限检查 - session中可能的权限字段:`, {
338
355
  authority: session.authority,
339
356
  permission: session.permission,
340
357
  role: session.role
341
358
  });
342
359
  } else {
343
- logger.debug(`权限检查 - author对象中的字段:`, {
360
+ clogV(`权限检查 - author对象中的字段:`, {
344
361
  permission: session.author.permission,
345
362
  role: session.author.role,
346
363
  permissions: session.author.permissions
347
364
  });
348
365
  }
349
366
  if (session.user) {
350
- logger.debug(`权限检查 - user对象中的权限信息:`, {
367
+ clogV(`权限检查 - user对象中的权限信息:`, {
351
368
  authority: session.user.authority,
352
369
  permission: session.user.permission,
353
370
  role: session.user.role
354
371
  });
355
372
  }
356
373
  if (koishiAuthority && koishiAuthority >= 3) {
357
- logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
374
+ clogV(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
375
+ clog("debug", `权限通过(koishi): uid=${session.userId} authority=${koishiAuthority}`);
358
376
  return [true];
359
377
  }
360
378
  try {
361
379
  const member = await session.bot.getGuildMember(groupId, session.userId);
362
- logger.debug(`权限检查 - 获取到成员信息:`, {
380
+ clogV(`权限检查 - 获取到成员信息:`, {
363
381
  roles: member?.roles,
364
382
  permissions: member?.permissions
365
383
  });
366
384
  if (member) {
367
385
  if (member.permissions?.includes("OWNER") || member.roles?.includes("owner")) {
368
- logger.debug(`权限检查 - 用户是群主`);
386
+ clogV(`权限检查 - 用户是群主`);
387
+ clog("debug", `权限通过(群主): uid=${session.userId}`);
369
388
  return [true];
370
389
  }
371
390
  if (member.roles?.includes("admin") || member.permissions?.includes("ADMINISTRATOR")) {
372
- logger.debug(`权限检查 - 用户是管理员`);
391
+ clogV(`权限检查 - 用户是管理员`);
392
+ clog("debug", `权限通过(管理员): uid=${session.userId}`);
373
393
  return [true];
374
394
  }
375
395
  }
@@ -380,9 +400,10 @@ async function checkPermission(session, targetGroupId) {
380
400
  }
381
401
  __name(checkPermission, "checkPermission");
382
402
  async function handleGuildMemberRequestEvent(ctx, session) {
383
- logger.debug("guild-member-request event", session);
403
+ clogV("guild-member-request event", session);
384
404
  let guildId = (session.guildId || session.channelId || "").toString().trim();
385
405
  const userId = session.userId;
406
+ clog("debug", `guild-member-request: guild=${guildId} user=${userId}`);
386
407
  const message = session.content || "";
387
408
  if (!guildId) {
388
409
  logger.warn("guild-member-request 没有 guildId,跳过处理");
@@ -393,7 +414,7 @@ async function handleGuildMemberRequestEvent(ctx, session) {
393
414
  if (!groupConfig || groupConfig.length === 0) return;
394
415
  const config = groupConfig[0];
395
416
  if (config.reviewMethod === 3) {
396
- logger.info(`配置要求全部拒绝,自动拒绝用户 ${userId}`);
417
+ clog("info", `配置要求全部拒绝,自动拒绝用户 ${userId}`);
397
418
  if (requestId) {
398
419
  try {
399
420
  await session.bot.handleGuildMemberRequest(requestId, false);
@@ -407,7 +428,7 @@ async function handleGuildMemberRequestEvent(ctx, session) {
407
428
  try {
408
429
  const blacklisted = await isUserBlacklisted(ctx, guildId, userId);
409
430
  if (blacklisted) {
410
- logger.info(`用户 ${userId} 在群 ${guildId} 或全局黑名单中,自动拒绝申请`);
431
+ clog("info", `用户 ${userId} 在群 ${guildId} 或全局黑名单中,自动拒绝申请`);
411
432
  if (requestId) {
412
433
  try {
413
434
  await session.bot.handleGuildMemberRequest(requestId, false);
@@ -422,12 +443,13 @@ async function handleGuildMemberRequestEvent(ctx, session) {
422
443
  logger.warn("黑名单检查失败", e);
423
444
  }
424
445
  const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
425
- logger.debug(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
446
+ clogV(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
426
447
  if (isValid) {
427
448
  if (requestId) {
428
449
  try {
429
450
  await session.bot.handleGuildMemberRequest(requestId, true);
430
- logger.debug(`自动同意 requestId=${requestId}`);
451
+ clogV(`自动同意 requestId=${requestId}`);
452
+ await updateStats(ctx, guildId, "autoApproved");
431
453
  if (!autoQueue.has(guildId)) autoQueue.set(guildId, /* @__PURE__ */ new Set());
432
454
  autoQueue.get(guildId).add(userId);
433
455
  } catch (e) {
@@ -567,7 +589,11 @@ async function verifyApplication(config, message, session) {
567
589
  isValid = false;
568
590
  requiredThreshold = "null";
569
591
  }
570
- logger.debug(`verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
592
+ clog(
593
+ "debug",
594
+ `verify: matched=${matchedCount}/${requiredThreshold} valid=${isValid}`,
595
+ `verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`
596
+ );
571
597
  return { isValid, matchedCount, requiredThreshold };
572
598
  }
573
599
  __name(verifyApplication, "verifyApplication");
@@ -580,7 +606,11 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
580
606
  }
581
607
  const username = session.username || "未知用户";
582
608
  const message = session.content || "";
583
- logger.debug(`处理失败验证 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold}`);
609
+ clog(
610
+ "debug",
611
+ `待审核: guild=${guildId} user=${userId} matched=${matchedCount}/${requiredThreshold}`,
612
+ `处理失败验证 guild=${guildId} user=${userId} msg="${session.content || ""}" matched=${matchedCount} threshold=${requiredThreshold}`
613
+ );
584
614
  if (matchedCount === void 0 || requiredThreshold === void 0) {
585
615
  const result = await verifyApplication(config, message, session);
586
616
  matchedCount = result.matchedCount;
@@ -603,7 +633,7 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
603
633
  applyTime: (/* @__PURE__ */ new Date()).toISOString()
604
634
  });
605
635
  if (!config.reminderEnabled || !config.reminderMessage || config.reminderMessage === "") {
606
- logger.debug(`群 ${guildId} 的提醒消息已被禁用,跳过发送`);
636
+ clog("debug", `群 ${guildId} 的提醒消息已被禁用,跳过发送`);
607
637
  return;
608
638
  }
609
639
  let reminderMsg = config.reminderMessage.replace(/\\\\n/g, "").replace(/\\n/g, "\n").replace(/\x01/g, "\\n");
@@ -611,7 +641,7 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
611
641
  const rawChannel = (session.channelId || "").toString().trim();
612
642
  const channel = rawChannel || guildId;
613
643
  const target = rawChannel ? [channel, guildId] : guildId;
614
- logger.debug("broadcast target", { channel, guildId, target });
644
+ clog("debug", `broadcast target: channel=${channel} guild=${guildId}`);
615
645
  if (session.bot && typeof session.bot.broadcast === "function") {
616
646
  try {
617
647
  await session.bot.broadcast([target], reminderMsg);
@@ -620,14 +650,14 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
620
650
  if (typeof ctx.broadcast === "function") {
621
651
  await ctx.broadcast([target], reminderMsg);
622
652
  } else {
623
- logger.info("ctx.broadcast 不可用,跳过发送");
653
+ clog("info", "ctx.broadcast 不可用,跳过发送");
624
654
  }
625
655
  }
626
656
  } else {
627
657
  if (typeof ctx.broadcast === "function") {
628
658
  await ctx.broadcast([target], reminderMsg);
629
659
  } else {
630
- logger.info("ctx.broadcast 不可用,跳过发送");
660
+ clog("info", "ctx.broadcast 不可用,跳过发送");
631
661
  }
632
662
  }
633
663
  }
@@ -711,7 +741,7 @@ async function processBlacklistCommand(ctx, session, rawArgs, config) {
711
741
  if (session.bot && typeof session.bot.kickGuildMember === "function") {
712
742
  try {
713
743
  await session.bot.kickGuildMember(group, targetUser);
714
- logger.info(`已将黑名单用户 ${targetUser} 从群 ${group} 踢出`);
744
+ clog("info", `已将黑名单用户 ${targetUser} 从群 ${group} 踢出`);
715
745
  } catch (e) {
716
746
  logger.warn(`踢出用户 ${targetUser} 失败`, e);
717
747
  }
@@ -863,11 +893,12 @@ function apply(ctx, config) {
863
893
  autoInc: true
864
894
  });
865
895
  logger = ctx.logger("group-verification");
866
- if (config.logLevel) logger.level = config.logLevel;
867
- logger.info("群组验证插件已启动");
868
- logger.info(`默认提醒消息: ${config.defaultReminderMessage}`);
869
- logger.info(`严格群号检查: ${config.enableStrictGroupCheck ? "启用" : "禁用"}`);
870
- logger.info(`日志级别: ${config.logLevel}`);
896
+ pluginLogLevel = { debug: "详细", info: "中等", warn: "简洁" }[config?.logLevel ?? "info"] ?? "中等";
897
+ if (pluginLogLevel === "详细") logger.level = 3;
898
+ clog("info", "群组验证插件已启动");
899
+ clog("info", `默认提醒消息: ${config.defaultReminderMessage}`);
900
+ clog("info", `严格群号检查: ${config.enableStrictGroupCheck ? "启用" : "禁用"}`);
901
+ clog("info", `日志详细度: ${config.logLevel ?? "中等"}`);
871
902
  ctx.model.extend("group_verification_stats", {
872
903
  id: "unsigned",
873
904
  groupId: "string",
@@ -906,54 +937,36 @@ function apply(ctx, config) {
906
937
  indexes: [["groupId"]]
907
938
  });
908
939
  ctx.on("guild-member-request", async (session) => {
909
- logger.debug("收到 guild-member-request 事件,转发给处理函数");
940
+ clog("debug", "收到 guild-member-request 事件,转发给处理函数");
910
941
  await handleGuildMemberRequestEvent(ctx, session);
911
942
  });
912
943
  ctx.on("guild-member-added", async (session) => {
913
944
  const groupId = session.guildId || "";
914
945
  const userId = session.userId || "";
915
- if (!groupId || !userId) {
916
- return;
917
- }
946
+ if (!groupId || !userId) return;
918
947
  await incrementTotal(ctx, groupId);
919
- const set = autoQueue.get(groupId);
920
- if (set && set.has(userId)) {
921
- await updateStats(ctx, groupId, "autoApproved");
922
- set.delete(userId);
923
- logger.info(`用户 ${userId} 通过机器人审批加入群 ${groupId}(autoQueue),统计已更新`);
924
- return;
925
- }
926
- const pendingRecords = await ctx.database.get("group_verification_pending", {
927
- groupId,
928
- userId
929
- });
930
- if (pendingRecords.length > 0) {
931
- await updateStats(ctx, groupId, "autoApproved");
932
- for (const rec of pendingRecords) {
933
- await ctx.database.remove("group_verification_pending", { id: rec.id });
934
- }
935
- logger.info(`用户 ${userId} 通过验证加入群 ${groupId},已清理 ${pendingRecords.length} 条待审核记录,统计已更新`);
936
- } else {
937
- await updateStats(ctx, groupId, "manuallyApproved");
938
- logger.info(`用户 ${userId} 被手动邀请加入群 ${groupId},手动批准统计已更新`);
948
+ const autoSet = autoQueue.get(groupId);
949
+ if (autoSet && autoSet.has(userId)) {
950
+ autoSet.delete(userId);
939
951
  }
952
+ await ctx.database.remove("group_verification_pending", { groupId, userId });
940
953
  });
941
954
  async function checkPermission2(session, targetGroupId) {
942
955
  const groupId = targetGroupId || session.guildId;
943
956
  if (!groupId) {
944
957
  return [false, config.invalidGroupMessage || "请在群聊中使用此命令或使用 -i 参数指定群号"];
945
958
  }
946
- logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
947
959
  const koishiAuthority = session.author?.authority || session.user?.authority;
948
- logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
960
+ clogV(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
961
+ clogV(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
949
962
  if (!session.author) {
950
- logger.debug(`权限检查 - session中可能的权限字段:`, {
963
+ clogV(`权限检查 - session中可能的权限字段:`, {
951
964
  authority: session.authority,
952
965
  permission: session.permission,
953
966
  role: session.role
954
967
  });
955
968
  } else {
956
- logger.debug(`权限检查 - author对象中的字段:`, {
969
+ clogV(`权限检查 - author对象中的字段:`, {
957
970
  authority: session.author.authority,
958
971
  permission: session.author.permission,
959
972
  role: session.author.role,
@@ -961,29 +974,32 @@ function apply(ctx, config) {
961
974
  });
962
975
  }
963
976
  if (session.user) {
964
- logger.debug(`权限检查 - user对象中的权限信息:`, {
977
+ clogV(`权限检查 - user对象中的权限信息:`, {
965
978
  authority: session.user.authority,
966
979
  permission: session.user.permission,
967
980
  role: session.user.role
968
981
  });
969
982
  }
970
983
  if (koishiAuthority && koishiAuthority >= 3) {
971
- logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
984
+ clogV(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
985
+ clog("debug", `权限通过(koishi): uid=${session.userId} authority=${koishiAuthority}`);
972
986
  return [true];
973
987
  }
974
988
  try {
975
989
  const member = await session.bot.getGuildMember(groupId, session.userId);
976
- logger.debug(`权限检查 - 获取到成员信息:`, {
990
+ clogV(`权限检查 - 获取到成员信息:`, {
977
991
  roles: member?.roles,
978
992
  permissions: member?.permissions
979
993
  });
980
994
  if (member) {
981
995
  if (member.permissions?.includes("OWNER") || member.roles?.includes("owner")) {
982
- logger.debug(`权限检查 - 用户是群主`);
996
+ clogV(`权限检查 - 用户是群主`);
997
+ clog("debug", `权限通过(群主): uid=${session.userId}`);
983
998
  return [true];
984
999
  }
985
1000
  if (member.roles?.includes("admin") || member.permissions?.includes("ADMINISTRATOR")) {
986
- logger.debug(`权限检查 - 用户是管理员`);
1001
+ clogV(`权限检查 - 用户是管理员`);
1002
+ clog("debug", `权限通过(管理员): uid=${session.userId}`);
987
1003
  return [true];
988
1004
  }
989
1005
  }
@@ -991,10 +1007,8 @@ function apply(ctx, config) {
991
1007
  logger.warn(`权限检查 - 获取群成员信息失败:`, error);
992
1008
  return [false, `无法获取群 ${groupId} 的成员信息,请确认机器人已在该群中`];
993
1009
  }
994
- logger.debug(`权限检查 - 权限不足`);
995
- const debugInfo = `调试信息 - 用户ID:${session.userId}, 群号:${groupId}, 权限等级:${koishiAuthority || "未知"}`;
996
- return [false, (config.permissionDeniedMessage || "权限不足: 需要群主/管理员权限或koishi三级以上权限") + `
997
- ${debugInfo}`];
1010
+ clogV(`权限检查 - 权限不足`);
1011
+ return [false, config.permissionDeniedMessage || "权限不足: 需要群主/管理员权限或koishi三级以上权限"];
998
1012
  }
999
1013
  __name(checkPermission2, "checkPermission");
1000
1014
  const groupVerify = ctx.command("group-verify", "群组验证管理命令").alias("gv", "gverify");
@@ -1007,16 +1021,17 @@ ${debugInfo}`];
1007
1021
  "group-verify.配置",
1008
1022
  "gvc"
1009
1023
  ).option("groupId", "-i <groupId> 指定群号").option("method", "-m <method> 审核方式 (0-3)").option("threshold", "-t <threshold> 阈值参数").option("message", "-msg [message] 自定义提醒消息").option("disableMessage", "-nomsg 禁用提醒消息").option("query", "-? 查询当前配置").option("remove", "-r 删除配置").action(async ({ session, options }, keywords) => {
1010
- logger.debug(`=== 命令解析调试 ===`);
1011
- logger.debug(`session内容: guildId=${session.guildId}, userId=${session.userId}`);
1024
+ clogV(`=== 命令解析调试 ===`);
1025
+ clogV(`session内容: guildId=${session.guildId}, userId=${session.userId}`);
1012
1026
  const rawInput = session.content.split(/\s+/).slice(1).join(" ");
1013
- logger.debug(`原始命令参数: "${rawInput}"`);
1027
+ clogV(`原始命令参数: "${rawInput}"`);
1028
+ if (pluginLogLevel !== "详细") clog("debug", `gvc: uid=${session.userId} guild=${session.guildId} args="${rawInput}"`);
1014
1029
  const parsed = parseConfigArgs(rawInput);
1015
1030
  const { keywords: parsedKeywords, flags, error: parseError } = parsed;
1016
1031
  if (parseError) {
1017
1032
  return parseError;
1018
1033
  }
1019
- logger.debug(`解析结果 flags=${JSON.stringify(flags)}, keywords=[${parsedKeywords.join(", ")}]`);
1034
+ clog("debug", `解析结果 flags=${JSON.stringify(flags)}, keywords=[${parsedKeywords.join(", ")}]`);
1020
1035
  const cleanedOptions = {
1021
1036
  groupId: flags.groupId || options.groupId,
1022
1037
  method: flags.method || (options.method === "" ? void 0 : options.method),
@@ -1028,7 +1043,8 @@ ${debugInfo}`];
1028
1043
  query: flags.query || options.query,
1029
1044
  remove: flags.remove || options.remove
1030
1045
  };
1031
- logger.debug(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}`);
1046
+ clogV(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}
1047
+ `);
1032
1048
  if ((cleanedOptions.query || cleanedOptions.remove) && (parsedKeywords.length > 0 || cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || cleanedOptions.message !== void 0 || cleanedOptions.enableMessage || cleanedOptions.disableMessage)) {
1033
1049
  return config.parameterConflictMessage || "参数冲突: -? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)";
1034
1050
  }
@@ -1077,7 +1093,7 @@ ${debugInfo}`];
1077
1093
  const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
1078
1094
  if (existingConfig2.length > 0) {
1079
1095
  await ctx.database.remove("group_verification_config", { id: existingConfig2[0].id });
1080
- logger.info(`配置删除 - 用户ID:${session.userId}, 群号:${targetGroupId}`);
1096
+ clog("info", `配置删除 - 用户ID:${session.userId}, 群号:${targetGroupId}`);
1081
1097
  return `已删除群 ${targetGroupId} 的验证配置`;
1082
1098
  } else {
1083
1099
  return `群 ${targetGroupId} 无验证配置`;
@@ -1130,7 +1146,7 @@ ${displayMsg}
1130
1146
  }
1131
1147
  }
1132
1148
  let keywordList = parsedKeywords.slice();
1133
- logger.debug(`关键词解析结果: [${keywordList.join(", ")}] - 原始输入: "${rawInput}"`);
1149
+ clogV(`关键词解析结果: [${keywordList.join(", ")}] - 原始输入: "${rawInput}"`);
1134
1150
  if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
1135
1151
  const hasConfigParams = cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || hasRealMessageParam || hasRealEnableMessageParam || hasRealDisableMessageParam;
1136
1152
  if (!hasConfigParams) {
@@ -1163,7 +1179,7 @@ ${displayMsg}
1163
1179
  reviewMethod = existingConfig.reviewMethod;
1164
1180
  if (existingConfig.reviewParameters === void 0 || existingConfig.reviewParameters === null || isNaN(existingConfig.reviewParameters)) {
1165
1181
  reviewParameters = 0;
1166
- logger.debug(`检测到老版本数据或无效值,使用默认阈值: 0`);
1182
+ clog("debug", `检测到老版本数据或无效值,使用默认阈值: 0`);
1167
1183
  } else {
1168
1184
  reviewParameters = existingConfig.reviewParameters;
1169
1185
  }
@@ -1175,10 +1191,10 @@ ${displayMsg}
1175
1191
  }
1176
1192
  const oldMethod = reviewMethod;
1177
1193
  reviewMethod = methodNum;
1178
- logger.debug(`审核方式明确指定为: ${reviewMethod}`);
1194
+ clog("debug", `审核方式明确指定为: ${reviewMethod}`);
1179
1195
  var methodChanged = oldMethod !== reviewMethod;
1180
1196
  } else {
1181
- logger.debug(`未指定审核方式,保持原有值: ${reviewMethod}`);
1197
+ clog("debug", `未指定审核方式,保持原有值: ${reviewMethod}`);
1182
1198
  var methodChanged = false;
1183
1199
  }
1184
1200
  const thresholdResult = resolveThreshold(
@@ -1198,7 +1214,7 @@ ${displayMsg}
1198
1214
  updatedBy: session.username || session.userId,
1199
1215
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1200
1216
  });
1201
- logger.debug(`自动调整并更新数据库阈值为: ${reviewParameters}`);
1217
+ clog("debug", `自动调整并更新数据库阈值为: ${reviewParameters}`);
1202
1218
  }
1203
1219
  let autoAdjustNote = "";
1204
1220
  if (thresholdResult.autoInfo === "methodChange") {
@@ -1216,7 +1232,6 @@ ${displayMsg}
1216
1232
  const encodedKeywords = keywordList.map((keyword) => {
1217
1233
  return keyword.replace(/,/g, "[[COMMA]]");
1218
1234
  });
1219
- logger.debug(`编码后准备存储的关键词: ${JSON.stringify(encodedKeywords)}`);
1220
1235
  const dbData = {
1221
1236
  keywords: encodedKeywords,
1222
1237
  reviewMethod,
@@ -1232,7 +1247,7 @@ ${displayMsg}
1232
1247
  ...dbData,
1233
1248
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1234
1249
  });
1235
- logger.info(`更新配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
1250
+ clog("info", `更新配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
1236
1251
  } else {
1237
1252
  await ctx.database.create("group_verification_config", {
1238
1253
  groupId: targetGroupId,
@@ -1240,7 +1255,7 @@ ${displayMsg}
1240
1255
  createdBy: session.username || session.userId,
1241
1256
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1242
1257
  });
1243
- logger.info(`创建配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
1258
+ clog("info", `创建配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
1244
1259
  }
1245
1260
  const decodedKeywords = keywordList;
1246
1261
  let feedbackMessage = `群 ${targetGroupId} 配置已更新:
@@ -1265,21 +1280,7 @@ ${displayMsg}
1265
1280
  ${displayReminder}
1266
1281
  `;
1267
1282
  }
1268
- logger.debug(`准备存储到数据库的关键词: ${JSON.stringify(encodedKeywords)}`);
1269
- logger.debug(`=== 配置处理详情 ===`);
1270
- logger.debug(`原始输入: ${keywords || "无关键词"}`);
1271
- logger.debug(`审核方式: ${reviewMethod} (${["全部同意", "按数量", "按比例", "全部拒绝"][reviewMethod]})`);
1272
- logger.debug(`阈值参数: ${JSON.stringify(reviewParameters)}`);
1273
- logger.debug(`关键词列表: [${keywordList.map((k) => `"${k}"`).join(", ")}]`);
1274
- logger.debug(`现有配置: ${existingConfig ? "存在" : "不存在"}`);
1275
- if (existingConfig) {
1276
- logger.debug(`原审核方式: ${existingConfig.reviewMethod}`);
1277
- logger.debug(`原阈值: ${JSON.stringify(existingConfig.reviewParameters)}`);
1278
- logger.debug(`原关键词数: ${existingConfig.keywords.length}`);
1279
- logger.debug(`新关键词数: ${keywordList.length}`);
1280
- }
1281
- logger.debug(`==================`);
1282
- logger.info(feedbackMessage.replace(/\n/g, "; "));
1283
+ clog("info", feedbackMessage.replace(/\n/g, "; "));
1283
1284
  return feedbackMessage;
1284
1285
  });
1285
1286
  groupVerify.subcommand(".approve [userId]", "同意加群申请").alias(
@@ -1315,6 +1316,7 @@ ${displayReminder}
1315
1316
  if (request2.requestId) {
1316
1317
  try {
1317
1318
  await session.bot.handleGuildMemberRequest(request2.requestId, true);
1319
+ await updateStats(ctx, groupId, "manuallyApproved");
1318
1320
  approvedCount++;
1319
1321
  } catch (error) {
1320
1322
  logger.warn(`处理申请 ${request2.id} 时出错:`, error);
@@ -1342,6 +1344,7 @@ ${displayReminder}
1342
1344
  const reply = `已同意用户 ${displayName} 的加群申请`;
1343
1345
  session.bot.handleGuildMemberRequest(request2.requestId, true).catch((e) => logger.warn("自动同意失败", e));
1344
1346
  await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
1347
+ await updateStats(ctx, groupId, "manuallyApproved");
1345
1348
  return reply;
1346
1349
  } catch (error) {
1347
1350
  return `处理申请时出错: ${error.message}`;
@@ -1364,6 +1367,7 @@ ${displayReminder}
1364
1367
  const reply = `已同意用户 ${displayName} 的加群申请`;
1365
1368
  session.bot.handleGuildMemberRequest(request.requestId, true).catch((e) => logger.warn("自动同意失败", e));
1366
1369
  await ctx.database.remove("group_verification_pending", { groupId, userId });
1370
+ await updateStats(ctx, groupId, "manuallyApproved");
1367
1371
  return reply;
1368
1372
  } catch (error) {
1369
1373
  return `处理申请时出错: ${error.message}`;
@@ -1631,7 +1635,7 @@ ${displayReminder}
1631
1635
  await ctx.database.set("group_verification_pending", { id: p.id }, updates);
1632
1636
  }
1633
1637
  }
1634
- logger.info("旧版日期字段迁移完成");
1638
+ clog("info", "旧版日期字段迁移完成");
1635
1639
  } catch (e) {
1636
1640
  logger.warn("迁移旧日期字段时出错", e);
1637
1641
  }
@@ -1644,9 +1648,9 @@ ${displayReminder}
1644
1648
  rejected: 0,
1645
1649
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1646
1650
  });
1647
- logger.info("已创建总计统计行");
1651
+ clog("info", "已创建总计统计行");
1648
1652
  } else {
1649
- logger.info("总计统计行已存在");
1653
+ clog("info", "总计统计行已存在");
1650
1654
  }
1651
1655
  await syncTotalStats(ctx);
1652
1656
  });
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "bugs": {
9
9
  "url": "https://github.com/LHDyx/koishi-plugin-group-verification/issues"
10
10
  },
11
- "version": "1.0.35",
11
+ "version": "1.0.36",
12
12
  "main": "lib/index.js",
13
13
  "typings": "lib/index.d.ts",
14
14
  "files": [
package/src/index.ts CHANGED
@@ -1,10 +1,40 @@
1
- import { Context, Schema, Session } from 'koishi'
1
+ import { Context, Schema, Session } from 'koishi'
2
2
 
3
3
  export const name = 'group-verification'
4
4
 
5
5
  // 模块级日志器,测试时使用 console
6
6
  let logger: any = console
7
7
 
8
+ // 当前日志详细度(由 apply() 在插件激活时写入)
9
+ let pluginLogLevel: '详细' | '中等' | '简洁' = '中等'
10
+
11
+ /**
12
+ * 三档日志输出:
13
+ * 简洁 — 只输出 warn/error
14
+ * 中等 — 输出所有级别的简短消息(mediumMsg)
15
+ * 详细 — 输出详细消息(verboseMsg,缺省时使用 mediumMsg)
16
+ */
17
+ function clog(
18
+ lvl: 'debug' | 'info' | 'warn' | 'error',
19
+ mediumMsg: string,
20
+ verboseMsg?: string
21
+ ): void {
22
+ if (pluginLogLevel === '简洁') {
23
+ if (lvl === 'warn' || lvl === 'error') logger[lvl](mediumMsg)
24
+ } else if (pluginLogLevel === '详细') {
25
+ logger[lvl](verboseMsg ?? mediumMsg)
26
+ } else {
27
+ // 中等模式: debug 级别提升为 info 输出,避免被 Koishi 框架过滤
28
+ const outputLvl = lvl === 'debug' ? 'info' : lvl
29
+ logger[outputLvl](mediumMsg)
30
+ }
31
+ }
32
+
33
+ /** 仅在 详细 模式下输出 debug 日志(支持传入对象,与 Koishi logger.debug(msg, obj) 一致) */
34
+ function clogV(msg: string, ...args: any[]): void {
35
+ if (pluginLogLevel === '详细') logger.debug(msg, ...args)
36
+ }
37
+
8
38
  // 数据库模型定义
9
39
  declare module 'koishi' {
10
40
  interface Tables {
@@ -65,7 +95,7 @@ export interface PendingVerification {
65
95
  export interface Config {
66
96
  defaultReminderMessage?: string
67
97
  enableStrictGroupCheck?: boolean // 群号合法性检查配置
68
- logLevel?: 'debug' | 'info' | 'warn' | 'error' // 日志等级配置
98
+ logLevel?: 'debug' | 'info' | 'warn' // 日志详细程度
69
99
  // 以下为可自定义的命令反馈提示词,可在插件管理界面调整
70
100
  permissionDeniedMessage?: string
71
101
  invalidGroupMessage?: string
@@ -83,7 +113,7 @@ export const Config: Schema<Config> = Schema.object({
83
113
  .description('默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)')
84
114
  .default('{user}({id}) 申请入群\\n申请理由: {question}\\n匹配情况: {answer}/{threshold}\\n使用 gva 同意或 gvr 拒绝申请'),
85
115
  enableStrictGroupCheck: Schema.boolean().description('是否启用严格的群号检查(检查群号长度)').default(false),
86
- logLevel: Schema.union(['debug', 'info', 'warn', 'error']).description('日志级别').default('info'),
116
+ logLevel: Schema.union(['debug', 'info', 'warn']).description('日志详细程度').default('info'), // debug=详细, info=中等, warn=简洁
87
117
  permissionDeniedMessage: Schema.string().description('权限不足时返回给调用者的提示').default('权限不足: 需要群主/管理员权限或koishi三级以上权限'),
88
118
  invalidGroupMessage: Schema.string().description('无效群号或机器人未在该群时的提示').default('群号 {group} 格式不合法或机器人不在该群中'),
89
119
  parameterConflictMessage: Schema.string().description('参数冲突时提示').default('参数冲突: -? 或 -r 不能与其他参数或关键词一起使用(仅可搭配 -i)'),
@@ -307,16 +337,16 @@ export function mergeReminder(
307
337
  // 优先级: disable > bare enable > new message content
308
338
  if (hasRealDisableMessageParam) {
309
339
  reminderEnabled = false;
310
- logger.debug('禁用提醒消息功能 (保留现有内容)');
340
+ clog('debug', '禁用提醒消息功能 (保留现有内容)');
311
341
  } else if (hasRealEnableMessageParam) {
312
342
  reminderEnabled = true;
313
- logger.debug(`启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
343
+ clog('debug', `启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
314
344
  } else if (hasRealMessageParam) {
315
345
  reminderEnabled = true;
316
346
  if (cleanedOptions.message !== undefined) {
317
347
  // 直接存储原始内容(\n 作为换行占位符,渲染时再转换)
318
348
  reminderMessage = cleanedOptions.message;
319
- logger.debug(`设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
349
+ clog('debug', `设置自定义提醒消息: ${reminderMessage.substring(0, 50)}...`);
320
350
  }
321
351
  }
322
352
  return { reminderEnabled, reminderMessage };
@@ -356,7 +386,7 @@ export async function syncTotalStats(ctx: Context) {
356
386
  lastUpdated: new Date().toISOString()
357
387
  })
358
388
 
359
- logger.debug(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`)
389
+ clog('debug', `总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`)
360
390
  }
361
391
  } catch (error) {
362
392
  logger.error('同步总计统计时出错:', error)
@@ -478,50 +508,50 @@ export async function checkPermission(session: any, targetGroupId?: string): Pro
478
508
  return [false, '请在群聊中使用此命令或使用 -i 参数指定群号']
479
509
  }
480
510
 
481
- logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
482
511
  const koishiAuthority = session.author?.authority || session.user?.authority
483
- logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
484
-
512
+ clogV(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
513
+ clogV(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
485
514
  if (!session.author) {
486
- logger.debug(`权限检查 - session中可能的权限字段:`, {
515
+ clogV(`权限检查 - session中可能的权限字段:`, {
487
516
  authority: session.authority,
488
517
  permission: session.permission,
489
518
  role: session.role
490
519
  })
491
520
  } else {
492
- logger.debug(`权限检查 - author对象中的字段:`, {
521
+ clogV(`权限检查 - author对象中的字段:`, {
493
522
  permission: session.author.permission,
494
523
  role: session.author.role,
495
524
  permissions: session.author.permissions
496
525
  })
497
526
  }
498
-
499
527
  if (session.user) {
500
- logger.debug(`权限检查 - user对象中的权限信息:`, {
528
+ clogV(`权限检查 - user对象中的权限信息:`, {
501
529
  authority: session.user.authority,
502
530
  permission: session.user.permission,
503
531
  role: session.user.role
504
532
  })
505
533
  }
506
-
507
534
  if (koishiAuthority && koishiAuthority >= 3) {
508
- logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
535
+ clogV(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
536
+ clog('debug', `权限通过(koishi): uid=${session.userId} authority=${koishiAuthority}`)
509
537
  return [true]
510
538
  }
511
539
 
512
540
  try {
513
541
  const member = await session.bot.getGuildMember(groupId, session.userId)
514
- logger.debug(`权限检查 - 获取到成员信息:`, {
542
+ clogV(`权限检查 - 获取到成员信息:`, {
515
543
  roles: member?.roles,
516
544
  permissions: member?.permissions
517
545
  })
518
546
  if (member) {
519
547
  if (member.permissions?.includes('OWNER') || member.roles?.includes('owner')) {
520
- logger.debug(`权限检查 - 用户是群主`)
548
+ clogV(`权限检查 - 用户是群主`)
549
+ clog('debug', `权限通过(群主): uid=${session.userId}`)
521
550
  return [true]
522
551
  }
523
552
  if (member.roles?.includes('admin') || member.permissions?.includes('ADMINISTRATOR')) {
524
- logger.debug(`权限检查 - 用户是管理员`)
553
+ clogV(`权限检查 - 用户是管理员`)
554
+ clog('debug', `权限通过(管理员): uid=${session.userId}`)
525
555
  return [true]
526
556
  }
527
557
  }
@@ -534,9 +564,10 @@ export async function checkPermission(session: any, targetGroupId?: string): Pro
534
564
 
535
565
  // 提供给测试的辅助函数: 处理 guild-member-request 事件的逻辑
536
566
  export async function handleGuildMemberRequestEvent(ctx: Context, session: any) {
537
- logger.debug('guild-member-request event', session)
567
+ clogV('guild-member-request event', session)
538
568
  let guildId = (session.guildId || session.channelId || '').toString().trim();
539
569
  const userId = session.userId;
570
+ clog('debug', `guild-member-request: guild=${guildId} user=${userId}`);
540
571
  const message = session.content || '';
541
572
 
542
573
  if (!guildId) {
@@ -550,7 +581,7 @@ export async function handleGuildMemberRequestEvent(ctx: Context, session: any)
550
581
  const config = groupConfig[0];
551
582
 
552
583
  if (config.reviewMethod === 3) {
553
- logger.info(`配置要求全部拒绝,自动拒绝用户 ${userId}`);
584
+ clog('info', `配置要求全部拒绝,自动拒绝用户 ${userId}`);
554
585
  if (requestId) {
555
586
  try { await session.bot.handleGuildMemberRequest(requestId, false); } catch (e) { logger.warn('自动拒绝失败', e); }
556
587
  }
@@ -562,7 +593,7 @@ export async function handleGuildMemberRequestEvent(ctx: Context, session: any)
562
593
  try {
563
594
  const blacklisted = await isUserBlacklisted(ctx, guildId, userId);
564
595
  if (blacklisted) {
565
- logger.info(`用户 ${userId} 在群 ${guildId} 或全局黑名单中,自动拒绝申请`);
596
+ clog('info', `用户 ${userId} 在群 ${guildId} 或全局黑名单中,自动拒绝申请`);
566
597
  if (requestId) {
567
598
  try { await session.bot.handleGuildMemberRequest(requestId, false); } catch (e) { logger.warn('自动拒绝失败', e); }
568
599
  }
@@ -574,13 +605,15 @@ export async function handleGuildMemberRequestEvent(ctx: Context, session: any)
574
605
  }
575
606
 
576
607
  const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
577
- logger.debug(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
608
+ clogV(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
578
609
 
579
610
  if (isValid) {
580
611
  if (requestId) {
581
612
  try {
582
613
  await session.bot.handleGuildMemberRequest(requestId, true);
583
- logger.debug(`自动同意 requestId=${requestId}`);
614
+ clogV(`自动同意 requestId=${requestId}`);
615
+ // 自动批准成功,立即记录统计(不依赖 guild-member-added 事件)
616
+ await updateStats(ctx, guildId, 'autoApproved');
584
617
  if (!autoQueue.has(guildId)) autoQueue.set(guildId, new Set());
585
618
  autoQueue.get(guildId)!.add(userId);
586
619
  } catch (e) {
@@ -750,7 +783,8 @@ export async function verifyApplication(config: GroupVerificationConfig, message
750
783
  requiredThreshold = 'null'
751
784
  }
752
785
 
753
- logger.debug(`verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`)
786
+ clog('debug', `verify: matched=${matchedCount}/${requiredThreshold} valid=${isValid}`,
787
+ `verifyApplication msg="${message}" keywords=${JSON.stringify(config.keywords)} matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`)
754
788
  return { isValid, matchedCount, requiredThreshold }
755
789
  }
756
790
 
@@ -771,7 +805,8 @@ export async function handleFailedVerification(
771
805
  }
772
806
  const username = session.username || '未知用户'
773
807
  const message = session.content || ''
774
- logger.debug(`处理失败验证 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold}`)
808
+ clog('debug', `待审核: guild=${guildId} user=${userId} matched=${matchedCount}/${requiredThreshold}`,
809
+ `处理失败验证 guild=${guildId} user=${userId} msg="${session.content || ''}" matched=${matchedCount} threshold=${requiredThreshold}`)
775
810
  // 如果未传入匹配信息,则重新计算一次(老调用)
776
811
  if (matchedCount === undefined || requiredThreshold === undefined) {
777
812
  const result = await verifyApplication(config, message, session)
@@ -805,7 +840,7 @@ export async function handleFailedVerification(
805
840
  })
806
841
  // 如果提醒消息被禁用,直接返回
807
842
  if (!config.reminderEnabled || !config.reminderMessage || config.reminderMessage === '') {
808
- logger.debug(`群 ${guildId} 的提醒消息已被禁用,跳过发送`)
843
+ clog('debug', `群 ${guildId} 的提醒消息已被禁用,跳过发送`)
809
844
  return
810
845
  }
811
846
 
@@ -829,7 +864,7 @@ export async function handleFailedVerification(
829
864
  const rawChannel = (session.channelId || '').toString().trim()
830
865
  const channel = rawChannel || guildId
831
866
  const target: string | [string, string] = rawChannel ? [channel, guildId] : guildId
832
- logger.debug('broadcast target', { channel, guildId, target })
867
+ clog('debug', `broadcast target: channel=${channel} guild=${guildId}`)
833
868
  // 优先使用 bot.broadcast,ctx.broadcast 可能不支持元组格式
834
869
  if (session.bot && typeof session.bot.broadcast === 'function') {
835
870
  try {
@@ -840,14 +875,14 @@ export async function handleFailedVerification(
840
875
  if (typeof (ctx.broadcast) === 'function') {
841
876
  await (ctx.broadcast as any)([target], reminderMsg)
842
877
  } else {
843
- logger.info('ctx.broadcast 不可用,跳过发送')
878
+ clog('info', 'ctx.broadcast 不可用,跳过发送')
844
879
  }
845
880
  }
846
881
  } else {
847
882
  if (typeof (ctx.broadcast) === 'function') {
848
883
  await (ctx.broadcast as any)([target], reminderMsg)
849
884
  } else {
850
- logger.info('ctx.broadcast 不可用,跳过发送')
885
+ clog('info', 'ctx.broadcast 不可用,跳过发送')
851
886
  }
852
887
  }
853
888
  }
@@ -948,7 +983,7 @@ export async function processBlacklistCommand(ctx: Context, session: any, rawArg
948
983
  if (session.bot && typeof session.bot.kickGuildMember === 'function') {
949
984
  try {
950
985
  await session.bot.kickGuildMember(group, targetUser)
951
- logger.info(`已将黑名单用户 ${targetUser} 从群 ${group} 踢出`)
986
+ clog('info', `已将黑名单用户 ${targetUser} 从群 ${group} 踢出`)
952
987
  } catch (e) {
953
988
  logger.warn(`踢出用户 ${targetUser} 失败`, e)
954
989
  }
@@ -1108,17 +1143,16 @@ export function apply(ctx: Context, config: Config) {
1108
1143
 
1109
1144
  // 获取logger实例并保存到模块级变量
1110
1145
  logger = ctx.logger('group-verification')
1111
- // 根据配置调整日志等级
1112
- if (config.logLevel) logger.level = config.logLevel
1113
-
1114
- // 设置日志级别
1115
- // 注意: Koishi logger的level设置可能需要不同的方式
1146
+ // 根据配置设置日志详细度(config 理论上已通过 Schema 校验,此处加防护)
1147
+ pluginLogLevel = ({ debug: '详细', info: '中等', warn: '简洁' }[config?.logLevel ?? 'info'] ?? '中等') as '详细' | '中等' | '简洁'
1148
+ // debug 模式需要把 Koishi logger 的输出阈值提升到 3(debug),否则 [D] 被框架过滤
1149
+ if (pluginLogLevel === '详细') logger.level = 3
1116
1150
 
1117
1151
  // 记录插件启动信息
1118
- logger.info('群组验证插件已启动')
1119
- logger.info(`默认提醒消息: ${config.defaultReminderMessage}`)
1120
- logger.info(`严格群号检查: ${config.enableStrictGroupCheck ? '启用' : '禁用'}`)
1121
- logger.info(`日志级别: ${config.logLevel}`)
1152
+ clog('info', '群组验证插件已启动')
1153
+ clog('info', `默认提醒消息: ${config.defaultReminderMessage}`)
1154
+ clog('info', `严格群号检查: ${config.enableStrictGroupCheck ? '启用' : '禁用'}`)
1155
+ clog('info', `日志详细度: ${config.logLevel ?? '中等'}`)
1122
1156
 
1123
1157
  ctx.model.extend('group_verification_stats', {
1124
1158
  id: 'unsigned',
@@ -1166,7 +1200,7 @@ export function apply(ctx: Context, config: Config) {
1166
1200
 
1167
1201
  // 监听 guild-member-request 事件,以便对新申请执行自动审批或拒绝
1168
1202
  ctx.on('guild-member-request', async (session) => {
1169
- logger.debug('收到 guild-member-request 事件,转发给处理函数')
1203
+ clog('debug', '收到 guild-member-request 事件,转发给处理函数')
1170
1204
  await handleGuildMemberRequestEvent(ctx, session)
1171
1205
  })
1172
1206
 
@@ -1174,42 +1208,19 @@ export function apply(ctx: Context, config: Config) {
1174
1208
  ctx.on('guild-member-added', async (session: any) => {
1175
1209
  const groupId = session.guildId || ''
1176
1210
  const userId = session.userId || ''
1177
- if (!groupId || !userId) {
1178
- // 无效会话,忽略
1179
- return
1180
- }
1211
+ if (!groupId || !userId) return
1181
1212
 
1182
- // 无论什么情况只要检测到加入就累加总入群
1213
+ // 始终增加总入群计数(四列统计相互独立)
1183
1214
  await incrementTotal(ctx, groupId)
1184
1215
 
1185
- // 先检查 autoQueue
1186
- const set = autoQueue.get(groupId)
1187
- if (set && set.has(userId)) {
1188
- await updateStats(ctx, groupId, 'autoApproved')
1189
- set.delete(userId)
1190
- logger.info(`用户 ${userId} 通过机器人审批加入群 ${groupId}(autoQueue),统计已更新`)
1191
- return
1216
+ // 清理 autoQueue 中的缓存(统计已在自动批准时记录)
1217
+ const autoSet = autoQueue.get(groupId)
1218
+ if (autoSet && autoSet.has(userId)) {
1219
+ autoSet.delete(userId)
1192
1220
  }
1193
1221
 
1194
- // 检查是否有待审核记录
1195
- const pendingRecords = await ctx.database.get('group_verification_pending', {
1196
- groupId: groupId,
1197
- userId: userId
1198
- })
1199
-
1200
- if (pendingRecords.length > 0) {
1201
- // 通过验证的用户入群,更新统计
1202
- await updateStats(ctx, groupId, 'autoApproved')
1203
- // 清除所有该用户的待审核记录
1204
- for (const rec of pendingRecords) {
1205
- await ctx.database.remove('group_verification_pending', { id: rec.id })
1206
- }
1207
- logger.info(`用户 ${userId} 通过验证加入群 ${groupId},已清理 ${pendingRecords.length} 条待审核记录,统计已更新`)
1208
- } else {
1209
- // 手动邀请入群,记录到手动批准统计
1210
- await updateStats(ctx, groupId, 'manuallyApproved')
1211
- logger.info(`用户 ${userId} 被手动邀请加入群 ${groupId},手动批准统计已更新`)
1212
- }
1222
+ // 清理残留的待审核记录(QQ 群管页面直接放行时 pending 记录不会被 gva 删除)
1223
+ await ctx.database.remove('group_verification_pending', { groupId, userId })
1213
1224
  })
1214
1225
 
1215
1226
 
@@ -1225,62 +1236,55 @@ export function apply(ctx: Context, config: Config) {
1225
1236
  return [false, config.invalidGroupMessage || '请在群聊中使用此命令或使用 -i 参数指定群号']
1226
1237
  }
1227
1238
 
1228
- // 使用Koishi logger输出调试信息
1229
- logger.debug(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
1230
-
1231
1239
  // 检查koishi权限等级(最高优先级)
1232
1240
  const koishiAuthority = session.author?.authority || session.user?.authority
1233
- logger.debug(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
1234
-
1235
- // 尝试其他可能的权限字段
1241
+ clogV(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
1242
+ clogV(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
1236
1243
  if (!session.author) {
1237
- logger.debug(`权限检查 - session中可能的权限字段:`, {
1244
+ clogV(`权限检查 - session中可能的权限字段:`, {
1238
1245
  authority: session.authority,
1239
1246
  permission: session.permission,
1240
1247
  role: session.role
1241
1248
  })
1242
1249
  } else {
1243
- // 检查author对象中的其他权限字段
1244
- logger.debug(`权限检查 - author对象中的字段:`, {
1250
+ clogV(`权限检查 - author对象中的字段:`, {
1245
1251
  authority: session.author.authority,
1246
1252
  permission: session.author.permission,
1247
1253
  role: session.author.role,
1248
1254
  permissions: session.author.permissions
1249
1255
  })
1250
1256
  }
1251
-
1252
- // 尝试从user对象获取权限信息
1253
1257
  if (session.user) {
1254
- logger.debug(`权限检查 - user对象中的权限信息:`, {
1258
+ clogV(`权限检查 - user对象中的权限信息:`, {
1255
1259
  authority: session.user.authority,
1256
1260
  permission: session.user.permission,
1257
1261
  role: session.user.role
1258
1262
  })
1259
1263
  }
1260
-
1261
- // 先检查koishi权限等级(最高优先级)
1262
1264
  if (koishiAuthority && koishiAuthority >= 3) {
1263
- logger.debug(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
1265
+ clogV(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
1266
+ clog('debug', `权限通过(koishi): uid=${session.userId} authority=${koishiAuthority}`)
1264
1267
  return [true]
1265
1268
  }
1266
1269
 
1267
1270
  // 再检查是否为群主或管理员(次优先级)
1268
1271
  try {
1269
1272
  const member = await session.bot.getGuildMember(groupId, session.userId)
1270
- logger.debug(`权限检查 - 获取到成员信息:`, {
1273
+ clogV(`权限检查 - 获取到成员信息:`, {
1271
1274
  roles: member?.roles,
1272
1275
  permissions: member?.permissions
1273
1276
  })
1274
-
1275
1277
  if (member) {
1276
1278
  // 检查群主权限
1277
1279
  if (member.permissions?.includes('OWNER') || member.roles?.includes('owner')) {
1278
- logger.debug(`权限检查 - 用户是群主`)
1280
+ clogV(`权限检查 - 用户是群主`)
1281
+ clog('debug', `权限通过(群主): uid=${session.userId}`)
1279
1282
  return [true]
1280
1283
  }
1281
1284
  // 检查管理员权限
1282
1285
  if (member.roles?.includes('admin') || member.permissions?.includes('ADMINISTRATOR')) {
1283
- logger.debug(`权限检查 - 用户是管理员`)
1286
+ clogV(`权限检查 - 用户是管理员`)
1287
+ clog('debug', `权限通过(管理员): uid=${session.userId}`)
1284
1288
  return [true]
1285
1289
  }
1286
1290
  }
@@ -1289,9 +1293,8 @@ export function apply(ctx: Context, config: Config) {
1289
1293
  return [false, `无法获取群 ${groupId} 的成员信息,请确认机器人已在该群中`]
1290
1294
  }
1291
1295
 
1292
- logger.debug(`权限检查 - 权限不足`)
1293
- const debugInfo = `调试信息 - 用户ID:${session.userId}, 群号:${groupId}, 权限等级:${koishiAuthority || '未知'}`
1294
- return [false, (config.permissionDeniedMessage || '权限不足: 需要群主/管理员权限或koishi三级以上权限') + `\n${debugInfo}`]
1296
+ clogV(`权限检查 - 权限不足`)
1297
+ return [false, config.permissionDeniedMessage || '权限不足: 需要群主/管理员权限或koishi三级以上权限']
1295
1298
  }
1296
1299
 
1297
1300
  // 创建主命令及别名
@@ -1314,13 +1317,14 @@ export function apply(ctx: Context, config: Config) {
1314
1317
  .option('query', '-? 查询当前配置')
1315
1318
  .option('remove', '-r 删除配置')
1316
1319
  .action(async ({ session, options }: any, keywords: any) => {
1317
- // 详细调试: 记录所有输入信息
1318
- logger.debug(`=== 命令解析调试 ===`)
1319
- logger.debug(`session内容: guildId=${session.guildId}, userId=${session.userId}`)
1320
-
1320
+ // 详细模式复现 1.0.35 的三行解析调试块
1321
+ clogV(`=== 命令解析调试 ===`)
1322
+ clogV(`session内容: guildId=${session.guildId}, userId=${session.userId}`)
1321
1323
  // 重新计算原始参数字符串(去掉命令本身)
1322
1324
  const rawInput = session.content.split(/\s+/).slice(1).join(' ')
1323
- logger.debug(`原始命令参数: "${rawInput}"`)
1325
+ clogV(`原始命令参数: "${rawInput}"`)
1326
+ // 中等模式下输出简洁行
1327
+ if (pluginLogLevel !== '详细') clog('debug', `gvc: uid=${session.userId} guild=${session.guildId} args="${rawInput}"`)
1324
1328
 
1325
1329
  // 使用自定义解析函数提取关键词和 flags
1326
1330
  const parsed = parseConfigArgs(rawInput)
@@ -1331,7 +1335,7 @@ export function apply(ctx: Context, config: Config) {
1331
1335
  return parseError
1332
1336
  }
1333
1337
 
1334
- logger.debug(`解析结果 flags=${JSON.stringify(flags)}, keywords=[${parsedKeywords.join(', ')}]`)
1338
+ clog('debug', `解析结果 flags=${JSON.stringify(flags)}, keywords=[${parsedKeywords.join(', ')}]`)
1335
1339
 
1336
1340
  // 由 flags 和 Koishi options 合并最终选项
1337
1341
  const cleanedOptions = {
@@ -1344,7 +1348,8 @@ export function apply(ctx: Context, config: Config) {
1344
1348
  query: flags.query || options.query,
1345
1349
  remove: flags.remove || options.remove,
1346
1350
  }
1347
- logger.debug(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}`)
1351
+ clogV(`合并后options: ${JSON.stringify(cleanedOptions, null, 2)}
1352
+ `)
1348
1353
 
1349
1354
  // 检查 -? 和 -r 的独占性;允许与 -i 并存
1350
1355
  if ((cleanedOptions.query || cleanedOptions.remove) &&
@@ -1417,7 +1422,7 @@ export function apply(ctx: Context, config: Config) {
1417
1422
  const existingConfig = await ctx.database.get('group_verification_config', { groupId: targetGroupId })
1418
1423
  if (existingConfig.length > 0) {
1419
1424
  await ctx.database.remove('group_verification_config', { id: existingConfig[0].id })
1420
- logger.info(`配置删除 - 用户ID:${session.userId}, 群号:${targetGroupId}`)
1425
+ clog('info', `配置删除 - 用户ID:${session.userId}, 群号:${targetGroupId}`)
1421
1426
  return `已删除群 ${targetGroupId} 的验证配置`
1422
1427
  } else {
1423
1428
  return `群 ${targetGroupId} 无验证配置`
@@ -1481,8 +1486,7 @@ export function apply(ctx: Context, config: Config) {
1481
1486
 
1482
1487
  // 关键词列表由之前解析得到的 parsedKeywords 构成
1483
1488
  let keywordList: string[] = parsedKeywords.slice()
1484
-
1485
- logger.debug(`关键词解析结果: [${keywordList.join(', ')}] - 原始输入: "${rawInput}"`)
1489
+ clogV(`关键词解析结果: [${keywordList.join(', ')}] - 原始输入: "${rawInput}"`)
1486
1490
 
1487
1491
  // 如果没有关键词且不是查询/删除操作,则根据现有配置或报错
1488
1492
  if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
@@ -1536,7 +1540,7 @@ export function apply(ctx: Context, config: Config) {
1536
1540
  existingConfig.reviewParameters === null ||
1537
1541
  isNaN(existingConfig.reviewParameters)) {
1538
1542
  reviewParameters = 0 // 默认值
1539
- logger.debug(`检测到老版本数据或无效值,使用默认阈值: 0`)
1543
+ clog('debug', `检测到老版本数据或无效值,使用默认阈值: 0`)
1540
1544
  } else {
1541
1545
  reviewParameters = existingConfig.reviewParameters
1542
1546
  }
@@ -1550,10 +1554,10 @@ export function apply(ctx: Context, config: Config) {
1550
1554
  }
1551
1555
  const oldMethod = reviewMethod
1552
1556
  reviewMethod = methodNum as 0 | 1 | 2 | 3
1553
- logger.debug(`审核方式明确指定为: ${reviewMethod}`)
1557
+ clog('debug', `审核方式明确指定为: ${reviewMethod}`)
1554
1558
  var methodChanged = oldMethod !== reviewMethod
1555
1559
  } else {
1556
- logger.debug(`未指定审核方式,保持原有值: ${reviewMethod}`)
1560
+ clog('debug', `未指定审核方式,保持原有值: ${reviewMethod}`)
1557
1561
  var methodChanged = false
1558
1562
  }
1559
1563
 
@@ -1577,7 +1581,7 @@ export function apply(ctx: Context, config: Config) {
1577
1581
  updatedBy: session.username || session.userId,
1578
1582
  updatedAt: new Date().toISOString()
1579
1583
  })
1580
- logger.debug(`自动调整并更新数据库阈值为: ${reviewParameters}`)
1584
+ clog('debug', `自动调整并更新数据库阈值为: ${reviewParameters}`)
1581
1585
  }
1582
1586
 
1583
1587
  // 自动调整说明,用于反馈消息
@@ -1599,8 +1603,6 @@ export function apply(ctx: Context, config: Config) {
1599
1603
  return keyword.replace(/,/g, '[[COMMA]]')
1600
1604
  })
1601
1605
 
1602
- logger.debug(`编码后准备存储的关键词: ${JSON.stringify(encodedKeywords)}`)
1603
-
1604
1606
  // 保存配置到数据库(使用新的简单格式)
1605
1607
  const dbData = {
1606
1608
  keywords: encodedKeywords,
@@ -1618,7 +1620,7 @@ export function apply(ctx: Context, config: Config) {
1618
1620
  ...dbData,
1619
1621
  updatedAt: new Date().toISOString(),
1620
1622
  })
1621
- logger.info(`更新配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`)
1623
+ clog('info', `更新配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`)
1622
1624
  } else {
1623
1625
  await ctx.database.create('group_verification_config', {
1624
1626
  groupId: targetGroupId,
@@ -1626,7 +1628,7 @@ export function apply(ctx: Context, config: Config) {
1626
1628
  createdBy: session.username || session.userId,
1627
1629
  createdAt: new Date().toISOString()
1628
1630
  })
1629
- logger.info(`创建配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`)
1631
+ clog('info', `创建配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`)
1630
1632
  }
1631
1633
 
1632
1634
  // 读取时解码关键词(修复更新显示中的关键词显示问题)
@@ -1657,25 +1659,7 @@ export function apply(ctx: Context, config: Config) {
1657
1659
  }
1658
1660
 
1659
1661
 
1660
- // 同时更新数据库存储时也要确保正确格式
1661
- logger.debug(`准备存储到数据库的关键词: ${JSON.stringify(encodedKeywords)}`)
1662
-
1663
- // 添加详细的处理日志
1664
- logger.debug(`=== 配置处理详情 ===`)
1665
- logger.debug(`原始输入: ${keywords || '无关键词'}`)
1666
- logger.debug(`审核方式: ${reviewMethod} (${['全部同意','按数量','按比例','全部拒绝'][reviewMethod]})`)
1667
- logger.debug(`阈值参数: ${JSON.stringify(reviewParameters)}`)
1668
- logger.debug(`关键词列表: [${keywordList.map(k => `"${k}"`).join(', ')}]`)
1669
- logger.debug(`现有配置: ${existingConfig ? '存在' : '不存在'}`)
1670
- if (existingConfig) {
1671
- logger.debug(`原审核方式: ${existingConfig.reviewMethod}`)
1672
- logger.debug(`原阈值: ${JSON.stringify(existingConfig.reviewParameters)}`)
1673
- logger.debug(`原关键词数: ${existingConfig.keywords.length}`)
1674
- logger.debug(`新关键词数: ${keywordList.length}`)
1675
- }
1676
- logger.debug(`==================`)
1677
-
1678
- logger.info(feedbackMessage.replace(/\n/g, '; '))
1662
+ clog('info', feedbackMessage.replace(/\n/g, '; '))
1679
1663
  return feedbackMessage
1680
1664
  })
1681
1665
 
@@ -1719,6 +1703,7 @@ export function apply(ctx: Context, config: Config) {
1719
1703
  if (request.requestId) {
1720
1704
  try {
1721
1705
  await session.bot.handleGuildMemberRequest(request.requestId, true)
1706
+ await updateStats(ctx, groupId, 'manuallyApproved')
1722
1707
  approvedCount++
1723
1708
  } catch (error: any) {
1724
1709
  logger.warn(`处理申请 ${request.id} 时出错:`, error)
@@ -1750,6 +1735,7 @@ export function apply(ctx: Context, config: Config) {
1750
1735
  session.bot.handleGuildMemberRequest(request.requestId, true).catch((e:any) => logger.warn('自动同意失败', e))
1751
1736
  // 清除该用户的所有待审核记录(异步也可以,顺序无关)
1752
1737
  await ctx.database.remove('group_verification_pending', { groupId, userId: request.userId })
1738
+ await updateStats(ctx, groupId, 'manuallyApproved')
1753
1739
  return reply
1754
1740
  } catch (error: any) {
1755
1741
  return `处理申请时出错: ${error.message}`
@@ -1776,6 +1762,7 @@ export function apply(ctx: Context, config: Config) {
1776
1762
  const reply = `已同意用户 ${displayName} 的加群申请`
1777
1763
  session.bot.handleGuildMemberRequest(request.requestId, true).catch((e:any) => logger.warn('自动同意失败', e))
1778
1764
  await ctx.database.remove('group_verification_pending', { groupId, userId })
1765
+ await updateStats(ctx, groupId, 'manuallyApproved')
1779
1766
  return reply
1780
1767
  } catch (error: any) {
1781
1768
  return `处理申请时出错: ${error.message}`
@@ -2090,7 +2077,7 @@ export function apply(ctx: Context, config: Config) {
2090
2077
  await ctx.database.set('group_verification_pending', { id: p.id }, updates)
2091
2078
  }
2092
2079
  }
2093
- logger.info('旧版日期字段迁移完成')
2080
+ clog('info', '旧版日期字段迁移完成')
2094
2081
  } catch (e) {
2095
2082
  logger.warn('迁移旧日期字段时出错', e)
2096
2083
  }
@@ -2107,9 +2094,9 @@ export function apply(ctx: Context, config: Config) {
2107
2094
  rejected: 0,
2108
2095
  lastUpdated: new Date().toISOString()
2109
2096
  })
2110
- logger.info('已创建总计统计行')
2097
+ clog('info', '已创建总计统计行')
2111
2098
  } else {
2112
- logger.info('总计统计行已存在')
2099
+ clog('info', '总计统计行已存在')
2113
2100
  }
2114
2101
 
2115
2102
  // 同步现有统计数据到总计行