koishi-plugin-group-verification 1.0.9 → 1.0.11
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 -0
- package/lib/index.js +218 -67
- package/package.json +1 -1
- package/src/index.ts +297 -85
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
Config: () => Config,
|
|
24
24
|
apply: () => apply,
|
|
25
|
+
inject: () => inject,
|
|
25
26
|
name: () => name
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -37,6 +38,7 @@ var Config = import_koishi.Schema.object({
|
|
|
37
38
|
).description("默认提醒消息模板"),
|
|
38
39
|
logLevel: import_koishi.Schema.union(["debug", "info", "warn", "error"]).default("info").description("日志级别")
|
|
39
40
|
}).description("群组验证插件配置");
|
|
41
|
+
var inject = ["database"];
|
|
40
42
|
function apply(ctx, config) {
|
|
41
43
|
ctx.model.extend("group_verification_config", {
|
|
42
44
|
id: "unsigned",
|
|
@@ -97,6 +99,22 @@ function apply(ctx, config) {
|
|
|
97
99
|
await handleFailedVerification(ctx, session, config2);
|
|
98
100
|
}
|
|
99
101
|
});
|
|
102
|
+
ctx.on("guild-member-added", async (session) => {
|
|
103
|
+
const groupId = session.guildId;
|
|
104
|
+
const userId = session.userId;
|
|
105
|
+
const pendingRecords = await ctx.database.get("group_verification_pending", {
|
|
106
|
+
groupId,
|
|
107
|
+
userId
|
|
108
|
+
});
|
|
109
|
+
if (pendingRecords.length > 0) {
|
|
110
|
+
await updateStats(groupId, "autoApproved");
|
|
111
|
+
await ctx.database.remove("group_verification_pending", { id: pendingRecords[0].id });
|
|
112
|
+
logger.info(`用户 ${userId} 通过验证加入群 ${groupId},统计已更新`);
|
|
113
|
+
} else {
|
|
114
|
+
await updateStats(groupId, "manuallyApproved");
|
|
115
|
+
logger.info(`用户 ${userId} 被手动邀请加入群 ${groupId},手动批准统计已更新`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
100
118
|
async function handleFailedVerification(ctx2, session, config2) {
|
|
101
119
|
const guildId = session.guildId;
|
|
102
120
|
const userId = session.userId;
|
|
@@ -153,39 +171,24 @@ function apply(ctx, config) {
|
|
|
153
171
|
return { isValid, matchedCount, requiredThreshold };
|
|
154
172
|
}
|
|
155
173
|
__name(verifyApplication, "verifyApplication");
|
|
156
|
-
async function updateStats(groupId,
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
const stats =
|
|
174
|
+
async function updateStats(groupId, action) {
|
|
175
|
+
const existingStats = await ctx.database.get("group_verification_stats", { groupId });
|
|
176
|
+
if (existingStats.length > 0) {
|
|
177
|
+
const stats = existingStats[0];
|
|
160
178
|
await ctx.database.set("group_verification_stats", { id: stats.id }, {
|
|
161
|
-
[
|
|
179
|
+
[action]: stats[action] + 1,
|
|
162
180
|
lastUpdated: /* @__PURE__ */ new Date()
|
|
163
181
|
});
|
|
164
182
|
} else {
|
|
165
183
|
await ctx.database.create("group_verification_stats", {
|
|
166
184
|
groupId,
|
|
167
|
-
autoApproved:
|
|
168
|
-
manuallyApproved:
|
|
169
|
-
rejected:
|
|
170
|
-
lastUpdated: /* @__PURE__ */ new Date()
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
const totalStats = await ctx.database.get("group_verification_stats", { groupId: "all" });
|
|
174
|
-
if (totalStats.length > 0) {
|
|
175
|
-
const stats = totalStats[0];
|
|
176
|
-
await ctx.database.set("group_verification_stats", { id: stats.id }, {
|
|
177
|
-
[statType]: stats[statType] + 1,
|
|
178
|
-
lastUpdated: /* @__PURE__ */ new Date()
|
|
179
|
-
});
|
|
180
|
-
} else {
|
|
181
|
-
await ctx.database.create("group_verification_stats", {
|
|
182
|
-
groupId: "all",
|
|
183
|
-
autoApproved: statType === "autoApproved" ? 1 : 0,
|
|
184
|
-
manuallyApproved: statType === "manuallyApproved" ? 1 : 0,
|
|
185
|
-
rejected: statType === "rejected" ? 1 : 0,
|
|
185
|
+
autoApproved: action === "autoApproved" ? 1 : 0,
|
|
186
|
+
manuallyApproved: action === "manuallyApproved" ? 1 : 0,
|
|
187
|
+
rejected: action === "rejected" ? 1 : 0,
|
|
186
188
|
lastUpdated: /* @__PURE__ */ new Date()
|
|
187
189
|
});
|
|
188
190
|
}
|
|
191
|
+
await syncTotalStats(ctx);
|
|
189
192
|
}
|
|
190
193
|
__name(updateStats, "updateStats");
|
|
191
194
|
async function checkPermission(session, targetGroupId) {
|
|
@@ -370,68 +373,149 @@ ${debugInfo}`];
|
|
|
370
373
|
return `已为群 ${targetGroupId} 创建验证配置`;
|
|
371
374
|
}
|
|
372
375
|
});
|
|
373
|
-
groupVerify.subcommand(".approve
|
|
374
|
-
|
|
375
|
-
|
|
376
|
+
groupVerify.subcommand(".approve [userId]", "同意加群申请").alias("accept", "同意", "通过", "gva").action(async ({ session }, userId) => {
|
|
377
|
+
const [hasPermission, errorMsg] = await checkPermission(session);
|
|
378
|
+
if (!hasPermission) {
|
|
379
|
+
return errorMsg || "权限不足";
|
|
376
380
|
}
|
|
377
|
-
|
|
381
|
+
const groupId = session.guildId;
|
|
382
|
+
if (!groupId) {
|
|
378
383
|
return "请在群聊中使用此命令";
|
|
379
384
|
}
|
|
380
|
-
if (!userId) {
|
|
381
|
-
|
|
385
|
+
if (!userId || userId.toLowerCase() === "all") {
|
|
386
|
+
if (userId?.toLowerCase() === "all") {
|
|
387
|
+
const pendingRequests2 = await ctx.database.get("group_verification_pending", { groupId });
|
|
388
|
+
if (pendingRequests2.length === 0) {
|
|
389
|
+
return "当前无待审核的加群申请";
|
|
390
|
+
}
|
|
391
|
+
let approvedCount = 0;
|
|
392
|
+
for (const request2 of pendingRequests2) {
|
|
393
|
+
try {
|
|
394
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
395
|
+
await updateStats(groupId, "manuallyApproved");
|
|
396
|
+
approvedCount++;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return `已处理 ${approvedCount} 个加群申请`;
|
|
402
|
+
} else {
|
|
403
|
+
const recentRequest = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName"]);
|
|
404
|
+
if (recentRequest.length === 0) {
|
|
405
|
+
return "当前无待审核的加群申请";
|
|
406
|
+
}
|
|
407
|
+
const request2 = recentRequest[0];
|
|
408
|
+
try {
|
|
409
|
+
await session.bot.handleGuildMemberRequest(request2.userId, true);
|
|
410
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
411
|
+
await updateStats(groupId, "manuallyApproved");
|
|
412
|
+
return `已同意用户 ${request2.userName}(${request2.userId}) 的加群申请`;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
return `处理申请时出错: ${error.message}`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
382
417
|
}
|
|
383
|
-
const
|
|
384
|
-
groupId
|
|
418
|
+
const pendingRequests = await ctx.database.get("group_verification_pending", {
|
|
419
|
+
groupId,
|
|
385
420
|
userId
|
|
386
421
|
});
|
|
387
|
-
if (
|
|
422
|
+
if (pendingRequests.length === 0) {
|
|
388
423
|
return `未找到用户 ${userId} 的待审核申请`;
|
|
389
424
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
425
|
+
const request = pendingRequests[0];
|
|
426
|
+
try {
|
|
427
|
+
await session.bot.handleGuildMemberRequest(request.userId, true);
|
|
428
|
+
await ctx.database.remove("group_verification_pending", { id: request.id });
|
|
429
|
+
await updateStats(groupId, "manuallyApproved");
|
|
430
|
+
return `已同意用户 ${request.userName}(${userId}) 的加群申请`;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
return `处理申请时出错: ${error.message}`;
|
|
433
|
+
}
|
|
394
434
|
});
|
|
395
|
-
groupVerify.subcommand(".reject
|
|
396
|
-
|
|
397
|
-
|
|
435
|
+
groupVerify.subcommand(".reject [userId]", "拒绝加群申请").alias("拒绝", "rej", "gvr").action(async ({ session }, userId) => {
|
|
436
|
+
const [hasPermission, errorMsg] = await checkPermission(session);
|
|
437
|
+
if (!hasPermission) {
|
|
438
|
+
return errorMsg || "权限不足";
|
|
398
439
|
}
|
|
399
|
-
|
|
440
|
+
const groupId = session.guildId;
|
|
441
|
+
if (!groupId) {
|
|
400
442
|
return "请在群聊中使用此命令";
|
|
401
443
|
}
|
|
402
|
-
if (!userId) {
|
|
403
|
-
|
|
444
|
+
if (!userId || userId.toLowerCase() === "all") {
|
|
445
|
+
if (userId?.toLowerCase() === "all") {
|
|
446
|
+
const pendingRequests2 = await ctx.database.get("group_verification_pending", { groupId });
|
|
447
|
+
if (pendingRequests2.length === 0) {
|
|
448
|
+
return "当前无待审核的加群申请";
|
|
449
|
+
}
|
|
450
|
+
let rejectedCount = 0;
|
|
451
|
+
for (const request2 of pendingRequests2) {
|
|
452
|
+
try {
|
|
453
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
454
|
+
await updateStats(groupId, "rejected");
|
|
455
|
+
rejectedCount++;
|
|
456
|
+
} catch (error) {
|
|
457
|
+
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return `已拒绝 ${rejectedCount} 个加群申请`;
|
|
461
|
+
} else {
|
|
462
|
+
const recentRequest = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName"]);
|
|
463
|
+
if (recentRequest.length === 0) {
|
|
464
|
+
return "当前无待审核的加群申请";
|
|
465
|
+
}
|
|
466
|
+
const request2 = recentRequest[0];
|
|
467
|
+
try {
|
|
468
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
469
|
+
await updateStats(groupId, "rejected");
|
|
470
|
+
return `已拒绝用户 ${request2.userName}(${request2.userId}) 的加群申请`;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
return `处理申请时出错: ${error.message}`;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
404
475
|
}
|
|
405
|
-
const
|
|
406
|
-
groupId
|
|
476
|
+
const pendingRequests = await ctx.database.get("group_verification_pending", {
|
|
477
|
+
groupId,
|
|
407
478
|
userId
|
|
408
479
|
});
|
|
409
|
-
if (
|
|
480
|
+
if (pendingRequests.length === 0) {
|
|
410
481
|
return `未找到用户 ${userId} 的待审核申请`;
|
|
411
482
|
}
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
483
|
+
const request = pendingRequests[0];
|
|
484
|
+
try {
|
|
485
|
+
await ctx.database.remove("group_verification_pending", { id: request.id });
|
|
486
|
+
await updateStats(groupId, "rejected");
|
|
487
|
+
return `已拒绝用户 ${request.userName}(${userId}) 的加群申请`;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
return `处理申请时出错: ${error.message}`;
|
|
490
|
+
}
|
|
416
491
|
});
|
|
417
|
-
groupVerify.subcommand(".stats [
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
492
|
+
groupVerify.subcommand(".stats [target]", "查看群组验证统计信息").alias("统计", "stat", "statistics").action(async ({ session }, target) => {
|
|
493
|
+
const validTargets = ["all", "total"];
|
|
494
|
+
const isGroupId = target && /^\d+$/.test(target);
|
|
495
|
+
const isSpecialTarget = target && validTargets.includes(target.toLowerCase());
|
|
496
|
+
if (target && !isGroupId && !isSpecialTarget) {
|
|
497
|
+
return "参数错误:只能指定群号、all、total或留空";
|
|
422
498
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
499
|
+
if (target?.toLowerCase() === "total" || target?.toLowerCase() === "all") {
|
|
500
|
+
const koishiAuthority = session.author?.authority || session.user?.authority;
|
|
501
|
+
if (!(koishiAuthority && koishiAuthority >= 3)) {
|
|
502
|
+
return "查看总计统计需要koishi 3级以上权限";
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const groupId = session.guildId;
|
|
506
|
+
if (!target) {
|
|
507
|
+
if (!groupId) {
|
|
508
|
+
return "请在群聊中使用此命令或指定群号";
|
|
509
|
+
}
|
|
510
|
+
return await getGroupStats(groupId);
|
|
434
511
|
}
|
|
512
|
+
if (target.toLowerCase() === "all" || target.toLowerCase() === "total") {
|
|
513
|
+
return await getTotalStats();
|
|
514
|
+
}
|
|
515
|
+
if (isGroupId) {
|
|
516
|
+
return await getGroupStats(target);
|
|
517
|
+
}
|
|
518
|
+
return "参数错误";
|
|
435
519
|
});
|
|
436
520
|
groupVerify.subcommand(".pending", "查看待审核加群申请").alias("list", "待审", "pend", "待处理").action(async ({ session }) => {
|
|
437
521
|
if (!session.guildId) {
|
|
@@ -488,15 +572,82 @@ ${debugInfo}`];
|
|
|
488
572
|
|
|
489
573
|
其他命令:
|
|
490
574
|
.approve <用户ID> - 同意加群申请
|
|
491
|
-
.reject <用户ID> - 拒绝加群申请
|
|
575
|
+
.reject <用户ID> - 拒绝加群申请
|
|
492
576
|
.pending - 查看待审核列表
|
|
493
577
|
.stats [群号] - 查看统计信息`;
|
|
494
578
|
});
|
|
579
|
+
ctx.on("ready", async () => {
|
|
580
|
+
const totalStats = await ctx.database.get("group_verification_stats", { groupId: "TOTAL" });
|
|
581
|
+
if (totalStats.length === 0) {
|
|
582
|
+
await ctx.database.create("group_verification_stats", {
|
|
583
|
+
groupId: "TOTAL",
|
|
584
|
+
autoApproved: 0,
|
|
585
|
+
manuallyApproved: 0,
|
|
586
|
+
rejected: 0,
|
|
587
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
588
|
+
});
|
|
589
|
+
logger.info("已创建总计统计行");
|
|
590
|
+
} else {
|
|
591
|
+
logger.info("总计统计行已存在");
|
|
592
|
+
}
|
|
593
|
+
await syncTotalStats(ctx);
|
|
594
|
+
});
|
|
595
|
+
async function getGroupStats(groupId) {
|
|
596
|
+
const stats = await ctx.database.get("group_verification_stats", { groupId });
|
|
597
|
+
if (stats.length === 0) {
|
|
598
|
+
return `群 ${groupId} 暂无验证统计信息`;
|
|
599
|
+
}
|
|
600
|
+
const stat = stats[0];
|
|
601
|
+
const lastUpdated = new Date(stat.lastUpdated).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
602
|
+
return `群 ${groupId} 验证统计:
|
|
603
|
+
自动批准: ${stat.autoApproved}
|
|
604
|
+
手动批准: ${stat.manuallyApproved}
|
|
605
|
+
拒绝: ${stat.rejected}
|
|
606
|
+
最后更新: ${lastUpdated}`;
|
|
607
|
+
}
|
|
608
|
+
__name(getGroupStats, "getGroupStats");
|
|
609
|
+
async function getTotalStats() {
|
|
610
|
+
const stats = await ctx.database.get("group_verification_stats", { groupId: "TOTAL" });
|
|
611
|
+
if (stats.length === 0) {
|
|
612
|
+
return "暂无统计信息";
|
|
613
|
+
}
|
|
614
|
+
const stat = stats[0];
|
|
615
|
+
const lastUpdated = new Date(stat.lastUpdated).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
616
|
+
return `总计验证统计:
|
|
617
|
+
自动批准: ${stat.autoApproved}
|
|
618
|
+
手动批准: ${stat.manuallyApproved}
|
|
619
|
+
拒绝: ${stat.rejected}
|
|
620
|
+
最后更新: ${lastUpdated}`;
|
|
621
|
+
}
|
|
622
|
+
__name(getTotalStats, "getTotalStats");
|
|
623
|
+
async function syncTotalStats(ctx2) {
|
|
624
|
+
try {
|
|
625
|
+
const allStats = await ctx2.database.get("group_verification_stats", {
|
|
626
|
+
groupId: { $ne: "TOTAL" }
|
|
627
|
+
});
|
|
628
|
+
if (allStats.length > 0) {
|
|
629
|
+
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0);
|
|
630
|
+
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0);
|
|
631
|
+
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0);
|
|
632
|
+
await ctx2.database.set("group_verification_stats", { groupId: "TOTAL" }, {
|
|
633
|
+
autoApproved: totalAutoApproved,
|
|
634
|
+
manuallyApproved: totalManuallyApproved,
|
|
635
|
+
rejected: totalRejected,
|
|
636
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
637
|
+
});
|
|
638
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`);
|
|
639
|
+
}
|
|
640
|
+
} catch (error) {
|
|
641
|
+
logger.error("同步总计统计时出错:", error);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
__name(syncTotalStats, "syncTotalStats");
|
|
495
645
|
}
|
|
496
646
|
__name(apply, "apply");
|
|
497
647
|
// Annotate the CommonJS export names for ESM import in node:
|
|
498
648
|
0 && (module.exports = {
|
|
499
649
|
Config,
|
|
500
650
|
apply,
|
|
651
|
+
inject,
|
|
501
652
|
name
|
|
502
653
|
});
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -67,6 +67,8 @@ export const Config: Schema<Config> = Schema.object({
|
|
|
67
67
|
.description('日志级别')
|
|
68
68
|
}).description('群组验证插件配置')
|
|
69
69
|
|
|
70
|
+
export const inject = ['database']
|
|
71
|
+
|
|
70
72
|
export function apply(ctx: Context, config: Config) {
|
|
71
73
|
// 创建数据库表
|
|
72
74
|
ctx.model.extend('group_verification_config', {
|
|
@@ -155,6 +157,30 @@ export function apply(ctx: Context, config: Config) {
|
|
|
155
157
|
}
|
|
156
158
|
})
|
|
157
159
|
|
|
160
|
+
// 监听群成员增加事件(包括手动邀请入群)
|
|
161
|
+
ctx.on('guild-member-added', async (session) => {
|
|
162
|
+
const groupId = session.guildId
|
|
163
|
+
const userId = session.userId
|
|
164
|
+
|
|
165
|
+
// 检查是否有待审核记录
|
|
166
|
+
const pendingRecords = await ctx.database.get('group_verification_pending', {
|
|
167
|
+
groupId: groupId,
|
|
168
|
+
userId: userId
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (pendingRecords.length > 0) {
|
|
172
|
+
// 通过验证的用户入群,更新统计
|
|
173
|
+
await updateStats(groupId, 'autoApproved')
|
|
174
|
+
// 清除待审核记录
|
|
175
|
+
await ctx.database.remove('group_verification_pending', { id: pendingRecords[0].id })
|
|
176
|
+
logger.info(`用户 ${userId} 通过验证加入群 ${groupId},统计已更新`)
|
|
177
|
+
} else {
|
|
178
|
+
// 手动邀请入群,记录到手动批准统计
|
|
179
|
+
await updateStats(groupId, 'manuallyApproved')
|
|
180
|
+
logger.info(`用户 ${userId} 被手动邀请加入群 ${groupId},手动批准统计已更新`)
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
158
184
|
// 处理验证失败的情况
|
|
159
185
|
async function handleFailedVerification(ctx: Context, session: any, config: GroupVerificationConfig) {
|
|
160
186
|
const guildId = session.guildId
|
|
@@ -236,44 +262,28 @@ export function apply(ctx: Context, config: Config) {
|
|
|
236
262
|
}
|
|
237
263
|
|
|
238
264
|
// 更新统计信息
|
|
239
|
-
async function updateStats(groupId: string,
|
|
265
|
+
async function updateStats(groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected') {
|
|
240
266
|
// 更新群组统计
|
|
241
|
-
const
|
|
267
|
+
const existingStats = await ctx.database.get('group_verification_stats', { groupId })
|
|
242
268
|
|
|
243
|
-
if (
|
|
244
|
-
const stats =
|
|
269
|
+
if (existingStats.length > 0) {
|
|
270
|
+
const stats = existingStats[0]
|
|
245
271
|
await ctx.database.set('group_verification_stats', { id: stats.id }, {
|
|
246
|
-
[
|
|
272
|
+
[action]: stats[action] + 1,
|
|
247
273
|
lastUpdated: new Date()
|
|
248
274
|
})
|
|
249
275
|
} else {
|
|
250
|
-
// 创建新的统计记录
|
|
251
276
|
await ctx.database.create('group_verification_stats', {
|
|
252
277
|
groupId,
|
|
253
|
-
autoApproved:
|
|
254
|
-
manuallyApproved:
|
|
255
|
-
rejected:
|
|
278
|
+
autoApproved: action === 'autoApproved' ? 1 : 0,
|
|
279
|
+
manuallyApproved: action === 'manuallyApproved' ? 1 : 0,
|
|
280
|
+
rejected: action === 'rejected' ? 1 : 0,
|
|
256
281
|
lastUpdated: new Date()
|
|
257
282
|
})
|
|
258
283
|
}
|
|
259
284
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
if (totalStats.length > 0) {
|
|
263
|
-
const stats = totalStats[0]
|
|
264
|
-
await ctx.database.set('group_verification_stats', { id: stats.id }, {
|
|
265
|
-
[statType]: stats[statType] + 1,
|
|
266
|
-
lastUpdated: new Date()
|
|
267
|
-
})
|
|
268
|
-
} else {
|
|
269
|
-
await ctx.database.create('group_verification_stats', {
|
|
270
|
-
groupId: 'all',
|
|
271
|
-
autoApproved: statType === 'autoApproved' ? 1 : 0,
|
|
272
|
-
manuallyApproved: statType === 'manuallyApproved' ? 1 : 0,
|
|
273
|
-
rejected: statType === 'rejected' ? 1 : 0,
|
|
274
|
-
lastUpdated: new Date()
|
|
275
|
-
})
|
|
276
|
-
}
|
|
285
|
+
// 同步更新总计统计
|
|
286
|
+
await syncTotalStats(ctx)
|
|
277
287
|
}
|
|
278
288
|
|
|
279
289
|
// 权限检查函数
|
|
@@ -526,97 +536,211 @@ export function apply(ctx: Context, config: Config) {
|
|
|
526
536
|
|
|
527
537
|
// Subcommand: approve join request
|
|
528
538
|
groupVerify
|
|
529
|
-
.subcommand('.approve
|
|
530
|
-
.alias('accept', '同意', '
|
|
539
|
+
.subcommand('.approve [userId]', '同意加群申请')
|
|
540
|
+
.alias('accept', '同意', '通过', 'gva')
|
|
531
541
|
.action(async ({ session }, userId) => {
|
|
532
542
|
// 权限检查
|
|
533
|
-
|
|
534
|
-
|
|
543
|
+
const [hasPermission, errorMsg] = await checkPermission(session)
|
|
544
|
+
if (!hasPermission) {
|
|
545
|
+
return errorMsg || '权限不足'
|
|
535
546
|
}
|
|
536
547
|
|
|
537
|
-
|
|
548
|
+
const groupId = session.guildId
|
|
549
|
+
if (!groupId) {
|
|
538
550
|
return '请在群聊中使用此命令'
|
|
539
551
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
552
|
+
|
|
553
|
+
// 处理默认情况和all情况
|
|
554
|
+
if (!userId || userId.toLowerCase() === 'all') {
|
|
555
|
+
if (userId?.toLowerCase() === 'all') {
|
|
556
|
+
// 处理所有待审核申请
|
|
557
|
+
const pendingRequests = await ctx.database.get('group_verification_pending', { groupId })
|
|
558
|
+
if (pendingRequests.length === 0) {
|
|
559
|
+
return '当前无待审核的加群申请'
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
let approvedCount = 0
|
|
563
|
+
for (const request of pendingRequests) {
|
|
564
|
+
try {
|
|
565
|
+
// TODO: 需要获取实际的requestId来进行审批
|
|
566
|
+
// await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
567
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
568
|
+
await updateStats(groupId, 'manuallyApproved')
|
|
569
|
+
approvedCount++
|
|
570
|
+
} catch (error) {
|
|
571
|
+
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return `已处理 ${approvedCount} 个加群申请`
|
|
576
|
+
} else {
|
|
577
|
+
// 处理最近的一个申请
|
|
578
|
+
const recentRequest = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName'])
|
|
579
|
+
if (recentRequest.length === 0) {
|
|
580
|
+
return '当前无待审核的加群申请'
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const request = recentRequest[0]
|
|
584
|
+
try {
|
|
585
|
+
// 这里需要获取实际的requestId,暂时用userId作为示例
|
|
586
|
+
await session.bot.handleGuildMemberRequest(request.userId, true)
|
|
587
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
588
|
+
await updateStats(groupId, 'manuallyApproved')
|
|
589
|
+
return `已同意用户 ${request.userName}(${request.userId}) 的加群申请`
|
|
590
|
+
} catch (error) {
|
|
591
|
+
return `处理申请时出错: ${error.message}`
|
|
592
|
+
}
|
|
593
|
+
}
|
|
543
594
|
}
|
|
544
|
-
|
|
545
|
-
//
|
|
546
|
-
const
|
|
547
|
-
groupId
|
|
548
|
-
userId: userId
|
|
595
|
+
|
|
596
|
+
// 处理指定用户ID的情况
|
|
597
|
+
const pendingRequests = await ctx.database.get('group_verification_pending', {
|
|
598
|
+
groupId,
|
|
599
|
+
userId: userId
|
|
549
600
|
})
|
|
550
|
-
|
|
551
|
-
if (
|
|
601
|
+
|
|
602
|
+
if (pendingRequests.length === 0) {
|
|
552
603
|
return `未找到用户 ${userId} 的待审核申请`
|
|
553
604
|
}
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
605
|
+
|
|
606
|
+
const request = pendingRequests[0]
|
|
607
|
+
try {
|
|
608
|
+
// 这里需要获取实际的requestId来进行审批
|
|
609
|
+
await session.bot.handleGuildMemberRequest(request.userId, true)
|
|
610
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
611
|
+
await updateStats(groupId, 'manuallyApproved')
|
|
612
|
+
return `已同意用户 ${request.userName}(${userId}) 的加群申请`
|
|
613
|
+
} catch (error) {
|
|
614
|
+
return `处理申请时出错: ${error.message}`
|
|
615
|
+
}
|
|
562
616
|
})
|
|
563
617
|
|
|
564
618
|
// Subcommand: reject join request
|
|
565
619
|
groupVerify
|
|
566
|
-
.subcommand('.reject
|
|
567
|
-
.alias('拒绝', 'rej', '
|
|
620
|
+
.subcommand('.reject [userId]', '拒绝加群申请')
|
|
621
|
+
.alias('拒绝', 'rej', 'gvr')
|
|
568
622
|
.action(async ({ session }, userId) => {
|
|
569
623
|
// 权限检查
|
|
570
|
-
|
|
571
|
-
|
|
624
|
+
const [hasPermission, errorMsg] = await checkPermission(session)
|
|
625
|
+
if (!hasPermission) {
|
|
626
|
+
return errorMsg || '权限不足'
|
|
572
627
|
}
|
|
573
628
|
|
|
574
|
-
|
|
629
|
+
const groupId = session.guildId
|
|
630
|
+
if (!groupId) {
|
|
575
631
|
return '请在群聊中使用此命令'
|
|
576
632
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
633
|
+
|
|
634
|
+
// 处理默认情况和all情况
|
|
635
|
+
if (!userId || userId.toLowerCase() === 'all') {
|
|
636
|
+
if (userId?.toLowerCase() === 'all') {
|
|
637
|
+
// 拒绝所有待审核申请
|
|
638
|
+
const pendingRequests = await ctx.database.get('group_verification_pending', { groupId })
|
|
639
|
+
if (pendingRequests.length === 0) {
|
|
640
|
+
return '当前无待审核的加群申请'
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
let rejectedCount = 0
|
|
644
|
+
for (const request of pendingRequests) {
|
|
645
|
+
try {
|
|
646
|
+
// TODO: 需要获取实际的requestId来进行拒绝
|
|
647
|
+
// await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
648
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
649
|
+
await updateStats(groupId, 'rejected')
|
|
650
|
+
rejectedCount++
|
|
651
|
+
} catch (error) {
|
|
652
|
+
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return `已拒绝 ${rejectedCount} 个加群申请`
|
|
657
|
+
} else {
|
|
658
|
+
// 拒绝最近的一个申请
|
|
659
|
+
const recentRequest = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName'])
|
|
660
|
+
if (recentRequest.length === 0) {
|
|
661
|
+
return '当前无待审核的加群申请'
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const request = recentRequest[0]
|
|
665
|
+
try {
|
|
666
|
+
// TODO: 需要获取实际的requestId来进行拒绝
|
|
667
|
+
// await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
668
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
669
|
+
await updateStats(groupId, 'rejected')
|
|
670
|
+
return `已拒绝用户 ${request.userName}(${request.userId}) 的加群申请`
|
|
671
|
+
} catch (error) {
|
|
672
|
+
return `处理申请时出错: ${error.message}`
|
|
673
|
+
}
|
|
674
|
+
}
|
|
580
675
|
}
|
|
581
|
-
|
|
582
|
-
//
|
|
583
|
-
const
|
|
584
|
-
groupId
|
|
585
|
-
userId: userId
|
|
676
|
+
|
|
677
|
+
// 处理指定用户ID的情况
|
|
678
|
+
const pendingRequests = await ctx.database.get('group_verification_pending', {
|
|
679
|
+
groupId,
|
|
680
|
+
userId: userId
|
|
586
681
|
})
|
|
587
|
-
|
|
588
|
-
if (
|
|
682
|
+
|
|
683
|
+
if (pendingRequests.length === 0) {
|
|
589
684
|
return `未找到用户 ${userId} 的待审核申请`
|
|
590
685
|
}
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
686
|
+
|
|
687
|
+
const request = pendingRequests[0]
|
|
688
|
+
try {
|
|
689
|
+
// TODO: 需要获取实际的requestId来进行拒绝
|
|
690
|
+
// await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
691
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
692
|
+
await updateStats(groupId, 'rejected')
|
|
693
|
+
return `已拒绝用户 ${request.userName}(${userId}) 的加群申请`
|
|
694
|
+
} catch (error) {
|
|
695
|
+
return `处理申请时出错: ${error.message}`
|
|
696
|
+
}
|
|
599
697
|
})
|
|
600
698
|
|
|
601
699
|
// Subcommand: view statistics
|
|
602
700
|
groupVerify
|
|
603
|
-
.subcommand('.stats [
|
|
604
|
-
.alias('
|
|
605
|
-
.action(async ({ session },
|
|
606
|
-
|
|
701
|
+
.subcommand('.stats [target]', '查看群组验证统计信息')
|
|
702
|
+
.alias('统计', 'stat', 'statistics')
|
|
703
|
+
.action(async ({ session }, target) => {
|
|
704
|
+
// 参数验证:只能是群号、all、total或空
|
|
705
|
+
const validTargets = ['all', 'total']
|
|
706
|
+
const isGroupId = target && /^\d+$/.test(target)
|
|
707
|
+
const isSpecialTarget = target && validTargets.includes(target.toLowerCase())
|
|
708
|
+
|
|
709
|
+
if (target && !isGroupId && !isSpecialTarget) {
|
|
710
|
+
return '参数错误:只能指定群号、all、total或留空'
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// 权限检查:总计统计需要3级以上权限
|
|
714
|
+
if (target?.toLowerCase() === 'total' || target?.toLowerCase() === 'all') {
|
|
715
|
+
// 检查是否为koishi 3级以上权限
|
|
716
|
+
const koishiAuthority = (session as any).author?.authority || (session as any).user?.authority
|
|
717
|
+
if (!(koishiAuthority && koishiAuthority >= 3)) {
|
|
718
|
+
return '查看总计统计需要koishi 3级以上权限'
|
|
719
|
+
}
|
|
720
|
+
}
|
|
607
721
|
|
|
608
|
-
const
|
|
722
|
+
const groupId = session.guildId
|
|
609
723
|
|
|
610
|
-
|
|
611
|
-
|
|
724
|
+
// 处理不同参数情况
|
|
725
|
+
if (!target) {
|
|
726
|
+
// 无参数:显示当前群统计
|
|
727
|
+
if (!groupId) {
|
|
728
|
+
return '请在群聊中使用此命令或指定群号'
|
|
729
|
+
}
|
|
730
|
+
return await getGroupStats(groupId)
|
|
612
731
|
}
|
|
613
732
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
return
|
|
617
|
-
}
|
|
618
|
-
|
|
733
|
+
if (target.toLowerCase() === 'all' || target.toLowerCase() === 'total') {
|
|
734
|
+
// 显示总计统计
|
|
735
|
+
return await getTotalStats()
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (isGroupId) {
|
|
739
|
+
// 显示指定群统计
|
|
740
|
+
return await getGroupStats(target)
|
|
619
741
|
}
|
|
742
|
+
|
|
743
|
+
return '参数错误'
|
|
620
744
|
})
|
|
621
745
|
|
|
622
746
|
// Subcommand: view pending requests
|
|
@@ -687,8 +811,96 @@ export function apply(ctx: Context, config: Config) {
|
|
|
687
811
|
|
|
688
812
|
其他命令:
|
|
689
813
|
.approve <用户ID> - 同意加群申请
|
|
690
|
-
.reject <用户ID> - 拒绝加群申请
|
|
814
|
+
.reject <用户ID> - 拒绝加群申请
|
|
691
815
|
.pending - 查看待审核列表
|
|
692
816
|
.stats [群号] - 查看统计信息`
|
|
693
817
|
})
|
|
818
|
+
|
|
819
|
+
// 插件初始化时确保总计统计行存在
|
|
820
|
+
ctx.on('ready', async () => {
|
|
821
|
+
// 检查是否已存在总计行(groupId为'TOTAL')
|
|
822
|
+
const totalStats = await ctx.database.get('group_verification_stats', { groupId: 'TOTAL' })
|
|
823
|
+
|
|
824
|
+
if (totalStats.length === 0) {
|
|
825
|
+
// 创建总计统计行
|
|
826
|
+
await ctx.database.create('group_verification_stats', {
|
|
827
|
+
groupId: 'TOTAL',
|
|
828
|
+
autoApproved: 0,
|
|
829
|
+
manuallyApproved: 0,
|
|
830
|
+
rejected: 0,
|
|
831
|
+
lastUpdated: new Date()
|
|
832
|
+
})
|
|
833
|
+
logger.info('已创建总计统计行')
|
|
834
|
+
} else {
|
|
835
|
+
logger.info('总计统计行已存在')
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// 同步现有统计数据到总计行
|
|
839
|
+
await syncTotalStats(ctx)
|
|
840
|
+
})
|
|
841
|
+
|
|
842
|
+
// 辅助函数:获取群组统计
|
|
843
|
+
async function getGroupStats(groupId: string): Promise<string> {
|
|
844
|
+
const stats = await ctx.database.get('group_verification_stats', { groupId })
|
|
845
|
+
|
|
846
|
+
if (stats.length === 0) {
|
|
847
|
+
return `群 ${groupId} 暂无验证统计信息`
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const stat = stats[0]
|
|
851
|
+
const lastUpdated = new Date(stat.lastUpdated).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
852
|
+
|
|
853
|
+
return `群 ${groupId} 验证统计:
|
|
854
|
+
自动批准: ${stat.autoApproved}
|
|
855
|
+
手动批准: ${stat.manuallyApproved}
|
|
856
|
+
拒绝: ${stat.rejected}
|
|
857
|
+
最后更新: ${lastUpdated}`
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// 辅助函数:获取总计统计
|
|
861
|
+
async function getTotalStats(): Promise<string> {
|
|
862
|
+
const stats = await ctx.database.get('group_verification_stats', { groupId: 'TOTAL' })
|
|
863
|
+
|
|
864
|
+
if (stats.length === 0) {
|
|
865
|
+
return '暂无统计信息'
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const stat = stats[0]
|
|
869
|
+
const lastUpdated = new Date(stat.lastUpdated).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
870
|
+
|
|
871
|
+
return `总计验证统计:
|
|
872
|
+
自动批准: ${stat.autoApproved}
|
|
873
|
+
手动批准: ${stat.manuallyApproved}
|
|
874
|
+
拒绝: ${stat.rejected}
|
|
875
|
+
最后更新: ${lastUpdated}`
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// 同步统计数据到总计行的函数
|
|
879
|
+
async function syncTotalStats(ctx: Context) {
|
|
880
|
+
try {
|
|
881
|
+
// 获取所有群组统计(排除TOTAL行)
|
|
882
|
+
const allStats = await ctx.database.get('group_verification_stats', {
|
|
883
|
+
groupId: { $ne: 'TOTAL' }
|
|
884
|
+
})
|
|
885
|
+
|
|
886
|
+
if (allStats.length > 0) {
|
|
887
|
+
// 计算总计
|
|
888
|
+
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0)
|
|
889
|
+
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0)
|
|
890
|
+
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0)
|
|
891
|
+
|
|
892
|
+
// 更新总计行
|
|
893
|
+
await ctx.database.set('group_verification_stats', { groupId: 'TOTAL' }, {
|
|
894
|
+
autoApproved: totalAutoApproved,
|
|
895
|
+
manuallyApproved: totalManuallyApproved,
|
|
896
|
+
rejected: totalRejected,
|
|
897
|
+
lastUpdated: new Date()
|
|
898
|
+
})
|
|
899
|
+
|
|
900
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`)
|
|
901
|
+
}
|
|
902
|
+
} catch (error) {
|
|
903
|
+
logger.error('同步总计统计时出错:', error)
|
|
904
|
+
}
|
|
905
|
+
}
|
|
694
906
|
}
|