koishi-plugin-group-verification 1.0.24 → 1.0.26
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 +6 -6
- package/lib/index.js +201 -100
- package/package.json +1 -1
- package/readme.md +3 -1
- package/src/index.ts +216 -121
package/lib/index.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ export interface ParsedArgs {
|
|
|
80
80
|
* 返回最终的 reminderEnabled 和 reminderMessage。
|
|
81
81
|
* 对于 -nomsg 不会清除已保存的 message,便于后续再次启用时恢复。
|
|
82
82
|
*/
|
|
83
|
+
export declare function usageString(): string;
|
|
83
84
|
export declare function mergeReminder(existingConfig: any | null, cleanedOptions: {
|
|
84
85
|
message?: string;
|
|
85
86
|
enableMessage?: boolean;
|
|
@@ -88,12 +89,11 @@ export declare function mergeReminder(existingConfig: any | null, cleanedOptions
|
|
|
88
89
|
reminderEnabled: boolean;
|
|
89
90
|
reminderMessage: string;
|
|
90
91
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
*/
|
|
92
|
+
export declare function syncTotalStats(ctx: Context): Promise<void>;
|
|
93
|
+
export declare function updateStats(ctx: Context, groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected'): Promise<void>;
|
|
94
|
+
export declare function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]>;
|
|
95
|
+
export declare function handleGuildMemberRequestEvent(ctx: Context, session: any): Promise<void>;
|
|
96
|
+
export declare function __getAutoQueue(): Map<string, Set<string>>;
|
|
97
97
|
export declare function parseConfigArgs(raw: string): ParsedArgs;
|
|
98
98
|
export declare function verifyApplication(config: GroupVerificationConfig, message: string, session: any): Promise<{
|
|
99
99
|
isValid: boolean;
|
package/lib/index.js
CHANGED
|
@@ -21,13 +21,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
Config: () => Config,
|
|
24
|
+
__getAutoQueue: () => __getAutoQueue,
|
|
24
25
|
apply: () => apply,
|
|
26
|
+
checkPermission: () => checkPermission,
|
|
25
27
|
handleFailedVerification: () => handleFailedVerification,
|
|
28
|
+
handleGuildMemberRequestEvent: () => handleGuildMemberRequestEvent,
|
|
26
29
|
inject: () => inject,
|
|
27
30
|
mergeReminder: () => mergeReminder,
|
|
28
31
|
name: () => name,
|
|
29
32
|
parseConfigArgs: () => parseConfigArgs,
|
|
33
|
+
syncTotalStats: () => syncTotalStats,
|
|
30
34
|
tokenize: () => tokenize,
|
|
35
|
+
updateStats: () => updateStats,
|
|
36
|
+
usageString: () => usageString,
|
|
31
37
|
verifyApplication: () => verifyApplication
|
|
32
38
|
});
|
|
33
39
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -35,7 +41,7 @@ var import_koishi = require("koishi");
|
|
|
35
41
|
var name = "group-verification";
|
|
36
42
|
var logger = console;
|
|
37
43
|
var Config = import_koishi.Schema.object({
|
|
38
|
-
defaultReminderMessage: import_koishi.Schema.string().description("
|
|
44
|
+
defaultReminderMessage: import_koishi.Schema.string().description("默认提醒消息模板(使用 \n 表示换行,可包含下方变量)").default("{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}\n使用 gva 同意或 gvr 拒绝申请"),
|
|
39
45
|
enableStrictGroupCheck: import_koishi.Schema.boolean().description("是否启用严格的群号检查(检查群号长度)").default(false),
|
|
40
46
|
logLevel: import_koishi.Schema.union(["debug", "info", "warn", "error"]).description("日志级别").default("info")
|
|
41
47
|
}).description("群组验证插件配置");
|
|
@@ -143,6 +149,31 @@ function validateKeywordFormat(raw) {
|
|
|
143
149
|
return true;
|
|
144
150
|
}
|
|
145
151
|
__name(validateKeywordFormat, "validateKeywordFormat");
|
|
152
|
+
function usageString() {
|
|
153
|
+
return `用法:
|
|
154
|
+
# 创建/修改配置
|
|
155
|
+
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
156
|
+
gvc -m 1 -t 2 # 修改审核参数
|
|
157
|
+
|
|
158
|
+
# 提醒消息控制(可用变量详见下方)
|
|
159
|
+
gvc -msg "消息内容" # 修改提醒消息
|
|
160
|
+
gvc -nomsg # 禁用提醒消息
|
|
161
|
+
# 查询/删除
|
|
162
|
+
gvc -? # 查询配置
|
|
163
|
+
gvc -r # 删除配置
|
|
164
|
+
|
|
165
|
+
审核方式说明(使用 -m 参数):
|
|
166
|
+
0 全部同意(默认)
|
|
167
|
+
1 按数量同意,需要 -t 指定数量
|
|
168
|
+
2 按比例同意,需要 -t 指定百分比
|
|
169
|
+
3 全部拒绝(拒绝后系统会自动阻止任何通过)
|
|
170
|
+
|
|
171
|
+
提醒消息可用变量:{user} 用户名 {id} 用户ID
|
|
172
|
+
{group} 群号 {gname} 群名称
|
|
173
|
+
{question} 申请理由 {answer} 匹配情况 {threshold} 阈值
|
|
174
|
+
使用 \\n 换行`;
|
|
175
|
+
}
|
|
176
|
+
__name(usageString, "usageString");
|
|
146
177
|
function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasRealEnableMessageParam, hasRealDisableMessageParam, logger2) {
|
|
147
178
|
let reminderEnabled = true;
|
|
148
179
|
let reminderMessage = "{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}";
|
|
@@ -166,6 +197,151 @@ function mergeReminder(existingConfig, cleanedOptions, hasRealMessageParam, hasR
|
|
|
166
197
|
return { reminderEnabled, reminderMessage };
|
|
167
198
|
}
|
|
168
199
|
__name(mergeReminder, "mergeReminder");
|
|
200
|
+
var autoQueue = /* @__PURE__ */ new Map();
|
|
201
|
+
async function syncTotalStats(ctx) {
|
|
202
|
+
try {
|
|
203
|
+
const allStats = await ctx.database.get("group_verification_stats", {
|
|
204
|
+
groupId: { $ne: "TOTAL" }
|
|
205
|
+
});
|
|
206
|
+
if (allStats.length > 0) {
|
|
207
|
+
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0);
|
|
208
|
+
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0);
|
|
209
|
+
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0);
|
|
210
|
+
await ctx.database.set("group_verification_stats", { groupId: "TOTAL" }, {
|
|
211
|
+
autoApproved: totalAutoApproved,
|
|
212
|
+
manuallyApproved: totalManuallyApproved,
|
|
213
|
+
rejected: totalRejected,
|
|
214
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
|
+
});
|
|
216
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
logger.error("同步总计统计时出错:", error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
__name(syncTotalStats, "syncTotalStats");
|
|
223
|
+
async function updateStats(ctx, groupId, action) {
|
|
224
|
+
const existingStats = await ctx.database.get("group_verification_stats", { groupId });
|
|
225
|
+
if (existingStats.length > 0) {
|
|
226
|
+
const stats = existingStats[0];
|
|
227
|
+
await ctx.database.set("group_verification_stats", { id: stats.id }, {
|
|
228
|
+
[action]: stats[action] + 1,
|
|
229
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
230
|
+
});
|
|
231
|
+
} else {
|
|
232
|
+
await ctx.database.create("group_verification_stats", {
|
|
233
|
+
groupId,
|
|
234
|
+
autoApproved: action === "autoApproved" ? 1 : 0,
|
|
235
|
+
manuallyApproved: action === "manuallyApproved" ? 1 : 0,
|
|
236
|
+
rejected: action === "rejected" ? 1 : 0,
|
|
237
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
await syncTotalStats(ctx);
|
|
241
|
+
}
|
|
242
|
+
__name(updateStats, "updateStats");
|
|
243
|
+
async function checkPermission(session, targetGroupId) {
|
|
244
|
+
const groupId = targetGroupId || session.guildId;
|
|
245
|
+
if (!groupId) {
|
|
246
|
+
return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
|
|
247
|
+
}
|
|
248
|
+
logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`);
|
|
249
|
+
const koishiAuthority = session.author?.authority || session.user?.authority;
|
|
250
|
+
logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || "未获取到"}`);
|
|
251
|
+
if (!session.author) {
|
|
252
|
+
logger.info(`权限检查 - session中可能的权限字段:`, {
|
|
253
|
+
authority: session.authority,
|
|
254
|
+
permission: session.permission,
|
|
255
|
+
role: session.role
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
logger.info(`权限检查 - author对象中的字段:`, {
|
|
259
|
+
authority: session.author.authority,
|
|
260
|
+
permission: session.author.permission,
|
|
261
|
+
role: session.author.role,
|
|
262
|
+
permissions: session.author.permissions
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (session.user) {
|
|
266
|
+
logger.info(`权限检查 - user对象中的权限信息:`, {
|
|
267
|
+
authority: session.user.authority,
|
|
268
|
+
permission: session.user.permission,
|
|
269
|
+
role: session.user.role
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (koishiAuthority && koishiAuthority >= 3) {
|
|
273
|
+
logger.info(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`);
|
|
274
|
+
return [true];
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const member = await session.bot.getGuildMember(groupId, session.userId);
|
|
278
|
+
logger.info(`权限检查 - 获取到成员信息:`, {
|
|
279
|
+
roles: member?.roles,
|
|
280
|
+
permissions: member?.permissions
|
|
281
|
+
});
|
|
282
|
+
if (member) {
|
|
283
|
+
if (member.permissions?.includes("OWNER") || member.roles?.includes("owner")) {
|
|
284
|
+
logger.info(`权限检查 - 用户是群主`);
|
|
285
|
+
return [true];
|
|
286
|
+
}
|
|
287
|
+
if (member.roles?.includes("admin") || member.permissions?.includes("ADMINISTRATOR")) {
|
|
288
|
+
logger.info(`权限检查 - 用户是管理员`);
|
|
289
|
+
return [true];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {
|
|
293
|
+
logger.warn("权限检查获取成员信息失败", e);
|
|
294
|
+
}
|
|
295
|
+
return [false, "权限不足"];
|
|
296
|
+
}
|
|
297
|
+
__name(checkPermission, "checkPermission");
|
|
298
|
+
async function handleGuildMemberRequestEvent(ctx, session) {
|
|
299
|
+
logger.debug("guild-member-request event", session);
|
|
300
|
+
let guildId = (session.guildId || session.channelId || "").toString().trim();
|
|
301
|
+
const userId = session.userId;
|
|
302
|
+
const message = session.content || "";
|
|
303
|
+
if (!guildId) {
|
|
304
|
+
logger.warn("guild-member-request 没有 guildId,跳过处理");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const requestId = session.event?.requestId || session.messageId || "";
|
|
308
|
+
const groupConfig = await ctx.database.get("group_verification_config", { groupId: guildId });
|
|
309
|
+
if (!groupConfig || groupConfig.length === 0) return;
|
|
310
|
+
const config = groupConfig[0];
|
|
311
|
+
if (config.reviewMethod === 3) {
|
|
312
|
+
logger.info(`配置要求全部拒绝,自动拒绝用户 ${userId}`);
|
|
313
|
+
if (requestId) {
|
|
314
|
+
try {
|
|
315
|
+
await session.bot.handleGuildMemberRequest(requestId, false);
|
|
316
|
+
} catch (e) {
|
|
317
|
+
logger.warn("自动拒绝失败", e);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
await updateStats(ctx, guildId, "rejected");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
|
|
324
|
+
logger.info(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
|
|
325
|
+
if (isValid) {
|
|
326
|
+
if (requestId) {
|
|
327
|
+
try {
|
|
328
|
+
await session.bot.handleGuildMemberRequest(requestId, true);
|
|
329
|
+
logger.info(`自动同意 requestId=${requestId}`);
|
|
330
|
+
if (!autoQueue.has(guildId)) autoQueue.set(guildId, /* @__PURE__ */ new Set());
|
|
331
|
+
autoQueue.get(guildId).add(userId);
|
|
332
|
+
} catch (e) {
|
|
333
|
+
logger.warn("自动同意失败", e);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
await handleFailedVerification(ctx, session, config, matchedCount, requiredThreshold);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
__name(handleGuildMemberRequestEvent, "handleGuildMemberRequestEvent");
|
|
341
|
+
function __getAutoQueue() {
|
|
342
|
+
return autoQueue;
|
|
343
|
+
}
|
|
344
|
+
__name(__getAutoQueue, "__getAutoQueue");
|
|
169
345
|
function parseConfigArgs(raw) {
|
|
170
346
|
const res = tokenize(raw);
|
|
171
347
|
if (res.error) {
|
|
@@ -386,47 +562,16 @@ function apply(ctx, config) {
|
|
|
386
562
|
primary: "id",
|
|
387
563
|
autoInc: true
|
|
388
564
|
});
|
|
389
|
-
const autoQueue = /* @__PURE__ */ new Map();
|
|
390
565
|
ctx.on("guild-member-request", async (session) => {
|
|
391
|
-
logger.
|
|
392
|
-
|
|
393
|
-
const userId = session.userId;
|
|
394
|
-
const message = session.content || "";
|
|
395
|
-
if (!guildId) {
|
|
396
|
-
logger.warn("guild-member-request 没有 guildId,跳过处理");
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
const requestId = session.event?.requestId || session.messageId || "";
|
|
400
|
-
const groupConfig = await ctx.database.get("group_verification_config", {
|
|
401
|
-
groupId: guildId
|
|
402
|
-
});
|
|
403
|
-
if (!groupConfig || groupConfig.length === 0) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
const config2 = groupConfig[0];
|
|
407
|
-
const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config2, message, session);
|
|
408
|
-
logger.info(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
|
|
409
|
-
if (isValid) {
|
|
410
|
-
if (requestId) {
|
|
411
|
-
try {
|
|
412
|
-
await session.bot.handleGuildMemberRequest(requestId, true);
|
|
413
|
-
logger.info(`自动同意 requestId=${requestId}`);
|
|
414
|
-
if (!autoQueue.has(guildId)) autoQueue.set(guildId, /* @__PURE__ */ new Set());
|
|
415
|
-
autoQueue.get(guildId).add(userId);
|
|
416
|
-
} catch (e) {
|
|
417
|
-
logger.warn("自动同意失败", e);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
} else {
|
|
421
|
-
await handleFailedVerification(ctx, session, config2, matchedCount, requiredThreshold);
|
|
422
|
-
}
|
|
566
|
+
logger.info("收到 guild-member-request 事件,转发给处理函数");
|
|
567
|
+
await handleGuildMemberRequestEvent(ctx, session);
|
|
423
568
|
});
|
|
424
569
|
ctx.on("guild-member-added", async (session) => {
|
|
425
570
|
const groupId = session.guildId;
|
|
426
571
|
const userId = session.userId;
|
|
427
572
|
const set = autoQueue.get(groupId);
|
|
428
573
|
if (set && set.has(userId)) {
|
|
429
|
-
await updateStats(groupId, "autoApproved");
|
|
574
|
+
await updateStats(ctx, groupId, "autoApproved");
|
|
430
575
|
set.delete(userId);
|
|
431
576
|
logger.info(`用户 ${userId} 通过机器人审批加入群 ${groupId}(autoQueue),统计已更新`);
|
|
432
577
|
return;
|
|
@@ -436,35 +581,15 @@ function apply(ctx, config) {
|
|
|
436
581
|
userId
|
|
437
582
|
});
|
|
438
583
|
if (pendingRecords.length > 0) {
|
|
439
|
-
await updateStats(groupId, "autoApproved");
|
|
584
|
+
await updateStats(ctx, groupId, "autoApproved");
|
|
440
585
|
await ctx.database.remove("group_verification_pending", { id: pendingRecords[0].id });
|
|
441
586
|
logger.info(`用户 ${userId} 通过验证加入群 ${groupId},统计已更新`);
|
|
442
587
|
} else {
|
|
443
|
-
await updateStats(groupId, "manuallyApproved");
|
|
588
|
+
await updateStats(ctx, groupId, "manuallyApproved");
|
|
444
589
|
logger.info(`用户 ${userId} 被手动邀请加入群 ${groupId},手动批准统计已更新`);
|
|
445
590
|
}
|
|
446
591
|
});
|
|
447
|
-
async function
|
|
448
|
-
const existingStats = await ctx.database.get("group_verification_stats", { groupId });
|
|
449
|
-
if (existingStats.length > 0) {
|
|
450
|
-
const stats = existingStats[0];
|
|
451
|
-
await ctx.database.set("group_verification_stats", { id: stats.id }, {
|
|
452
|
-
[action]: stats[action] + 1,
|
|
453
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
454
|
-
});
|
|
455
|
-
} else {
|
|
456
|
-
await ctx.database.create("group_verification_stats", {
|
|
457
|
-
groupId,
|
|
458
|
-
autoApproved: action === "autoApproved" ? 1 : 0,
|
|
459
|
-
manuallyApproved: action === "manuallyApproved" ? 1 : 0,
|
|
460
|
-
rejected: action === "rejected" ? 1 : 0,
|
|
461
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
await syncTotalStats(ctx);
|
|
465
|
-
}
|
|
466
|
-
__name(updateStats, "updateStats");
|
|
467
|
-
async function checkPermission(session, targetGroupId) {
|
|
592
|
+
async function checkPermission2(session, targetGroupId) {
|
|
468
593
|
const groupId = targetGroupId || session.guildId;
|
|
469
594
|
if (!groupId) {
|
|
470
595
|
return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
|
|
@@ -522,7 +647,7 @@ function apply(ctx, config) {
|
|
|
522
647
|
return [false, `权限不足:需要群主/管理员权限或koishi三级以上权限
|
|
523
648
|
${debugInfo}`];
|
|
524
649
|
}
|
|
525
|
-
__name(
|
|
650
|
+
__name(checkPermission2, "checkPermission");
|
|
526
651
|
const groupVerify = ctx.command("group-verify", "群组验证管理命令").alias("gv", "gverify");
|
|
527
652
|
groupVerify.subcommand(".config [keywords:text]", "配置群组验证规则").alias(
|
|
528
653
|
"gv.cfg",
|
|
@@ -570,7 +695,7 @@ ${debugInfo}`];
|
|
|
570
695
|
}
|
|
571
696
|
try {
|
|
572
697
|
await session.bot.getGuild(targetGroupId);
|
|
573
|
-
const [hasPermission, errorMsg] = await
|
|
698
|
+
const [hasPermission, errorMsg] = await checkPermission2(session, targetGroupId);
|
|
574
699
|
if (!hasPermission) {
|
|
575
700
|
return errorMsg || "权限不足";
|
|
576
701
|
}
|
|
@@ -658,13 +783,7 @@ ${debugInfo}`];
|
|
|
658
783
|
if (keywordList.length === 0 && !cleanedOptions.query && !cleanedOptions.remove) {
|
|
659
784
|
const hasConfigParams = cleanedOptions.method !== void 0 || cleanedOptions.threshold !== void 0 || hasRealMessageParam || hasRealEnableMessageParam || hasRealDisableMessageParam;
|
|
660
785
|
if (!hasConfigParams) {
|
|
661
|
-
return
|
|
662
|
-
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
663
|
-
gvc -m 1 -t 2 # 修改审核参数
|
|
664
|
-
gvc -msg "消息内容" # 修改提醒消息
|
|
665
|
-
gvc -nomsg # 禁用提醒消息
|
|
666
|
-
gvc -? # 查询配置
|
|
667
|
-
gvc -r # 删除配置`;
|
|
786
|
+
return usageString();
|
|
668
787
|
}
|
|
669
788
|
}
|
|
670
789
|
let existingConfig = null;
|
|
@@ -820,7 +939,7 @@ gvc -r # 删除配置`;
|
|
|
820
939
|
"group-verify.同意",
|
|
821
940
|
"gva"
|
|
822
941
|
).action(async ({ session }, userId) => {
|
|
823
|
-
const [hasPermission, errorMsg] = await
|
|
942
|
+
const [hasPermission, errorMsg] = await checkPermission2(session);
|
|
824
943
|
if (!hasPermission) {
|
|
825
944
|
return errorMsg || "权限不足";
|
|
826
945
|
}
|
|
@@ -828,6 +947,10 @@ gvc -r # 删除配置`;
|
|
|
828
947
|
if (!groupId) {
|
|
829
948
|
return "请在群聊中使用此命令";
|
|
830
949
|
}
|
|
950
|
+
const configs = await ctx.database.get("group_verification_config", { groupId });
|
|
951
|
+
if (configs.length > 0 && configs[0].reviewMethod === 3) {
|
|
952
|
+
return "该群已设为全部拒绝,无法手动同意任何申请";
|
|
953
|
+
}
|
|
831
954
|
if (!userId || userId.toLowerCase() === "all") {
|
|
832
955
|
if (userId?.toLowerCase() === "all") {
|
|
833
956
|
const pendingRequests2 = await ctx.database.get("group_verification_pending", { groupId });
|
|
@@ -838,8 +961,6 @@ gvc -r # 删除配置`;
|
|
|
838
961
|
for (const request2 of pendingRequests2) {
|
|
839
962
|
try {
|
|
840
963
|
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
841
|
-
if (!autoQueue.has(groupId)) autoQueue.set(groupId, /* @__PURE__ */ new Set());
|
|
842
|
-
autoQueue.get(groupId).add(request2.userId);
|
|
843
964
|
approvedCount++;
|
|
844
965
|
} catch (error) {
|
|
845
966
|
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
@@ -855,8 +976,6 @@ gvc -r # 删除配置`;
|
|
|
855
976
|
try {
|
|
856
977
|
await session.bot.handleGuildMemberRequest(request2.userId, true);
|
|
857
978
|
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
858
|
-
if (!autoQueue.has(groupId)) autoQueue.set(groupId, /* @__PURE__ */ new Set());
|
|
859
|
-
autoQueue.get(groupId).add(request2.userId);
|
|
860
979
|
return `已同意用户 ${request2.userName}(${request2.userId}) 的加群申请`;
|
|
861
980
|
} catch (error) {
|
|
862
981
|
return `处理申请时出错: ${error.message}`;
|
|
@@ -874,8 +993,6 @@ gvc -r # 删除配置`;
|
|
|
874
993
|
try {
|
|
875
994
|
await session.bot.handleGuildMemberRequest(request.userId, true);
|
|
876
995
|
await ctx.database.remove("group_verification_pending", { id: request.id });
|
|
877
|
-
if (!autoQueue.has(groupId)) autoQueue.set(groupId, /* @__PURE__ */ new Set());
|
|
878
|
-
autoQueue.get(groupId).add(userId);
|
|
879
996
|
return `已同意用户 ${request.userName}(${userId}) 的加群申请`;
|
|
880
997
|
} catch (error) {
|
|
881
998
|
return `处理申请时出错: ${error.message}`;
|
|
@@ -890,7 +1007,7 @@ gvc -r # 删除配置`;
|
|
|
890
1007
|
"group-verify.rej",
|
|
891
1008
|
"gvr"
|
|
892
1009
|
).action(async ({ session }, userId) => {
|
|
893
|
-
const [hasPermission, errorMsg] = await
|
|
1010
|
+
const [hasPermission, errorMsg] = await checkPermission2(session);
|
|
894
1011
|
if (!hasPermission) {
|
|
895
1012
|
return errorMsg || "权限不足";
|
|
896
1013
|
}
|
|
@@ -908,7 +1025,7 @@ gvc -r # 删除配置`;
|
|
|
908
1025
|
for (const request2 of pendingRequests2) {
|
|
909
1026
|
try {
|
|
910
1027
|
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
911
|
-
await updateStats(groupId, "rejected");
|
|
1028
|
+
await updateStats(ctx, groupId, "rejected");
|
|
912
1029
|
rejectedCount++;
|
|
913
1030
|
} catch (error) {
|
|
914
1031
|
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
@@ -923,7 +1040,7 @@ gvc -r # 删除配置`;
|
|
|
923
1040
|
const request2 = recentRequest[0];
|
|
924
1041
|
try {
|
|
925
1042
|
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
926
|
-
await updateStats(groupId, "rejected");
|
|
1043
|
+
await updateStats(ctx, groupId, "rejected");
|
|
927
1044
|
return `已拒绝用户 ${request2.userName}(${request2.userId}) 的加群申请`;
|
|
928
1045
|
} catch (error) {
|
|
929
1046
|
return `处理申请时出错: ${error.message}`;
|
|
@@ -940,7 +1057,7 @@ gvc -r # 删除配置`;
|
|
|
940
1057
|
const request = pendingRequests[0];
|
|
941
1058
|
try {
|
|
942
1059
|
await ctx.database.remove("group_verification_pending", { id: request.id });
|
|
943
|
-
await updateStats(groupId, "rejected");
|
|
1060
|
+
await updateStats(ctx, groupId, "rejected");
|
|
944
1061
|
return `已拒绝用户 ${request.userName}(${userId}) 的加群申请`;
|
|
945
1062
|
} catch (error) {
|
|
946
1063
|
return `处理申请时出错: ${error.message}`;
|
|
@@ -1145,39 +1262,23 @@ gvc -r # 删除配置`;
|
|
|
1145
1262
|
最后更新: ${lastUpdated}`;
|
|
1146
1263
|
}
|
|
1147
1264
|
__name(getTotalStats, "getTotalStats");
|
|
1148
|
-
async function syncTotalStats(ctx2) {
|
|
1149
|
-
try {
|
|
1150
|
-
const allStats = await ctx2.database.get("group_verification_stats", {
|
|
1151
|
-
groupId: { $ne: "TOTAL" }
|
|
1152
|
-
});
|
|
1153
|
-
if (allStats.length > 0) {
|
|
1154
|
-
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0);
|
|
1155
|
-
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0);
|
|
1156
|
-
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0);
|
|
1157
|
-
await ctx2.database.set("group_verification_stats", { groupId: "TOTAL" }, {
|
|
1158
|
-
autoApproved: totalAutoApproved,
|
|
1159
|
-
manuallyApproved: totalManuallyApproved,
|
|
1160
|
-
rejected: totalRejected,
|
|
1161
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1162
|
-
});
|
|
1163
|
-
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`);
|
|
1164
|
-
}
|
|
1165
|
-
} catch (error) {
|
|
1166
|
-
logger.error("同步总计统计时出错:", error);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
__name(syncTotalStats, "syncTotalStats");
|
|
1170
1265
|
}
|
|
1171
1266
|
__name(apply, "apply");
|
|
1172
1267
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1173
1268
|
0 && (module.exports = {
|
|
1174
1269
|
Config,
|
|
1270
|
+
__getAutoQueue,
|
|
1175
1271
|
apply,
|
|
1272
|
+
checkPermission,
|
|
1176
1273
|
handleFailedVerification,
|
|
1274
|
+
handleGuildMemberRequestEvent,
|
|
1177
1275
|
inject,
|
|
1178
1276
|
mergeReminder,
|
|
1179
1277
|
name,
|
|
1180
1278
|
parseConfigArgs,
|
|
1279
|
+
syncTotalStats,
|
|
1181
1280
|
tokenize,
|
|
1281
|
+
updateStats,
|
|
1282
|
+
usageString,
|
|
1182
1283
|
verifyApplication
|
|
1183
1284
|
});
|
package/package.json
CHANGED
package/readme.md
CHANGED
package/src/index.ts
CHANGED
|
@@ -56,7 +56,9 @@ export interface Config {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export const Config: Schema<Config> = Schema.object({
|
|
59
|
-
defaultReminderMessage: Schema.string()
|
|
59
|
+
defaultReminderMessage: Schema.string()
|
|
60
|
+
.description('默认提醒消息模板(使用 \n 表示换行,可包含下方变量)')
|
|
61
|
+
.default('{user}({id}) 申请加入群 {gname}({group})\n申请理由:{question}\n匹配情况:{answer}/{threshold}\n使用 gva 同意或 gvr 拒绝申请'),
|
|
60
62
|
enableStrictGroupCheck: Schema.boolean().description('是否启用严格的群号检查(检查群号长度)').default(false),
|
|
61
63
|
logLevel: Schema.union(['debug', 'info', 'warn', 'error']).description('日志级别').default('info')
|
|
62
64
|
}).description('群组验证插件配置')
|
|
@@ -216,6 +218,31 @@ function validateKeywordFormat(raw: string): boolean {
|
|
|
216
218
|
* 返回最终的 reminderEnabled 和 reminderMessage。
|
|
217
219
|
* 对于 -nomsg 不会清除已保存的 message,便于后续再次启用时恢复。
|
|
218
220
|
*/
|
|
221
|
+
export function usageString(): string {
|
|
222
|
+
return `用法:
|
|
223
|
+
# 创建/修改配置
|
|
224
|
+
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
225
|
+
gvc -m 1 -t 2 # 修改审核参数
|
|
226
|
+
|
|
227
|
+
# 提醒消息控制(可用变量详见下方)
|
|
228
|
+
gvc -msg "消息内容" # 修改提醒消息
|
|
229
|
+
gvc -nomsg # 禁用提醒消息
|
|
230
|
+
# 查询/删除
|
|
231
|
+
gvc -? # 查询配置
|
|
232
|
+
gvc -r # 删除配置
|
|
233
|
+
|
|
234
|
+
审核方式说明(使用 -m 参数):
|
|
235
|
+
0 全部同意(默认)
|
|
236
|
+
1 按数量同意,需要 -t 指定数量
|
|
237
|
+
2 按比例同意,需要 -t 指定百分比
|
|
238
|
+
3 全部拒绝(拒绝后系统会自动阻止任何通过)
|
|
239
|
+
|
|
240
|
+
提醒消息可用变量:{user} 用户名 {id} 用户ID
|
|
241
|
+
{group} 群号 {gname} 群名称
|
|
242
|
+
{question} 申请理由 {answer} 匹配情况 {threshold} 阈值
|
|
243
|
+
使用 \\n 换行`;
|
|
244
|
+
}
|
|
245
|
+
|
|
219
246
|
export function mergeReminder(
|
|
220
247
|
existingConfig: any | null,
|
|
221
248
|
cleanedOptions: {
|
|
@@ -260,6 +287,177 @@ export function mergeReminder(
|
|
|
260
287
|
* 返回关键词数组和各类 flag 的值,未出现的 flag 保持 undefined。
|
|
261
288
|
* 若检测到格式错误(如纯空格分隔关键词),返回 error 字段。
|
|
262
289
|
*/
|
|
290
|
+
// 全局缓存:记录通过机器人自动批准的用户,供 guild-member-added 事件使用
|
|
291
|
+
const autoQueue = new Map<string, Set<string>>();
|
|
292
|
+
|
|
293
|
+
// 更新统计信息函数,提取到模块层供多个位置调用
|
|
294
|
+
// synchronize overall statistics across groups
|
|
295
|
+
export async function syncTotalStats(ctx: Context) {
|
|
296
|
+
try {
|
|
297
|
+
// 获取所有群组统计(排除TOTAL行)
|
|
298
|
+
const allStats = await ctx.database.get('group_verification_stats', {
|
|
299
|
+
groupId: { $ne: 'TOTAL' }
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
if (allStats.length > 0) {
|
|
303
|
+
// 计算总计
|
|
304
|
+
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0)
|
|
305
|
+
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0)
|
|
306
|
+
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0)
|
|
307
|
+
|
|
308
|
+
// 更新总计行
|
|
309
|
+
await ctx.database.set('group_verification_stats', { groupId: 'TOTAL' }, {
|
|
310
|
+
autoApproved: totalAutoApproved,
|
|
311
|
+
manuallyApproved: totalManuallyApproved,
|
|
312
|
+
rejected: totalRejected,
|
|
313
|
+
lastUpdated: new Date().toISOString()
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`)
|
|
317
|
+
}
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.error('同步总计统计时出错:', error)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function updateStats(ctx: Context, groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected') {
|
|
324
|
+
// 更新群组统计
|
|
325
|
+
const existingStats = await ctx.database.get('group_verification_stats', { groupId })
|
|
326
|
+
|
|
327
|
+
if (existingStats.length > 0) {
|
|
328
|
+
const stats = existingStats[0]
|
|
329
|
+
await ctx.database.set('group_verification_stats', { id: stats.id }, {
|
|
330
|
+
[action]: stats[action] + 1,
|
|
331
|
+
lastUpdated: new Date().toISOString()
|
|
332
|
+
})
|
|
333
|
+
} else {
|
|
334
|
+
await ctx.database.create('group_verification_stats', {
|
|
335
|
+
groupId,
|
|
336
|
+
autoApproved: action === 'autoApproved' ? 1 : 0,
|
|
337
|
+
manuallyApproved: action === 'manuallyApproved' ? 1 : 0,
|
|
338
|
+
rejected: action === 'rejected' ? 1 : 0,
|
|
339
|
+
lastUpdated: new Date().toISOString()
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 同步更新总计统计
|
|
344
|
+
await syncTotalStats(ctx)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 权限检查函数(也可用于命令)
|
|
348
|
+
export async function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]> {
|
|
349
|
+
const groupId = targetGroupId || session.guildId
|
|
350
|
+
|
|
351
|
+
// 私聊情况下必须指定群号
|
|
352
|
+
if (!groupId) {
|
|
353
|
+
return [false, '请在群聊中使用此命令或使用 -i 参数指定群号']
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
logger.info(`权限检查 - 用户ID: ${session.userId}, 群号: ${groupId}`)
|
|
357
|
+
const koishiAuthority = session.author?.authority || session.user?.authority
|
|
358
|
+
logger.info(`权限检查 - Koishi权限等级: ${koishiAuthority || '未获取到'}`)
|
|
359
|
+
|
|
360
|
+
if (!session.author) {
|
|
361
|
+
logger.info(`权限检查 - session中可能的权限字段:`, {
|
|
362
|
+
authority: session.authority,
|
|
363
|
+
permission: session.permission,
|
|
364
|
+
role: session.role
|
|
365
|
+
})
|
|
366
|
+
} else {
|
|
367
|
+
logger.info(`权限检查 - author对象中的字段:`, {
|
|
368
|
+
authority: session.author.authority,
|
|
369
|
+
permission: session.author.permission,
|
|
370
|
+
role: session.author.role,
|
|
371
|
+
permissions: session.author.permissions
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (session.user) {
|
|
376
|
+
logger.info(`权限检查 - user对象中的权限信息:`, {
|
|
377
|
+
authority: session.user.authority,
|
|
378
|
+
permission: session.user.permission,
|
|
379
|
+
role: session.user.role
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (koishiAuthority && koishiAuthority >= 3) {
|
|
384
|
+
logger.info(`权限检查 - 通过koishi权限检查: ${koishiAuthority}`)
|
|
385
|
+
return [true]
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
const member = await session.bot.getGuildMember(groupId, session.userId)
|
|
390
|
+
logger.info(`权限检查 - 获取到成员信息:`, {
|
|
391
|
+
roles: member?.roles,
|
|
392
|
+
permissions: member?.permissions
|
|
393
|
+
})
|
|
394
|
+
if (member) {
|
|
395
|
+
if (member.permissions?.includes('OWNER') || member.roles?.includes('owner')) {
|
|
396
|
+
logger.info(`权限检查 - 用户是群主`)
|
|
397
|
+
return [true]
|
|
398
|
+
}
|
|
399
|
+
if (member.roles?.includes('admin') || member.permissions?.includes('ADMINISTRATOR')) {
|
|
400
|
+
logger.info(`权限检查 - 用户是管理员`)
|
|
401
|
+
return [true]
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch (e) {
|
|
405
|
+
logger.warn('权限检查获取成员信息失败', e)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return [false, '权限不足']
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 提供给测试的辅助函数:处理 guild-member-request 事件的逻辑
|
|
412
|
+
export async function handleGuildMemberRequestEvent(ctx: Context, session: any) {
|
|
413
|
+
logger.debug('guild-member-request event', session)
|
|
414
|
+
let guildId = (session.guildId || session.channelId || '').toString().trim();
|
|
415
|
+
const userId = session.userId;
|
|
416
|
+
const message = session.content || '';
|
|
417
|
+
|
|
418
|
+
if (!guildId) {
|
|
419
|
+
logger.warn('guild-member-request 没有 guildId,跳过处理');
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const requestId = ((session.event as any)?.requestId) || session.messageId || '';
|
|
424
|
+
const groupConfig = await ctx.database.get('group_verification_config', { groupId: guildId });
|
|
425
|
+
if (!groupConfig || groupConfig.length === 0) return;
|
|
426
|
+
const config = groupConfig[0];
|
|
427
|
+
|
|
428
|
+
if (config.reviewMethod === 3) {
|
|
429
|
+
logger.info(`配置要求全部拒绝,自动拒绝用户 ${userId}`);
|
|
430
|
+
if (requestId) {
|
|
431
|
+
try { await session.bot.handleGuildMemberRequest(requestId, false); } catch (e) { logger.warn('自动拒绝失败', e); }
|
|
432
|
+
}
|
|
433
|
+
await updateStats(ctx, guildId, 'rejected');
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
|
|
438
|
+
logger.info(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
|
|
439
|
+
|
|
440
|
+
if (isValid) {
|
|
441
|
+
if (requestId) {
|
|
442
|
+
try {
|
|
443
|
+
await session.bot.handleGuildMemberRequest(requestId, true);
|
|
444
|
+
logger.info(`自动同意 requestId=${requestId}`);
|
|
445
|
+
if (!autoQueue.has(guildId)) autoQueue.set(guildId, new Set());
|
|
446
|
+
autoQueue.get(guildId)!.add(userId);
|
|
447
|
+
} catch (e) {
|
|
448
|
+
logger.warn('自动同意失败', e);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
await handleFailedVerification(ctx, session, config, matchedCount, requiredThreshold);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 供测试读取当前 autoQueue 状态
|
|
457
|
+
export function __getAutoQueue() {
|
|
458
|
+
return autoQueue;
|
|
459
|
+
}
|
|
460
|
+
|
|
263
461
|
export function parseConfigArgs(raw: string): ParsedArgs {
|
|
264
462
|
const res = tokenize(raw);
|
|
265
463
|
if (res.error) {
|
|
@@ -543,57 +741,13 @@ export function apply(ctx: Context, config: Config) {
|
|
|
543
741
|
autoInc: true
|
|
544
742
|
})
|
|
545
743
|
|
|
546
|
-
// 缓存正在审批的用户,用于避免 guild-member-added 重复计数
|
|
547
|
-
const autoQueue = new Map<string, Set<string>>();
|
|
548
744
|
|
|
549
|
-
// 监听加群申请事件
|
|
550
|
-
ctx.on('guild-member-request', async (session) => {
|
|
551
|
-
// debug: 输出整个 session 以便定位字段名
|
|
552
|
-
logger.debug('guild-member-request event', session)
|
|
553
|
-
|
|
554
|
-
let guildId = (session.guildId || session.channelId || '').toString().trim();
|
|
555
|
-
const userId = session.userId;
|
|
556
|
-
const message = session.content || '';
|
|
557
|
-
|
|
558
|
-
if (!guildId) {
|
|
559
|
-
logger.warn('guild-member-request 没有 guildId,跳过处理');
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
745
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
groupId: guildId
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
if (!groupConfig || groupConfig.length === 0) {
|
|
572
|
-
// 无配置直接放行(需要有 requestId)
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
const config = groupConfig[0];
|
|
576
|
-
|
|
577
|
-
// 执行验证,记录详细情况
|
|
578
|
-
const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session);
|
|
579
|
-
logger.info(`验证结果 guild=${guildId} user=${userId} msg="${message}" matched=${matchedCount} threshold=${requiredThreshold} valid=${isValid}`);
|
|
580
|
-
|
|
581
|
-
if (isValid) {
|
|
582
|
-
if (requestId) {
|
|
583
|
-
try {
|
|
584
|
-
await session.bot.handleGuildMemberRequest(requestId, true);
|
|
585
|
-
logger.info(`自动同意 requestId=${requestId}`);
|
|
586
|
-
// 将此用户标记为自动批准,等待 guild-member-added 更新统计
|
|
587
|
-
if (!autoQueue.has(guildId)) autoQueue.set(guildId, new Set());
|
|
588
|
-
autoQueue.get(guildId)!.add(userId);
|
|
589
|
-
} catch (e) {
|
|
590
|
-
logger.warn('自动同意失败', e);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
} else {
|
|
594
|
-
await handleFailedVerification(ctx, session, config, matchedCount, requiredThreshold);
|
|
595
|
-
}
|
|
596
|
-
});
|
|
746
|
+
// 监听 guild-member-request 事件,以便对新申请执行自动审批或拒绝
|
|
747
|
+
ctx.on('guild-member-request', async (session) => {
|
|
748
|
+
logger.info('收到 guild-member-request 事件,转发给处理函数')
|
|
749
|
+
await handleGuildMemberRequestEvent(ctx, session)
|
|
750
|
+
})
|
|
597
751
|
|
|
598
752
|
// 监听群成员增加事件(包括手动邀请入群)
|
|
599
753
|
ctx.on('guild-member-added', async (session) => {
|
|
@@ -603,7 +757,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
603
757
|
// 先检查 autoQueue
|
|
604
758
|
const set = autoQueue.get(groupId)
|
|
605
759
|
if (set && set.has(userId)) {
|
|
606
|
-
await updateStats(groupId, 'autoApproved')
|
|
760
|
+
await updateStats(ctx, groupId, 'autoApproved')
|
|
607
761
|
set.delete(userId)
|
|
608
762
|
logger.info(`用户 ${userId} 通过机器人审批加入群 ${groupId}(autoQueue),统计已更新`)
|
|
609
763
|
return
|
|
@@ -617,13 +771,13 @@ export function apply(ctx: Context, config: Config) {
|
|
|
617
771
|
|
|
618
772
|
if (pendingRecords.length > 0) {
|
|
619
773
|
// 通过验证的用户入群,更新统计
|
|
620
|
-
await updateStats(groupId, 'autoApproved')
|
|
774
|
+
await updateStats(ctx, groupId, 'autoApproved')
|
|
621
775
|
// 清除待审核记录
|
|
622
776
|
await ctx.database.remove('group_verification_pending', { id: pendingRecords[0].id })
|
|
623
777
|
logger.info(`用户 ${userId} 通过验证加入群 ${groupId},统计已更新`)
|
|
624
778
|
} else {
|
|
625
779
|
// 手动邀请入群,记录到手动批准统计
|
|
626
|
-
await updateStats(groupId, 'manuallyApproved')
|
|
780
|
+
await updateStats(ctx, groupId, 'manuallyApproved')
|
|
627
781
|
logger.info(`用户 ${userId} 被手动邀请加入群 ${groupId},手动批准统计已更新`)
|
|
628
782
|
}
|
|
629
783
|
})
|
|
@@ -631,29 +785,6 @@ export function apply(ctx: Context, config: Config) {
|
|
|
631
785
|
|
|
632
786
|
|
|
633
787
|
// 更新统计信息
|
|
634
|
-
async function updateStats(groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected') {
|
|
635
|
-
// 更新群组统计
|
|
636
|
-
const existingStats = await ctx.database.get('group_verification_stats', { groupId })
|
|
637
|
-
|
|
638
|
-
if (existingStats.length > 0) {
|
|
639
|
-
const stats = existingStats[0]
|
|
640
|
-
await ctx.database.set('group_verification_stats', { id: stats.id }, {
|
|
641
|
-
[action]: stats[action] + 1,
|
|
642
|
-
lastUpdated: new Date().toISOString()
|
|
643
|
-
})
|
|
644
|
-
} else {
|
|
645
|
-
await ctx.database.create('group_verification_stats', {
|
|
646
|
-
groupId,
|
|
647
|
-
autoApproved: action === 'autoApproved' ? 1 : 0,
|
|
648
|
-
manuallyApproved: action === 'manuallyApproved' ? 1 : 0,
|
|
649
|
-
rejected: action === 'rejected' ? 1 : 0,
|
|
650
|
-
lastUpdated: new Date().toISOString()
|
|
651
|
-
})
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// 同步更新总计统计
|
|
655
|
-
await syncTotalStats(ctx)
|
|
656
|
-
}
|
|
657
788
|
|
|
658
789
|
// 权限检查函数
|
|
659
790
|
async function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]> {
|
|
@@ -927,13 +1058,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
927
1058
|
hasRealEnableMessageParam ||
|
|
928
1059
|
hasRealDisableMessageParam
|
|
929
1060
|
if (!hasConfigParams) {
|
|
930
|
-
return
|
|
931
|
-
gvc 关键词1,关键词2 -m 1 -t 2 # 创建配置
|
|
932
|
-
gvc -m 1 -t 2 # 修改审核参数
|
|
933
|
-
gvc -msg "消息内容" # 修改提醒消息
|
|
934
|
-
gvc -nomsg # 禁用提醒消息
|
|
935
|
-
gvc -? # 查询配置
|
|
936
|
-
gvc -r # 删除配置`
|
|
1061
|
+
return usageString()
|
|
937
1062
|
}
|
|
938
1063
|
// 这里会在下面获取一次 existingConfig,故暂不重复查询
|
|
939
1064
|
}
|
|
@@ -1145,6 +1270,11 @@ gvc -r # 删除配置`
|
|
|
1145
1270
|
return '请在群聊中使用此命令'
|
|
1146
1271
|
}
|
|
1147
1272
|
|
|
1273
|
+
// 在开始之前检查配置是否为全部拒绝
|
|
1274
|
+
const configs = await ctx.database.get('group_verification_config', { groupId })
|
|
1275
|
+
if (configs.length > 0 && configs[0].reviewMethod === 3) {
|
|
1276
|
+
return '该群已设为全部拒绝,无法手动同意任何申请'
|
|
1277
|
+
}
|
|
1148
1278
|
// 处理默认情况和all情况
|
|
1149
1279
|
if (!userId || userId.toLowerCase() === 'all') {
|
|
1150
1280
|
if (userId?.toLowerCase() === 'all') {
|
|
@@ -1160,9 +1290,6 @@ gvc -r # 删除配置`
|
|
|
1160
1290
|
// TODO: 需要获取实际的requestId来进行审批
|
|
1161
1291
|
// await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1162
1292
|
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1163
|
-
// 标记为自动批准,实际统计在 guild-member-added 处理
|
|
1164
|
-
if (!autoQueue.has(groupId)) autoQueue.set(groupId, new Set())
|
|
1165
|
-
autoQueue.get(groupId)!.add(request.userId)
|
|
1166
1293
|
approvedCount++
|
|
1167
1294
|
} catch (error) {
|
|
1168
1295
|
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
@@ -1182,8 +1309,6 @@ gvc -r # 删除配置`
|
|
|
1182
1309
|
// 这里需要获取实际的requestId,暂时用userId作为示例
|
|
1183
1310
|
await session.bot.handleGuildMemberRequest(request.userId, true)
|
|
1184
1311
|
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1185
|
-
if (!autoQueue.has(groupId)) autoQueue.set(groupId, new Set())
|
|
1186
|
-
autoQueue.get(groupId)!.add(request.userId)
|
|
1187
1312
|
return `已同意用户 ${request.userName}(${request.userId}) 的加群申请`
|
|
1188
1313
|
} catch (error) {
|
|
1189
1314
|
return `处理申请时出错: ${error.message}`
|
|
@@ -1206,8 +1331,6 @@ gvc -r # 删除配置`
|
|
|
1206
1331
|
// 这里需要获取实际的requestId来进行审批
|
|
1207
1332
|
await session.bot.handleGuildMemberRequest(request.userId, true)
|
|
1208
1333
|
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1209
|
-
if (!autoQueue.has(groupId)) autoQueue.set(groupId, new Set())
|
|
1210
|
-
autoQueue.get(groupId)!.add(userId)
|
|
1211
1334
|
return `已同意用户 ${request.userName}(${userId}) 的加群申请`
|
|
1212
1335
|
} catch (error) {
|
|
1213
1336
|
return `处理申请时出错: ${error.message}`
|
|
@@ -1249,7 +1372,7 @@ gvc -r # 删除配置`
|
|
|
1249
1372
|
// TODO: 需要获取实际的requestId来进行拒绝
|
|
1250
1373
|
// await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1251
1374
|
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1252
|
-
await updateStats(groupId, 'rejected')
|
|
1375
|
+
await updateStats(ctx, groupId, 'rejected')
|
|
1253
1376
|
rejectedCount++
|
|
1254
1377
|
} catch (error) {
|
|
1255
1378
|
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
@@ -1269,7 +1392,7 @@ gvc -r # 删除配置`
|
|
|
1269
1392
|
// TODO: 需要获取实际的requestId来进行拒绝
|
|
1270
1393
|
// await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1271
1394
|
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1272
|
-
await updateStats(groupId, 'rejected')
|
|
1395
|
+
await updateStats(ctx, groupId, 'rejected')
|
|
1273
1396
|
return `已拒绝用户 ${request.userName}(${request.userId}) 的加群申请`
|
|
1274
1397
|
} catch (error) {
|
|
1275
1398
|
return `处理申请时出错: ${error.message}`
|
|
@@ -1292,7 +1415,7 @@ gvc -r # 删除配置`
|
|
|
1292
1415
|
// TODO: 需要获取实际的requestId来进行拒绝
|
|
1293
1416
|
// await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1294
1417
|
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1295
|
-
await updateStats(groupId, 'rejected')
|
|
1418
|
+
await updateStats(ctx, groupId, 'rejected')
|
|
1296
1419
|
return `已拒绝用户 ${request.userName}(${userId}) 的加群申请`
|
|
1297
1420
|
} catch (error) {
|
|
1298
1421
|
return `处理申请时出错: ${error.message}`
|
|
@@ -1542,32 +1665,4 @@ gvc -r # 删除配置`
|
|
|
1542
1665
|
最后更新: ${lastUpdated}`
|
|
1543
1666
|
}
|
|
1544
1667
|
|
|
1545
|
-
// 同步统计数据到总计行的函数
|
|
1546
|
-
async function syncTotalStats(ctx: Context) {
|
|
1547
|
-
try {
|
|
1548
|
-
// 获取所有群组统计(排除TOTAL行)
|
|
1549
|
-
const allStats = await ctx.database.get('group_verification_stats', {
|
|
1550
|
-
groupId: { $ne: 'TOTAL' }
|
|
1551
|
-
})
|
|
1552
|
-
|
|
1553
|
-
if (allStats.length > 0) {
|
|
1554
|
-
// 计算总计
|
|
1555
|
-
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0)
|
|
1556
|
-
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0)
|
|
1557
|
-
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0)
|
|
1558
|
-
|
|
1559
|
-
// 更新总计行
|
|
1560
|
-
await ctx.database.set('group_verification_stats', { groupId: 'TOTAL' }, {
|
|
1561
|
-
autoApproved: totalAutoApproved,
|
|
1562
|
-
manuallyApproved: totalManuallyApproved,
|
|
1563
|
-
rejected: totalRejected,
|
|
1564
|
-
lastUpdated: new Date().toISOString()
|
|
1565
|
-
})
|
|
1566
|
-
|
|
1567
|
-
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`)
|
|
1568
|
-
}
|
|
1569
|
-
} catch (error) {
|
|
1570
|
-
logger.error('同步总计统计时出错:', error)
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
1668
|
}
|