koishi-plugin-group-verification 1.0.34 → 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 +1 -1
- package/lib/index.js +117 -111
- package/package.json +3 -3
- package/readme.md +2 -2
- package/src/index.ts +2142 -2146
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'
|
|
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
|
-
defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板(使用 \\n 表示换行,可包含下方变量)").default("{user}({id})
|
|
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"
|
|
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)"),
|
|
@@ -194,22 +211,22 @@ function usageString() {
|
|
|
194
211
|
__name(usageString, "usageString");
|
|
195
212
|
function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger2, defaultMessage) {
|
|
196
213
|
let reminderEnabled = true;
|
|
197
|
-
let reminderMessage = defaultMessage || "{user}({id})
|
|
214
|
+
let reminderMessage = defaultMessage || "{user}({id}) 申请入群\\n申请理由: {question}\\n匹配情况: {answer}/{threshold}\\n使用 gva 同意或 gvr 拒绝申请";
|
|
198
215
|
if (existingConfig) {
|
|
199
216
|
reminderEnabled = existingConfig.reminderEnabled;
|
|
200
217
|
reminderMessage = existingConfig.reminderMessage || reminderMessage;
|
|
201
218
|
}
|
|
202
219
|
if (hasRealDisableMessageParam) {
|
|
203
220
|
reminderEnabled = false;
|
|
204
|
-
|
|
221
|
+
clog("debug", "禁用提醒消息功能 (保留现有内容)");
|
|
205
222
|
} else if (hasRealEnableMessageParam) {
|
|
206
223
|
reminderEnabled = true;
|
|
207
|
-
|
|
224
|
+
clog("debug", `启用提醒消息(保留原消息): ${reminderMessage.substring(0, 50)}...`);
|
|
208
225
|
} else if (hasRealMessageParam) {
|
|
209
226
|
reminderEnabled = true;
|
|
210
227
|
if (cleanedOptions.message !== void 0) {
|
|
211
|
-
reminderMessage = cleanedOptions.message
|
|
212
|
-
|
|
228
|
+
reminderMessage = cleanedOptions.message;
|
|
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
|
-
|
|
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
|
-
|
|
351
|
+
clogV(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
|
|
352
|
+
clogV(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
|
|
336
353
|
if (!session.author) {
|
|
337
|
-
|
|
354
|
+
clogV(`权限检查 - session中可能的权限字段:`, {
|
|
338
355
|
authority: session.authority,
|
|
339
356
|
permission: session.permission,
|
|
340
357
|
role: session.role
|
|
341
358
|
});
|
|
342
359
|
} else {
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -461,7 +483,7 @@ function parseConfigArgs(raw) {
|
|
|
461
483
|
}
|
|
462
484
|
}
|
|
463
485
|
tokens = tokens.map(
|
|
464
|
-
(t) => t.replace(new RegExp(ESC_QUOTE, "g"), '"').replace(new RegExp(ESC_BACKSLASH, "g"), "
|
|
486
|
+
(t) => t.replace(new RegExp(ESC_QUOTE, "g"), '"').replace(new RegExp(ESC_BACKSLASH, "g"), "\\\\")
|
|
465
487
|
);
|
|
466
488
|
for (let i = 0; i < tokens.length; i++) {
|
|
467
489
|
const tok = tokens[i];
|
|
@@ -567,7 +589,11 @@ async function verifyApplication(config, message, session) {
|
|
|
567
589
|
isValid = false;
|
|
568
590
|
requiredThreshold = "null";
|
|
569
591
|
}
|
|
570
|
-
|
|
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
|
-
|
|
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,31 +633,31 @@ 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
|
-
|
|
636
|
+
clog("debug", `群 ${guildId} 的提醒消息已被禁用,跳过发送`);
|
|
607
637
|
return;
|
|
608
638
|
}
|
|
609
|
-
let reminderMsg = config.reminderMessage;
|
|
639
|
+
let reminderMsg = config.reminderMessage.replace(/\\\\n/g, "").replace(/\\n/g, "\n").replace(/\x01/g, "\\n");
|
|
610
640
|
reminderMsg = reminderMsg.replace(/{user}/g, username).replace(/{id}/g, userId).replace(/{group}/g, guildId).replace(/{gname}/g, groupName).replace(/{question}/g, message).replace(/{answer}/g, matchedCount.toString()).replace(/{threshold}/g, requiredThreshold);
|
|
611
641
|
const rawChannel = (session.channelId || "").toString().trim();
|
|
612
642
|
const channel = rawChannel || guildId;
|
|
613
643
|
const target = rawChannel ? [channel, guildId] : guildId;
|
|
614
|
-
|
|
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);
|
|
618
648
|
} catch (err) {
|
|
619
|
-
logger.warn("bot.broadcast
|
|
649
|
+
logger.warn("bot.broadcast 失败,回退到 ctx.broadcast", err);
|
|
620
650
|
if (typeof ctx.broadcast === "function") {
|
|
621
651
|
await ctx.broadcast([target], reminderMsg);
|
|
622
652
|
} else {
|
|
623
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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",
|
|
@@ -875,8 +906,8 @@ function apply(ctx, config) {
|
|
|
875
906
|
manuallyApproved: "integer",
|
|
876
907
|
rejected: "integer",
|
|
877
908
|
totalJoined: "integer",
|
|
878
|
-
//
|
|
879
|
-
// Koishi
|
|
909
|
+
// 以字符串(ISO 时间戳)格式存储,保留完整日期+时间;
|
|
910
|
+
// Koishi 的 date 类型会截断到天,导致时间显示为 00:00:00。
|
|
880
911
|
lastUpdated: "string"
|
|
881
912
|
}, {
|
|
882
913
|
primary: "id",
|
|
@@ -890,7 +921,7 @@ function apply(ctx, config) {
|
|
|
890
921
|
requestMessage: "string",
|
|
891
922
|
// 保存 OneBot 事件提供的原始 requestId;用于同意/拒绝操作
|
|
892
923
|
requestId: "string",
|
|
893
|
-
//
|
|
924
|
+
// 以字符串格式记录完整时间戳,保留时间分量
|
|
894
925
|
applyTime: "string"
|
|
895
926
|
}, {
|
|
896
927
|
primary: "id",
|
|
@@ -906,54 +937,36 @@ function apply(ctx, config) {
|
|
|
906
937
|
indexes: [["groupId"]]
|
|
907
938
|
});
|
|
908
939
|
ctx.on("guild-member-request", async (session) => {
|
|
909
|
-
|
|
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
|
|
920
|
-
if (
|
|
921
|
-
|
|
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
|
-
|
|
960
|
+
clogV(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
|
|
961
|
+
clogV(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
|
|
949
962
|
if (!session.author) {
|
|
950
|
-
|
|
963
|
+
clogV(`权限检查 - session中可能的权限字段:`, {
|
|
951
964
|
authority: session.authority,
|
|
952
965
|
permission: session.permission,
|
|
953
966
|
role: session.role
|
|
954
967
|
});
|
|
955
968
|
} else {
|
|
956
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
995
|
-
|
|
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
|
-
|
|
1011
|
-
|
|
1024
|
+
clogV(`=== 命令解析调试 ===`);
|
|
1025
|
+
clogV(`session内容: guildId=${session.guildId}, userId=${session.userId}`);
|
|
1012
1026
|
const rawInput = session.content.split(/\s+/).slice(1).join(" ");
|
|
1013
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1096
|
+
clog("info", `配置删除 - 用户ID:${session.userId}, 群号:${targetGroupId}`);
|
|
1081
1097
|
return `已删除群 ${targetGroupId} 的验证配置`;
|
|
1082
1098
|
} else {
|
|
1083
1099
|
return `群 ${targetGroupId} 无验证配置`;
|
|
@@ -1113,13 +1129,14 @@ ${debugInfo}`];
|
|
|
1113
1129
|
const createTime = new Date(config2.createdAt).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
1114
1130
|
const updateTime = new Date(config2.updatedAt).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
1115
1131
|
const reminderStatus = config2.reminderEnabled ? "启用" : "禁用";
|
|
1132
|
+
const displayMsg = (config2.reminderMessage || "无").replace(/\\\\n/g, "").replace(/\\n/g, "\n").replace(/\x01/g, "\\n");
|
|
1116
1133
|
return `群 ${targetGroupId} 配置:
|
|
1117
1134
|
关键词: ${decodedKeywords2.join(", ")}
|
|
1118
1135
|
审核方式: ${methodDesc}
|
|
1119
1136
|
阈值: ${thresholdInfo}
|
|
1120
1137
|
提醒消息: ${reminderStatus}
|
|
1121
1138
|
自定义消息:
|
|
1122
|
-
${
|
|
1139
|
+
${displayMsg}
|
|
1123
1140
|
创建时间: ${createTime}
|
|
1124
1141
|
更新时间: ${updateTime}
|
|
1125
1142
|
创建者: ${config2.createdBy}
|
|
@@ -1129,7 +1146,7 @@ ${config2.reminderMessage || "无"}
|
|
|
1129
1146
|
}
|
|
1130
1147
|
}
|
|
1131
1148
|
let keywordList = parsedKeywords.slice();
|
|
1132
|
-
|
|
1149
|
+
clogV(`关键词解析结果: [${keywordList.join(", ")}] - 原始输入: "${rawInput}"`);
|
|
1133
1150
|
if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
|
|
1134
1151
|
const hasConfigParams = cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || hasRealMessageParam || hasRealEnableMessageParam || hasRealDisableMessageParam;
|
|
1135
1152
|
if (!hasConfigParams) {
|
|
@@ -1162,7 +1179,7 @@ ${config2.reminderMessage || "无"}
|
|
|
1162
1179
|
reviewMethod = existingConfig.reviewMethod;
|
|
1163
1180
|
if (existingConfig.reviewParameters === void 0 || existingConfig.reviewParameters === null || isNaN(existingConfig.reviewParameters)) {
|
|
1164
1181
|
reviewParameters = 0;
|
|
1165
|
-
|
|
1182
|
+
clog("debug", `检测到老版本数据或无效值,使用默认阈值: 0`);
|
|
1166
1183
|
} else {
|
|
1167
1184
|
reviewParameters = existingConfig.reviewParameters;
|
|
1168
1185
|
}
|
|
@@ -1174,10 +1191,10 @@ ${config2.reminderMessage || "无"}
|
|
|
1174
1191
|
}
|
|
1175
1192
|
const oldMethod = reviewMethod;
|
|
1176
1193
|
reviewMethod = methodNum;
|
|
1177
|
-
|
|
1194
|
+
clog("debug", `审核方式明确指定为: ${reviewMethod}`);
|
|
1178
1195
|
var methodChanged = oldMethod !== reviewMethod;
|
|
1179
1196
|
} else {
|
|
1180
|
-
|
|
1197
|
+
clog("debug", `未指定审核方式,保持原有值: ${reviewMethod}`);
|
|
1181
1198
|
var methodChanged = false;
|
|
1182
1199
|
}
|
|
1183
1200
|
const thresholdResult = resolveThreshold(
|
|
@@ -1197,7 +1214,7 @@ ${config2.reminderMessage || "无"}
|
|
|
1197
1214
|
updatedBy: session.username || session.userId,
|
|
1198
1215
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1199
1216
|
});
|
|
1200
|
-
|
|
1217
|
+
clog("debug", `自动调整并更新数据库阈值为: ${reviewParameters}`);
|
|
1201
1218
|
}
|
|
1202
1219
|
let autoAdjustNote = "";
|
|
1203
1220
|
if (thresholdResult.autoInfo === "methodChange") {
|
|
@@ -1215,7 +1232,6 @@ ${config2.reminderMessage || "无"}
|
|
|
1215
1232
|
const encodedKeywords = keywordList.map((keyword) => {
|
|
1216
1233
|
return keyword.replace(/,/g, "[[COMMA]]");
|
|
1217
1234
|
});
|
|
1218
|
-
logger.info(`编码后准备存储的关键词: ${JSON.stringify(encodedKeywords)}`);
|
|
1219
1235
|
const dbData = {
|
|
1220
1236
|
keywords: encodedKeywords,
|
|
1221
1237
|
reviewMethod,
|
|
@@ -1231,7 +1247,7 @@ ${config2.reminderMessage || "无"}
|
|
|
1231
1247
|
...dbData,
|
|
1232
1248
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1233
1249
|
});
|
|
1234
|
-
|
|
1250
|
+
clog("info", `更新配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
|
|
1235
1251
|
} else {
|
|
1236
1252
|
await ctx.database.create("group_verification_config", {
|
|
1237
1253
|
groupId: targetGroupId,
|
|
@@ -1239,7 +1255,7 @@ ${config2.reminderMessage || "无"}
|
|
|
1239
1255
|
createdBy: session.username || session.userId,
|
|
1240
1256
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1241
1257
|
});
|
|
1242
|
-
|
|
1258
|
+
clog("info", `创建配置成功 - 审核方式: ${reviewMethod}, 阈值: ${reviewParameters}`);
|
|
1243
1259
|
}
|
|
1244
1260
|
const decodedKeywords = keywordList;
|
|
1245
1261
|
let feedbackMessage = `群 ${targetGroupId} 配置已更新:
|
|
@@ -1259,25 +1275,12 @@ ${config2.reminderMessage || "无"}
|
|
|
1259
1275
|
feedbackMessage += `提醒状态: ${reminderEnabled ? "启用" : "禁用"}
|
|
1260
1276
|
`;
|
|
1261
1277
|
if (reminderMessage && reminderEnabled) {
|
|
1278
|
+
const displayReminder = reminderMessage.replace(/\\\\n/g, "").replace(/\\n/g, "\n").replace(/\x01/g, "\\n");
|
|
1262
1279
|
feedbackMessage += `提醒消息:
|
|
1263
|
-
${
|
|
1280
|
+
${displayReminder}
|
|
1264
1281
|
`;
|
|
1265
1282
|
}
|
|
1266
|
-
|
|
1267
|
-
logger.info(`=== 配置处理详情 ===`);
|
|
1268
|
-
logger.info(`原始输入: ${keywords || "无关键词"}`);
|
|
1269
|
-
logger.info(`审核方式: ${reviewMethod} (${["全部同意", "按数量", "按比例", "全部拒绝"][reviewMethod]})`);
|
|
1270
|
-
logger.info(`阈值参数: ${JSON.stringify(reviewParameters)}`);
|
|
1271
|
-
logger.info(`关键词列表: [${keywordList.map((k) => `"${k}"`).join(", ")}]`);
|
|
1272
|
-
logger.info(`现有配置: ${existingConfig ? "存在" : "不存在"}`);
|
|
1273
|
-
if (existingConfig) {
|
|
1274
|
-
logger.info(`原审核方式: ${existingConfig.reviewMethod}`);
|
|
1275
|
-
logger.info(`原阈值: ${JSON.stringify(existingConfig.reviewParameters)}`);
|
|
1276
|
-
logger.info(`原关键词数: ${existingConfig.keywords.length}`);
|
|
1277
|
-
logger.info(`新关键词数: ${keywordList.length}`);
|
|
1278
|
-
}
|
|
1279
|
-
logger.info(`==================`);
|
|
1280
|
-
logger.info(feedbackMessage.replace(/\n/g, "; "));
|
|
1283
|
+
clog("info", feedbackMessage.replace(/\n/g, "; "));
|
|
1281
1284
|
return feedbackMessage;
|
|
1282
1285
|
});
|
|
1283
1286
|
groupVerify.subcommand(".approve [userId]", "同意加群申请").alias(
|
|
@@ -1313,6 +1316,7 @@ ${reminderMessage}
|
|
|
1313
1316
|
if (request2.requestId) {
|
|
1314
1317
|
try {
|
|
1315
1318
|
await session.bot.handleGuildMemberRequest(request2.requestId, true);
|
|
1319
|
+
await updateStats(ctx, groupId, "manuallyApproved");
|
|
1316
1320
|
approvedCount++;
|
|
1317
1321
|
} catch (error) {
|
|
1318
1322
|
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
@@ -1340,6 +1344,7 @@ ${reminderMessage}
|
|
|
1340
1344
|
const reply = `已同意用户 ${displayName} 的加群申请`;
|
|
1341
1345
|
session.bot.handleGuildMemberRequest(request2.requestId, true).catch((e) => logger.warn("自动同意失败", e));
|
|
1342
1346
|
await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
|
|
1347
|
+
await updateStats(ctx, groupId, "manuallyApproved");
|
|
1343
1348
|
return reply;
|
|
1344
1349
|
} catch (error) {
|
|
1345
1350
|
return `处理申请时出错: ${error.message}`;
|
|
@@ -1362,6 +1367,7 @@ ${reminderMessage}
|
|
|
1362
1367
|
const reply = `已同意用户 ${displayName} 的加群申请`;
|
|
1363
1368
|
session.bot.handleGuildMemberRequest(request.requestId, true).catch((e) => logger.warn("自动同意失败", e));
|
|
1364
1369
|
await ctx.database.remove("group_verification_pending", { groupId, userId });
|
|
1370
|
+
await updateStats(ctx, groupId, "manuallyApproved");
|
|
1365
1371
|
return reply;
|
|
1366
1372
|
} catch (error) {
|
|
1367
1373
|
return `处理申请时出错: ${error.message}`;
|
|
@@ -1629,7 +1635,7 @@ ${reminderMessage}
|
|
|
1629
1635
|
await ctx.database.set("group_verification_pending", { id: p.id }, updates);
|
|
1630
1636
|
}
|
|
1631
1637
|
}
|
|
1632
|
-
|
|
1638
|
+
clog("info", "旧版日期字段迁移完成");
|
|
1633
1639
|
} catch (e) {
|
|
1634
1640
|
logger.warn("迁移旧日期字段时出错", e);
|
|
1635
1641
|
}
|
|
@@ -1642,9 +1648,9 @@ ${reminderMessage}
|
|
|
1642
1648
|
rejected: 0,
|
|
1643
1649
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1644
1650
|
});
|
|
1645
|
-
|
|
1651
|
+
clog("info", "已创建总计统计行");
|
|
1646
1652
|
} else {
|
|
1647
|
-
|
|
1653
|
+
clog("info", "总计统计行已存在");
|
|
1648
1654
|
}
|
|
1649
1655
|
await syncTotalStats(ctx);
|
|
1650
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.
|
|
11
|
+
"version": "1.0.36",
|
|
12
12
|
"main": "lib/index.js",
|
|
13
13
|
"typings": "lib/index.d.ts",
|
|
14
14
|
"files": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"koishi": "^4.15.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"
|
|
48
|
-
"
|
|
47
|
+
"@types/node": "^16.0.0",
|
|
48
|
+
"typescript": "^4.9.0"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/readme.md
CHANGED
|
@@ -138,11 +138,11 @@ group-verify.blacklist i <用户ID> [群号|all]
|
|
|
138
138
|
|
|
139
139
|
插件启动时会输出运行状态,并根据 `logLevel` 调整输出量。
|
|
140
140
|
- `debug`:打印所有调试细节,包括权限检查、命令解析等。
|
|
141
|
-
- `info
|
|
141
|
+
- `info`:默认值,记录关键事件(插件启动、配置修改、自动拒绝、黑名单踢人等),包括成功踢人日志。
|
|
142
142
|
- `warn`:记录可恢复的异常,例如尝试踢出用户失败、数据库操作问题。
|
|
143
143
|
- `error`:仅在遇到严重错误时输出。
|
|
144
144
|
|
|
145
|
-
添加黑名单时会尝试在对应群踢出该用户,成功记为 `info`,失败记为 `warn
|
|
145
|
+
添加黑名单时会尝试在对应群踢出该用户,成功记为 `info`,失败记为 `warn`。权限检查的详细过程则只会在 `debug` 级别输出。
|
|
146
146
|
|
|
147
147
|
### 严格群号检查
|
|
148
148
|
|