koishi-plugin-group-verification 1.0.26 → 1.0.28
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 +3 -0
- package/lib/index.js +129 -42
- package/package.json +1 -1
- package/readme.md +17 -5
- package/src/index.ts +159 -62
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 {
|
|
@@ -34,6 +35,7 @@ export interface PendingVerification {
|
|
|
34
35
|
userId: string;
|
|
35
36
|
userName: string;
|
|
36
37
|
requestMessage: string;
|
|
38
|
+
requestId?: string;
|
|
37
39
|
applyTime: string | Date;
|
|
38
40
|
}
|
|
39
41
|
export interface Config {
|
|
@@ -91,6 +93,7 @@ export declare function mergeReminder(existingConfig: any | null, cleanedOptions
|
|
|
91
93
|
};
|
|
92
94
|
export declare function syncTotalStats(ctx: Context): Promise<void>;
|
|
93
95
|
export declare function updateStats(ctx: Context, groupId: string, action: 'autoApproved' | 'manuallyApproved' | 'rejected'): Promise<void>;
|
|
96
|
+
export declare function incrementTotal(ctx: Context, groupId: string): Promise<void>;
|
|
94
97
|
export declare function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]>;
|
|
95
98
|
export declare function handleGuildMemberRequestEvent(ctx: Context, session: any): Promise<void>;
|
|
96
99
|
export declare function __getAutoQueue(): Map<string, Set<string>>;
|
package/lib/index.js
CHANGED
|
@@ -26,6 +26,7 @@ __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,
|
|
@@ -207,13 +208,15 @@ async function syncTotalStats(ctx) {
|
|
|
207
208
|
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0);
|
|
208
209
|
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0);
|
|
209
210
|
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0);
|
|
211
|
+
const totalJoined = allStats.reduce((sum, stat) => sum + (stat.totalJoined || 0), 0);
|
|
210
212
|
await ctx.database.set("group_verification_stats", { groupId: "TOTAL" }, {
|
|
211
213
|
autoApproved: totalAutoApproved,
|
|
212
214
|
manuallyApproved: totalManuallyApproved,
|
|
213
215
|
rejected: totalRejected,
|
|
216
|
+
totalJoined,
|
|
214
217
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
218
|
});
|
|
216
|
-
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`);
|
|
219
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`);
|
|
217
220
|
}
|
|
218
221
|
} catch (error) {
|
|
219
222
|
logger.error("同步总计统计时出错:", error);
|
|
@@ -234,12 +237,34 @@ async function updateStats(ctx, groupId, action) {
|
|
|
234
237
|
autoApproved: action === "autoApproved" ? 1 : 0,
|
|
235
238
|
manuallyApproved: action === "manuallyApproved" ? 1 : 0,
|
|
236
239
|
rejected: action === "rejected" ? 1 : 0,
|
|
240
|
+
totalJoined: 0,
|
|
237
241
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
238
242
|
});
|
|
239
243
|
}
|
|
240
244
|
await syncTotalStats(ctx);
|
|
241
245
|
}
|
|
242
246
|
__name(updateStats, "updateStats");
|
|
247
|
+
async function incrementTotal(ctx, groupId) {
|
|
248
|
+
const existingStats = await ctx.database.get("group_verification_stats", { groupId });
|
|
249
|
+
if (existingStats.length > 0) {
|
|
250
|
+
const stats = existingStats[0];
|
|
251
|
+
await ctx.database.set("group_verification_stats", { id: stats.id }, {
|
|
252
|
+
totalJoined: (stats.totalJoined || 0) + 1,
|
|
253
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
await ctx.database.create("group_verification_stats", {
|
|
257
|
+
groupId,
|
|
258
|
+
autoApproved: 0,
|
|
259
|
+
manuallyApproved: 0,
|
|
260
|
+
rejected: 0,
|
|
261
|
+
totalJoined: 1,
|
|
262
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
await syncTotalStats(ctx);
|
|
266
|
+
}
|
|
267
|
+
__name(incrementTotal, "incrementTotal");
|
|
243
268
|
async function checkPermission(session, targetGroupId) {
|
|
244
269
|
const groupId = targetGroupId || session.guildId;
|
|
245
270
|
if (!groupId) {
|
|
@@ -443,14 +468,20 @@ async function verifyApplication(config, message, session) {
|
|
|
443
468
|
requiredThreshold = "null";
|
|
444
469
|
break;
|
|
445
470
|
case 1:
|
|
446
|
-
|
|
447
|
-
|
|
471
|
+
{
|
|
472
|
+
const thresholdNum = config.reviewParameters !== void 0 && config.reviewParameters !== null ? config.reviewParameters : 0;
|
|
473
|
+
isValid = matchedCount >= thresholdNum;
|
|
474
|
+
requiredThreshold = `${thresholdNum}`;
|
|
475
|
+
}
|
|
448
476
|
break;
|
|
449
477
|
case 2:
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
478
|
+
{
|
|
479
|
+
const thresholdPct = config.reviewParameters !== void 0 && config.reviewParameters !== null ? config.reviewParameters : 100;
|
|
480
|
+
const ratio = matchedCount / config.keywords.length;
|
|
481
|
+
isValid = ratio >= thresholdPct / 100;
|
|
482
|
+
const needed = Math.ceil(config.keywords.length * thresholdPct / 100);
|
|
483
|
+
requiredThreshold = `${needed}`;
|
|
484
|
+
}
|
|
454
485
|
break;
|
|
455
486
|
case 3:
|
|
456
487
|
isValid = false;
|
|
@@ -485,11 +516,14 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
|
|
|
485
516
|
groupName = guild.name || groupName;
|
|
486
517
|
} catch (error) {
|
|
487
518
|
}
|
|
519
|
+
const requestId = session.event?.requestId || session.messageId || "";
|
|
520
|
+
await ctx.database.remove("group_verification_pending", { groupId: guildId, userId });
|
|
488
521
|
await ctx.database.create("group_verification_pending", {
|
|
489
522
|
groupId: guildId,
|
|
490
523
|
userId,
|
|
491
524
|
userName: username,
|
|
492
525
|
requestMessage: message,
|
|
526
|
+
requestId,
|
|
493
527
|
applyTime: (/* @__PURE__ */ new Date()).toISOString()
|
|
494
528
|
});
|
|
495
529
|
if (!config.reminderEnabled || !config.reminderMessage || config.reminderMessage === "") {
|
|
@@ -507,10 +541,18 @@ async function handleFailedVerification(ctx, session, config, matchedCount, requ
|
|
|
507
541
|
await session.bot.broadcast([target], reminderMsg);
|
|
508
542
|
} catch (err) {
|
|
509
543
|
logger.warn("bot.broadcast failed, fallback to ctx.broadcast", err);
|
|
510
|
-
|
|
544
|
+
if (typeof ctx.broadcast === "function") {
|
|
545
|
+
await ctx.broadcast([target], reminderMsg);
|
|
546
|
+
} else {
|
|
547
|
+
logger.info("ctx.broadcast 不可用,跳过发送");
|
|
548
|
+
}
|
|
511
549
|
}
|
|
512
550
|
} else {
|
|
513
|
-
|
|
551
|
+
if (typeof ctx.broadcast === "function") {
|
|
552
|
+
await ctx.broadcast([target], reminderMsg);
|
|
553
|
+
} else {
|
|
554
|
+
logger.info("ctx.broadcast 不可用,跳过发送");
|
|
555
|
+
}
|
|
514
556
|
}
|
|
515
557
|
}
|
|
516
558
|
__name(handleFailedVerification, "handleFailedVerification");
|
|
@@ -543,6 +585,7 @@ function apply(ctx, config) {
|
|
|
543
585
|
autoApproved: "integer",
|
|
544
586
|
manuallyApproved: "integer",
|
|
545
587
|
rejected: "integer",
|
|
588
|
+
totalJoined: "integer",
|
|
546
589
|
// store as string (ISO timestamp) to preserve full date+time;
|
|
547
590
|
// Koishi `date` type truncates to day which leads to 00:00:00.
|
|
548
591
|
lastUpdated: "string"
|
|
@@ -556,6 +599,8 @@ function apply(ctx, config) {
|
|
|
556
599
|
userId: "string",
|
|
557
600
|
userName: "string",
|
|
558
601
|
requestMessage: "string",
|
|
602
|
+
// store the raw requestId if provided by OneBot event; used for approving/rejecting
|
|
603
|
+
requestId: "string",
|
|
559
604
|
// record full timestamp as string to keep time component
|
|
560
605
|
applyTime: "string"
|
|
561
606
|
}, {
|
|
@@ -569,6 +614,7 @@ function apply(ctx, config) {
|
|
|
569
614
|
ctx.on("guild-member-added", async (session) => {
|
|
570
615
|
const groupId = session.guildId;
|
|
571
616
|
const userId = session.userId;
|
|
617
|
+
await incrementTotal(ctx, groupId);
|
|
572
618
|
const set = autoQueue.get(groupId);
|
|
573
619
|
if (set && set.has(userId)) {
|
|
574
620
|
await updateStats(ctx, groupId, "autoApproved");
|
|
@@ -582,8 +628,10 @@ function apply(ctx, config) {
|
|
|
582
628
|
});
|
|
583
629
|
if (pendingRecords.length > 0) {
|
|
584
630
|
await updateStats(ctx, groupId, "autoApproved");
|
|
585
|
-
|
|
586
|
-
|
|
631
|
+
for (const rec of pendingRecords) {
|
|
632
|
+
await ctx.database.remove("group_verification_pending", { id: rec.id });
|
|
633
|
+
}
|
|
634
|
+
logger.info(`用户 ${userId} 通过验证加入群 ${groupId},已清理 ${pendingRecords.length} 条待审核记录,统计已更新`);
|
|
587
635
|
} else {
|
|
588
636
|
await updateStats(ctx, groupId, "manuallyApproved");
|
|
589
637
|
logger.info(`用户 ${userId} 被手动邀请加入群 ${groupId},手动批准统计已更新`);
|
|
@@ -898,7 +946,7 @@ ${debugInfo}`];
|
|
|
898
946
|
const methodMap = { 0: "全部同意", 1: "按数量", 2: "按比例", 3: "全部拒绝" };
|
|
899
947
|
feedbackMessage += `审核方式: ${methodMap[reviewMethod]}
|
|
900
948
|
`;
|
|
901
|
-
if (
|
|
949
|
+
if (reviewMethod === 1 || reviewMethod === 2) {
|
|
902
950
|
const thresholdDisplay = reviewMethod === 2 ? `${reviewParameters}%` : reviewParameters.toString();
|
|
903
951
|
feedbackMessage += `阈值: ${thresholdDisplay}
|
|
904
952
|
`;
|
|
@@ -958,31 +1006,44 @@ ${debugInfo}`];
|
|
|
958
1006
|
return "当前无待审核的加群申请";
|
|
959
1007
|
}
|
|
960
1008
|
let approvedCount = 0;
|
|
1009
|
+
let skippedCount = 0;
|
|
961
1010
|
for (const request2 of pendingRequests2) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1011
|
+
if (request2.requestId) {
|
|
1012
|
+
try {
|
|
1013
|
+
await session.bot.handleGuildMemberRequest(request2.requestId, true);
|
|
1014
|
+
approvedCount++;
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
1017
|
+
}
|
|
1018
|
+
} else {
|
|
1019
|
+
skippedCount++;
|
|
967
1020
|
}
|
|
1021
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
968
1022
|
}
|
|
969
|
-
|
|
1023
|
+
let msg = `已处理 ${approvedCount} 个加群申请`;
|
|
1024
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1025
|
+
return msg;
|
|
970
1026
|
} else {
|
|
971
|
-
|
|
972
|
-
if (
|
|
1027
|
+
let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime", "requestId"]);
|
|
1028
|
+
if (pending.length === 0) {
|
|
973
1029
|
return "当前无待审核的加群申请";
|
|
974
1030
|
}
|
|
975
|
-
|
|
1031
|
+
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)));
|
|
1032
|
+
const request2 = pending[0];
|
|
1033
|
+
if (!request2.requestId) {
|
|
1034
|
+
return `用户 ${request2.userId} 的申请缺少 requestId,无法自动同意`;
|
|
1035
|
+
}
|
|
976
1036
|
try {
|
|
977
|
-
await session.bot.handleGuildMemberRequest(request2.
|
|
978
|
-
await ctx.database.remove("group_verification_pending", {
|
|
979
|
-
|
|
1037
|
+
await session.bot.handleGuildMemberRequest(request2.requestId, true);
|
|
1038
|
+
await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
|
|
1039
|
+
const displayName = request2.userName && request2.userName !== request2.userId ? `${request2.userName}(${request2.userId})` : request2.userId;
|
|
1040
|
+
return `已同意用户 ${displayName} 的加群申请`;
|
|
980
1041
|
} catch (error) {
|
|
981
1042
|
return `处理申请时出错: ${error.message}`;
|
|
982
1043
|
}
|
|
983
1044
|
}
|
|
984
1045
|
}
|
|
985
|
-
|
|
1046
|
+
let pendingRequests = await ctx.database.get("group_verification_pending", {
|
|
986
1047
|
groupId,
|
|
987
1048
|
userId
|
|
988
1049
|
});
|
|
@@ -990,10 +1051,14 @@ ${debugInfo}`];
|
|
|
990
1051
|
return `未找到用户 ${userId} 的待审核申请`;
|
|
991
1052
|
}
|
|
992
1053
|
const request = pendingRequests[0];
|
|
1054
|
+
if (!request.requestId) {
|
|
1055
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动同意`;
|
|
1056
|
+
}
|
|
993
1057
|
try {
|
|
994
|
-
await session.bot.handleGuildMemberRequest(request.
|
|
995
|
-
await ctx.database.remove("group_verification_pending", {
|
|
996
|
-
|
|
1058
|
+
await session.bot.handleGuildMemberRequest(request.requestId, true);
|
|
1059
|
+
await ctx.database.remove("group_verification_pending", { groupId, userId });
|
|
1060
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId;
|
|
1061
|
+
return `已同意用户 ${displayName} 的加群申请`;
|
|
997
1062
|
} catch (error) {
|
|
998
1063
|
return `处理申请时出错: ${error.message}`;
|
|
999
1064
|
}
|
|
@@ -1022,26 +1087,40 @@ ${debugInfo}`];
|
|
|
1022
1087
|
return "当前无待审核的加群申请";
|
|
1023
1088
|
}
|
|
1024
1089
|
let rejectedCount = 0;
|
|
1090
|
+
let skippedCount = 0;
|
|
1025
1091
|
for (const request2 of pendingRequests2) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1092
|
+
if (request2.requestId) {
|
|
1093
|
+
try {
|
|
1094
|
+
await session.bot.handleGuildMemberRequest(request2.requestId, false);
|
|
1095
|
+
rejectedCount++;
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
logger.warn(`处理申请 ${request2.id} 时出错:`, error);
|
|
1098
|
+
}
|
|
1099
|
+
} else {
|
|
1100
|
+
skippedCount++;
|
|
1032
1101
|
}
|
|
1102
|
+
await ctx.database.remove("group_verification_pending", { id: request2.id });
|
|
1103
|
+
await updateStats(ctx, groupId, "rejected");
|
|
1033
1104
|
}
|
|
1034
|
-
|
|
1105
|
+
let msg = `已拒绝 ${rejectedCount} 个加群申请`;
|
|
1106
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1107
|
+
return msg;
|
|
1035
1108
|
} else {
|
|
1036
|
-
|
|
1037
|
-
if (
|
|
1109
|
+
let pending = await ctx.database.get("group_verification_pending", { groupId }, ["id", "userId", "userName", "applyTime", "requestId"]);
|
|
1110
|
+
if (pending.length === 0) {
|
|
1038
1111
|
return "当前无待审核的加群申请";
|
|
1039
1112
|
}
|
|
1040
|
-
|
|
1113
|
+
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)));
|
|
1114
|
+
const request2 = pending[0];
|
|
1115
|
+
if (!request2.requestId) {
|
|
1116
|
+
return `用户 ${request2.userId} 的申请缺少 requestId,无法自动拒绝`;
|
|
1117
|
+
}
|
|
1041
1118
|
try {
|
|
1042
|
-
await
|
|
1119
|
+
await session.bot.handleGuildMemberRequest(request2.requestId, false);
|
|
1120
|
+
await ctx.database.remove("group_verification_pending", { groupId, userId: request2.userId });
|
|
1043
1121
|
await updateStats(ctx, groupId, "rejected");
|
|
1044
|
-
|
|
1122
|
+
const displayName = request2.userName && request2.userName !== request2.userId ? `${request2.userName}(${request2.userId})` : request2.userId;
|
|
1123
|
+
return `已拒绝用户 ${displayName} 的加群申请`;
|
|
1045
1124
|
} catch (error) {
|
|
1046
1125
|
return `处理申请时出错: ${error.message}`;
|
|
1047
1126
|
}
|
|
@@ -1055,10 +1134,15 @@ ${debugInfo}`];
|
|
|
1055
1134
|
return `未找到用户 ${userId} 的待审核申请`;
|
|
1056
1135
|
}
|
|
1057
1136
|
const request = pendingRequests[0];
|
|
1137
|
+
if (!request.requestId) {
|
|
1138
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动拒绝`;
|
|
1139
|
+
}
|
|
1058
1140
|
try {
|
|
1059
|
-
await
|
|
1141
|
+
await session.bot.handleGuildMemberRequest(request.requestId, false);
|
|
1142
|
+
await ctx.database.remove("group_verification_pending", { groupId, userId });
|
|
1060
1143
|
await updateStats(ctx, groupId, "rejected");
|
|
1061
|
-
|
|
1144
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId;
|
|
1145
|
+
return `已拒绝用户 ${displayName} 的加群申请`;
|
|
1062
1146
|
} catch (error) {
|
|
1063
1147
|
return `处理申请时出错: ${error.message}`;
|
|
1064
1148
|
}
|
|
@@ -1203,6 +1287,7 @@ ${debugInfo}`];
|
|
|
1203
1287
|
for (const st of stats) {
|
|
1204
1288
|
const updates = {};
|
|
1205
1289
|
if (st.lastUpdated instanceof Date) updates.lastUpdated = st.lastUpdated.toISOString();
|
|
1290
|
+
if (st.totalJoined === void 0) updates.totalJoined = 0;
|
|
1206
1291
|
if (Object.keys(updates).length) {
|
|
1207
1292
|
await ctx.database.set("group_verification_stats", { id: st.id }, updates);
|
|
1208
1293
|
}
|
|
@@ -1211,6 +1296,7 @@ ${debugInfo}`];
|
|
|
1211
1296
|
for (const p of pendings) {
|
|
1212
1297
|
const updates = {};
|
|
1213
1298
|
if (p.applyTime instanceof Date) updates.applyTime = p.applyTime.toISOString();
|
|
1299
|
+
if (p.requestId === void 0) updates.requestId = "";
|
|
1214
1300
|
if (Object.keys(updates).length) {
|
|
1215
1301
|
await ctx.database.set("group_verification_pending", { id: p.id }, updates);
|
|
1216
1302
|
}
|
|
@@ -1272,6 +1358,7 @@ __name(apply, "apply");
|
|
|
1272
1358
|
checkPermission,
|
|
1273
1359
|
handleFailedVerification,
|
|
1274
1360
|
handleGuildMemberRequestEvent,
|
|
1361
|
+
incrementTotal,
|
|
1275
1362
|
inject,
|
|
1276
1363
|
mergeReminder,
|
|
1277
1364
|
name,
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- **多关键词匹配**:支持多个关键词的灵活配置
|
|
8
8
|
- **四种审核方式**:全部同意、按数量同意、按比例同意、全部拒绝
|
|
9
9
|
- **完善的权限控制**:群主/管理员权限或 Koishi 三级以上权限
|
|
10
|
-
-
|
|
10
|
+
- **详细的统计功能**:自动记录审核统计和手动入群统计(含自动通过、手动通过、拒绝以及总入群人数)
|
|
11
11
|
- **灵活的消息配置**:支持自定义提醒消息和禁用功能
|
|
12
12
|
- **友好的命令系统**:丰富的别名和快捷命令
|
|
13
13
|
|
|
@@ -54,6 +54,12 @@ group-verify.config -i 123456789 关键词1,关键词2 -m 1 -t 1
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
### 审核命令
|
|
57
|
+
|
|
58
|
+
> **命令增强**
|
|
59
|
+
> - 未提供参数时 `gva`/`gvr` 会处理最近一条申请,`all` 可以批量处理。
|
|
60
|
+
> - 如果申请记录缺少 requestId,则无法通过机器人接口处理,会提示管理员请在客户端手动操作。
|
|
61
|
+
> - 输出结果会智能展示用户名和ID,避免出现 "12345(12345)" 这样的重复显示。
|
|
62
|
+
|
|
57
63
|
```
|
|
58
64
|
# 同意申请(处理最近一个)
|
|
59
65
|
group-verify.approve
|
|
@@ -103,6 +109,10 @@ group-verify.stats total
|
|
|
103
109
|
- 方式2(按比例):需要达到的百分比(1-100)
|
|
104
110
|
|
|
105
111
|
### 提醒消息配置
|
|
112
|
+
|
|
113
|
+
提醒消息里的 `{threshold}` 在“按比例审核”模式下会显示为需要匹配的关键词数量
|
|
114
|
+
(而非百分比),例如三条关键词、阈值60%时显示为“1/2”。
|
|
115
|
+
|
|
106
116
|
- `-msg "消息内容"` - 设置自定义提醒消息
|
|
107
117
|
- 消息内部可包含逗号:只要所有逗号前后都没有空格,它们将被视为同一消息内容。
|
|
108
118
|
- 若逗号之后仍需写关键词,请使用引号包裹整个消息或在逗号后加空格以分隔。
|
|
@@ -144,10 +154,12 @@ group-verify.stats total
|
|
|
144
154
|
## 📊 统计功能
|
|
145
155
|
|
|
146
156
|
插件会自动记录以下统计信息:
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
157
|
+
- **自动批准**:通过关键词验证自动入群的用户数(包括自动审批与手动 gva 同意)
|
|
158
|
+
- **手动批准**:通过 gva 指令手动同意的用户数
|
|
159
|
+
- **拒绝**:被拒绝的申请数(包括自动拒绝与 gvr 指令)
|
|
160
|
+
- **总入群人数**:无论通过哪种方式,只要检测到成员加入即增加
|
|
161
|
+
|
|
162
|
+
> ⚠️ 注意:绕过机器人手动在客户端同意/拒绝的操作不会计入自动批准/拒绝统计,但仍会反映在总入群人数中。
|
|
151
163
|
### ⚠️ OneBot/QQ 适配器注意
|
|
152
164
|
默认情况下插件会使用 `ctx.broadcast` 发送提醒消息,
|
|
153
165
|
为了兼容 OneBot、QQ 等需要同时指定频道和群号的协议,
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface GroupVerificationStats {
|
|
|
36
36
|
autoApproved: number
|
|
37
37
|
manuallyApproved: number
|
|
38
38
|
rejected: number
|
|
39
|
+
// 新增:总入群人数(不论方式,只要检测到成员加入则增加)
|
|
40
|
+
totalJoined: number
|
|
39
41
|
lastUpdated: string | Date
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -46,6 +48,8 @@ export interface PendingVerification {
|
|
|
46
48
|
userId: string
|
|
47
49
|
userName: string
|
|
48
50
|
requestMessage: string
|
|
51
|
+
// raw OneBot requestId (may be empty string)
|
|
52
|
+
requestId?: string
|
|
49
53
|
applyTime: string | Date
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -304,16 +308,18 @@ export async function syncTotalStats(ctx: Context) {
|
|
|
304
308
|
const totalAutoApproved = allStats.reduce((sum, stat) => sum + (stat.autoApproved || 0), 0)
|
|
305
309
|
const totalManuallyApproved = allStats.reduce((sum, stat) => sum + (stat.manuallyApproved || 0), 0)
|
|
306
310
|
const totalRejected = allStats.reduce((sum, stat) => sum + (stat.rejected || 0), 0)
|
|
311
|
+
const totalJoined = allStats.reduce((sum, stat) => sum + (stat.totalJoined || 0), 0)
|
|
307
312
|
|
|
308
313
|
// 更新总计行
|
|
309
314
|
await ctx.database.set('group_verification_stats', { groupId: 'TOTAL' }, {
|
|
310
315
|
autoApproved: totalAutoApproved,
|
|
311
316
|
manuallyApproved: totalManuallyApproved,
|
|
312
317
|
rejected: totalRejected,
|
|
318
|
+
totalJoined,
|
|
313
319
|
lastUpdated: new Date().toISOString()
|
|
314
320
|
})
|
|
315
321
|
|
|
316
|
-
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}`)
|
|
322
|
+
logger.info(`总计统计已同步: 自动批准${totalAutoApproved}, 手动批准${totalManuallyApproved}, 拒绝${totalRejected}, 入群${totalJoined}`)
|
|
317
323
|
}
|
|
318
324
|
} catch (error) {
|
|
319
325
|
logger.error('同步总计统计时出错:', error)
|
|
@@ -336,6 +342,7 @@ export async function updateStats(ctx: Context, groupId: string, action: 'autoAp
|
|
|
336
342
|
autoApproved: action === 'autoApproved' ? 1 : 0,
|
|
337
343
|
manuallyApproved: action === 'manuallyApproved' ? 1 : 0,
|
|
338
344
|
rejected: action === 'rejected' ? 1 : 0,
|
|
345
|
+
totalJoined: 0,
|
|
339
346
|
lastUpdated: new Date().toISOString()
|
|
340
347
|
})
|
|
341
348
|
}
|
|
@@ -344,6 +351,28 @@ export async function updateStats(ctx: Context, groupId: string, action: 'autoAp
|
|
|
344
351
|
await syncTotalStats(ctx)
|
|
345
352
|
}
|
|
346
353
|
|
|
354
|
+
// 提取成独立函数:增加总入群计数,供事件统一调用
|
|
355
|
+
export async function incrementTotal(ctx: Context, groupId: string) {
|
|
356
|
+
const existingStats = await ctx.database.get('group_verification_stats', { groupId })
|
|
357
|
+
if (existingStats.length > 0) {
|
|
358
|
+
const stats = existingStats[0]
|
|
359
|
+
await ctx.database.set('group_verification_stats', { id: stats.id }, {
|
|
360
|
+
totalJoined: (stats.totalJoined || 0) + 1,
|
|
361
|
+
lastUpdated: new Date().toISOString()
|
|
362
|
+
})
|
|
363
|
+
} else {
|
|
364
|
+
await ctx.database.create('group_verification_stats', {
|
|
365
|
+
groupId,
|
|
366
|
+
autoApproved: 0,
|
|
367
|
+
manuallyApproved: 0,
|
|
368
|
+
rejected: 0,
|
|
369
|
+
totalJoined: 1,
|
|
370
|
+
lastUpdated: new Date().toISOString()
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
await syncTotalStats(ctx)
|
|
374
|
+
}
|
|
375
|
+
|
|
347
376
|
// 权限检查函数(也可用于命令)
|
|
348
377
|
export async function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]> {
|
|
349
378
|
const groupId = targetGroupId || session.guildId
|
|
@@ -581,14 +610,26 @@ export async function verifyApplication(config: GroupVerificationConfig, message
|
|
|
581
610
|
requiredThreshold = 'null'
|
|
582
611
|
break
|
|
583
612
|
case 1: // 按数量同意
|
|
584
|
-
|
|
585
|
-
|
|
613
|
+
{
|
|
614
|
+
// threshold may legitimately be 0 (表示全部同意)
|
|
615
|
+
const thresholdNum = config.reviewParameters !== undefined && config.reviewParameters !== null
|
|
616
|
+
? config.reviewParameters
|
|
617
|
+
: 0
|
|
618
|
+
isValid = matchedCount >= thresholdNum
|
|
619
|
+
requiredThreshold = `${thresholdNum}`
|
|
620
|
+
}
|
|
586
621
|
break
|
|
587
622
|
case 2: // 按比例同意
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
623
|
+
{
|
|
624
|
+
const thresholdPct = config.reviewParameters !== undefined && config.reviewParameters !== null
|
|
625
|
+
? config.reviewParameters
|
|
626
|
+
: 100
|
|
627
|
+
const ratio = matchedCount / config.keywords.length
|
|
628
|
+
isValid = ratio >= thresholdPct / 100
|
|
629
|
+
// 显示阈值为需要匹配的关键词数量,避免 "1/60%" 之类混淆
|
|
630
|
+
const needed = Math.ceil(config.keywords.length * thresholdPct / 100)
|
|
631
|
+
requiredThreshold = `${needed}`
|
|
632
|
+
}
|
|
592
633
|
break
|
|
593
634
|
case 3: // 全部拒绝
|
|
594
635
|
isValid = false
|
|
@@ -637,12 +678,19 @@ export async function handleFailedVerification(
|
|
|
637
678
|
// 无法获取群名称时使用默认值
|
|
638
679
|
}
|
|
639
680
|
|
|
681
|
+
// extract requestId if available (OneBot event attaches it)
|
|
682
|
+
const requestId = ((session.event as any)?.requestId) || session.messageId || ''
|
|
683
|
+
|
|
684
|
+
// 删除同一用户在该群之前的所有待审核记录,保留最新一个
|
|
685
|
+
await ctx.database.remove('group_verification_pending', { groupId: guildId, userId })
|
|
686
|
+
|
|
640
687
|
// 将申请加入待审核列表
|
|
641
688
|
await ctx.database.create('group_verification_pending', {
|
|
642
689
|
groupId: guildId,
|
|
643
690
|
userId: userId,
|
|
644
691
|
userName: username,
|
|
645
692
|
requestMessage: message,
|
|
693
|
+
requestId,
|
|
646
694
|
applyTime: new Date().toISOString()
|
|
647
695
|
})
|
|
648
696
|
// 如果提醒消息被禁用,直接返回
|
|
@@ -676,10 +724,18 @@ export async function handleFailedVerification(
|
|
|
676
724
|
} catch (err) {
|
|
677
725
|
// fallback to ctx.broadcast if bot.broadcast fails for some reason
|
|
678
726
|
logger.warn('bot.broadcast failed, fallback to ctx.broadcast', err)
|
|
679
|
-
|
|
727
|
+
if (typeof (ctx.broadcast) === 'function') {
|
|
728
|
+
await (ctx.broadcast as any)([target], reminderMsg)
|
|
729
|
+
} else {
|
|
730
|
+
logger.info('ctx.broadcast 不可用,跳过发送')
|
|
731
|
+
}
|
|
680
732
|
}
|
|
681
733
|
} else {
|
|
682
|
-
|
|
734
|
+
if (typeof (ctx.broadcast) === 'function') {
|
|
735
|
+
await (ctx.broadcast as any)([target], reminderMsg)
|
|
736
|
+
} else {
|
|
737
|
+
logger.info('ctx.broadcast 不可用,跳过发送')
|
|
738
|
+
}
|
|
683
739
|
}
|
|
684
740
|
}
|
|
685
741
|
|
|
@@ -720,6 +776,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
720
776
|
autoApproved: 'integer',
|
|
721
777
|
manuallyApproved: 'integer',
|
|
722
778
|
rejected: 'integer',
|
|
779
|
+
totalJoined: 'integer',
|
|
723
780
|
// store as string (ISO timestamp) to preserve full date+time;
|
|
724
781
|
// Koishi `date` type truncates to day which leads to 00:00:00.
|
|
725
782
|
lastUpdated: 'string'
|
|
@@ -728,15 +785,18 @@ export function apply(ctx: Context, config: Config) {
|
|
|
728
785
|
autoInc: true
|
|
729
786
|
})
|
|
730
787
|
|
|
788
|
+
// cast schema to any to avoid type conflicts when adding new fields
|
|
731
789
|
ctx.model.extend('group_verification_pending', {
|
|
732
790
|
id: 'unsigned',
|
|
733
791
|
groupId: 'string',
|
|
734
792
|
userId: 'string',
|
|
735
793
|
userName: 'string',
|
|
736
794
|
requestMessage: 'string',
|
|
795
|
+
// store the raw requestId if provided by OneBot event; used for approving/rejecting
|
|
796
|
+
requestId: 'string',
|
|
737
797
|
// record full timestamp as string to keep time component
|
|
738
798
|
applyTime: 'string'
|
|
739
|
-
}, {
|
|
799
|
+
} as any, {
|
|
740
800
|
primary: 'id',
|
|
741
801
|
autoInc: true
|
|
742
802
|
})
|
|
@@ -754,6 +814,9 @@ export function apply(ctx: Context, config: Config) {
|
|
|
754
814
|
const groupId = session.guildId
|
|
755
815
|
const userId = session.userId
|
|
756
816
|
|
|
817
|
+
// 无论什么情况只要检测到加入就累加总入群
|
|
818
|
+
await incrementTotal(ctx, groupId)
|
|
819
|
+
|
|
757
820
|
// 先检查 autoQueue
|
|
758
821
|
const set = autoQueue.get(groupId)
|
|
759
822
|
if (set && set.has(userId)) {
|
|
@@ -772,9 +835,11 @@ export function apply(ctx: Context, config: Config) {
|
|
|
772
835
|
if (pendingRecords.length > 0) {
|
|
773
836
|
// 通过验证的用户入群,更新统计
|
|
774
837
|
await updateStats(ctx, groupId, 'autoApproved')
|
|
775
|
-
//
|
|
776
|
-
|
|
777
|
-
|
|
838
|
+
// 清除所有该用户的待审核记录
|
|
839
|
+
for (const rec of pendingRecords) {
|
|
840
|
+
await ctx.database.remove('group_verification_pending', { id: rec.id })
|
|
841
|
+
}
|
|
842
|
+
logger.info(`用户 ${userId} 通过验证加入群 ${groupId},已清理 ${pendingRecords.length} 条待审核记录,统计已更新`)
|
|
778
843
|
} else {
|
|
779
844
|
// 手动邀请入群,记录到手动批准统计
|
|
780
845
|
await updateStats(ctx, groupId, 'manuallyApproved')
|
|
@@ -1212,7 +1277,8 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1212
1277
|
const methodMap = {0: '全部同意', 1: '按数量', 2: '按比例', 3: '全部拒绝'}
|
|
1213
1278
|
feedbackMessage += `审核方式: ${methodMap[reviewMethod]}\n`
|
|
1214
1279
|
|
|
1215
|
-
|
|
1280
|
+
// 显示阈值(即便为 0)
|
|
1281
|
+
if (reviewMethod === 1 || reviewMethod === 2) {
|
|
1216
1282
|
const thresholdDisplay = reviewMethod === 2 ? `${reviewParameters}%` : reviewParameters.toString()
|
|
1217
1283
|
feedbackMessage += `阈值: ${thresholdDisplay}\n`
|
|
1218
1284
|
}
|
|
@@ -1275,7 +1341,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1275
1341
|
if (configs.length > 0 && configs[0].reviewMethod === 3) {
|
|
1276
1342
|
return '该群已设为全部拒绝,无法手动同意任何申请'
|
|
1277
1343
|
}
|
|
1278
|
-
// 处理默认情况和all情况
|
|
1344
|
+
// 处理默认情况和 all 情况
|
|
1279
1345
|
if (!userId || userId.toLowerCase() === 'all') {
|
|
1280
1346
|
if (userId?.toLowerCase() === 'all') {
|
|
1281
1347
|
// 处理所有待审核申请
|
|
@@ -1285,31 +1351,41 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1285
1351
|
}
|
|
1286
1352
|
|
|
1287
1353
|
let approvedCount = 0
|
|
1354
|
+
let skippedCount = 0
|
|
1288
1355
|
for (const request of pendingRequests) {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1356
|
+
if (request.requestId) {
|
|
1357
|
+
try {
|
|
1358
|
+
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1359
|
+
approvedCount++
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
1362
|
+
}
|
|
1363
|
+
} else {
|
|
1364
|
+
skippedCount++
|
|
1296
1365
|
}
|
|
1366
|
+
// 不论是否有 requestId,都清除记录,避免无限积累
|
|
1367
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1297
1368
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1369
|
+
let msg = `已处理 ${approvedCount} 个加群申请`
|
|
1370
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1371
|
+
return msg
|
|
1300
1372
|
} else {
|
|
1301
|
-
//
|
|
1302
|
-
|
|
1303
|
-
if (
|
|
1373
|
+
// 处理最近的一个申请(按时间降序)
|
|
1374
|
+
let pending: any[] = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName', 'applyTime', 'requestId'])
|
|
1375
|
+
if (pending.length === 0) {
|
|
1304
1376
|
return '当前无待审核的加群申请'
|
|
1305
1377
|
}
|
|
1306
|
-
|
|
1307
|
-
const request =
|
|
1378
|
+
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)))
|
|
1379
|
+
const request: any = pending[0]
|
|
1380
|
+
if (!request.requestId) {
|
|
1381
|
+
return `用户 ${request.userId} 的申请缺少 requestId,无法自动同意`;
|
|
1382
|
+
}
|
|
1308
1383
|
try {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
await ctx.database.remove('group_verification_pending', {
|
|
1312
|
-
|
|
1384
|
+
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1385
|
+
// 清除该用户的所有待审核记录
|
|
1386
|
+
await ctx.database.remove('group_verification_pending', { groupId, userId: request.userId })
|
|
1387
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1388
|
+
return `已同意用户 ${displayName} 的加群申请`
|
|
1313
1389
|
} catch (error) {
|
|
1314
1390
|
return `处理申请时出错: ${error.message}`
|
|
1315
1391
|
}
|
|
@@ -1317,7 +1393,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1317
1393
|
}
|
|
1318
1394
|
|
|
1319
1395
|
// 处理指定用户ID的情况
|
|
1320
|
-
|
|
1396
|
+
let pendingRequests = await ctx.database.get('group_verification_pending', {
|
|
1321
1397
|
groupId,
|
|
1322
1398
|
userId: userId
|
|
1323
1399
|
})
|
|
@@ -1327,11 +1403,15 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1327
1403
|
}
|
|
1328
1404
|
|
|
1329
1405
|
const request = pendingRequests[0]
|
|
1406
|
+
if (!request.requestId) {
|
|
1407
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动同意`
|
|
1408
|
+
}
|
|
1330
1409
|
try {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
await ctx.database.remove('group_verification_pending', {
|
|
1334
|
-
|
|
1410
|
+
await session.bot.handleGuildMemberRequest(request.requestId, true)
|
|
1411
|
+
// 删除该用户的所有记录
|
|
1412
|
+
await ctx.database.remove('group_verification_pending', { groupId, userId })
|
|
1413
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1414
|
+
return `已同意用户 ${displayName} 的加群申请`
|
|
1335
1415
|
} catch (error) {
|
|
1336
1416
|
return `处理申请时出错: ${error.message}`
|
|
1337
1417
|
}
|
|
@@ -1357,7 +1437,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1357
1437
|
return '请在群聊中使用此命令'
|
|
1358
1438
|
}
|
|
1359
1439
|
|
|
1360
|
-
// 处理默认情况和all情况
|
|
1440
|
+
// 处理默认情况和 all 情况
|
|
1361
1441
|
if (!userId || userId.toLowerCase() === 'all') {
|
|
1362
1442
|
if (userId?.toLowerCase() === 'all') {
|
|
1363
1443
|
// 拒绝所有待审核申请
|
|
@@ -1367,33 +1447,42 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1367
1447
|
}
|
|
1368
1448
|
|
|
1369
1449
|
let rejectedCount = 0
|
|
1450
|
+
let skippedCount = 0
|
|
1370
1451
|
for (const request of pendingRequests) {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1452
|
+
if (request.requestId) {
|
|
1453
|
+
try {
|
|
1454
|
+
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1455
|
+
rejectedCount++
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
logger.warn(`处理申请 ${request.id} 时出错:`, error)
|
|
1458
|
+
}
|
|
1459
|
+
} else {
|
|
1460
|
+
skippedCount++
|
|
1379
1461
|
}
|
|
1462
|
+
await ctx.database.remove('group_verification_pending', { id: request.id })
|
|
1463
|
+
await updateStats(ctx, groupId, 'rejected')
|
|
1380
1464
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1465
|
+
let msg = `已拒绝 ${rejectedCount} 个加群申请`
|
|
1466
|
+
if (skippedCount) msg += `,${skippedCount} 个因缺少 requestId 未处理`;
|
|
1467
|
+
return msg
|
|
1383
1468
|
} else {
|
|
1384
|
-
//
|
|
1385
|
-
|
|
1386
|
-
if (
|
|
1469
|
+
// 拒绝最近的一个申请(按 applyTime 降序)
|
|
1470
|
+
let pending: any[] = await ctx.database.get('group_verification_pending', { groupId }, ['id', 'userId', 'userName', 'applyTime', 'requestId'])
|
|
1471
|
+
if (pending.length === 0) {
|
|
1387
1472
|
return '当前无待审核的加群申请'
|
|
1388
1473
|
}
|
|
1389
|
-
|
|
1390
|
-
const request =
|
|
1474
|
+
pending.sort((a, b) => String(b.applyTime).localeCompare(String(a.applyTime)))
|
|
1475
|
+
const request: any = pending[0]
|
|
1476
|
+
if (!request.requestId) {
|
|
1477
|
+
return `用户 ${request.userId} 的申请缺少 requestId,无法自动拒绝`;
|
|
1478
|
+
}
|
|
1391
1479
|
try {
|
|
1392
|
-
|
|
1393
|
-
//
|
|
1394
|
-
await ctx.database.remove('group_verification_pending', {
|
|
1480
|
+
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1481
|
+
// 删除该用户的所有记录
|
|
1482
|
+
await ctx.database.remove('group_verification_pending', { groupId, userId: request.userId })
|
|
1395
1483
|
await updateStats(ctx, groupId, 'rejected')
|
|
1396
|
-
|
|
1484
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1485
|
+
return `已拒绝用户 ${displayName} 的加群申请`
|
|
1397
1486
|
} catch (error) {
|
|
1398
1487
|
return `处理申请时出错: ${error.message}`
|
|
1399
1488
|
}
|
|
@@ -1411,12 +1500,16 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1411
1500
|
}
|
|
1412
1501
|
|
|
1413
1502
|
const request = pendingRequests[0]
|
|
1503
|
+
if (!request.requestId) {
|
|
1504
|
+
return `用户 ${userId} 的申请缺少 requestId,无法自动拒绝`
|
|
1505
|
+
}
|
|
1414
1506
|
try {
|
|
1415
|
-
|
|
1416
|
-
//
|
|
1417
|
-
await ctx.database.remove('group_verification_pending', {
|
|
1507
|
+
await session.bot.handleGuildMemberRequest(request.requestId, false)
|
|
1508
|
+
// 删除该用户的所有记录
|
|
1509
|
+
await ctx.database.remove('group_verification_pending', { groupId, userId })
|
|
1418
1510
|
await updateStats(ctx, groupId, 'rejected')
|
|
1419
|
-
|
|
1511
|
+
const displayName = request.userName && request.userName !== request.userId ? `${request.userName}(${request.userId})` : request.userId
|
|
1512
|
+
return `已拒绝用户 ${displayName} 的加群申请`
|
|
1420
1513
|
} catch (error) {
|
|
1421
1514
|
return `处理申请时出错: ${error.message}`
|
|
1422
1515
|
}
|
|
@@ -1591,6 +1684,8 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1591
1684
|
for (const st of stats) {
|
|
1592
1685
|
const updates: any = {}
|
|
1593
1686
|
if (st.lastUpdated instanceof Date) updates.lastUpdated = st.lastUpdated.toISOString()
|
|
1687
|
+
// add missing totalJoined column default 0
|
|
1688
|
+
if (st.totalJoined === undefined) updates.totalJoined = 0
|
|
1594
1689
|
if (Object.keys(updates).length) {
|
|
1595
1690
|
await ctx.database.set('group_verification_stats', { id: st.id }, updates)
|
|
1596
1691
|
}
|
|
@@ -1599,6 +1694,8 @@ export function apply(ctx: Context, config: Config) {
|
|
|
1599
1694
|
for (const p of pendings) {
|
|
1600
1695
|
const updates: any = {}
|
|
1601
1696
|
if (p.applyTime instanceof Date) updates.applyTime = p.applyTime.toISOString()
|
|
1697
|
+
// add default empty requestId if record pre-dates the new column
|
|
1698
|
+
if (p.requestId === undefined) updates.requestId = ''
|
|
1602
1699
|
if (Object.keys(updates).length) {
|
|
1603
1700
|
await ctx.database.set('group_verification_pending', { id: p.id }, updates)
|
|
1604
1701
|
}
|