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