koishi-plugin-group-verification 1.0.2 → 1.0.4
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 +2 -0
- package/lib/index.js +139 -60
- package/package.json +4 -4
- package/src/index.ts +193 -66
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -36,6 +36,8 @@ function apply(ctx, config) {
|
|
|
36
36
|
reviewMethod: "integer",
|
|
37
37
|
reviewParameters: "json",
|
|
38
38
|
reminderMessage: "string",
|
|
39
|
+
createdBy: "string",
|
|
40
|
+
updatedBy: "string",
|
|
39
41
|
createdAt: "date",
|
|
40
42
|
updatedAt: "date"
|
|
41
43
|
}, {
|
|
@@ -75,7 +77,7 @@ function apply(ctx, config) {
|
|
|
75
77
|
return;
|
|
76
78
|
}
|
|
77
79
|
const config2 = groupConfig[0];
|
|
78
|
-
const isValid = await verifyApplication(config2, message);
|
|
80
|
+
const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config2, message, session);
|
|
79
81
|
if (isValid) {
|
|
80
82
|
await updateStats(guildId, "autoApproved");
|
|
81
83
|
} else {
|
|
@@ -87,6 +89,13 @@ function apply(ctx, config) {
|
|
|
87
89
|
const userId = session.userId;
|
|
88
90
|
const username = session.username || "未知用户";
|
|
89
91
|
const message = session.content || "";
|
|
92
|
+
let groupName = "未知群组";
|
|
93
|
+
try {
|
|
94
|
+
const guild = await session.bot.getGuild(guildId);
|
|
95
|
+
groupName = guild.name || groupName;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
}
|
|
98
|
+
const { matchedCount, requiredThreshold } = await verifyApplication(config2, message, session);
|
|
90
99
|
await ctx2.database.create("group_verification_pending", {
|
|
91
100
|
groupId: guildId,
|
|
92
101
|
userId,
|
|
@@ -94,30 +103,41 @@ function apply(ctx, config) {
|
|
|
94
103
|
requestMessage: message,
|
|
95
104
|
applyTime: /* @__PURE__ */ new Date()
|
|
96
105
|
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
请管理员使用 #同意 或 #拒绝 来处理此申请`;
|
|
106
|
+
let reminderMsg = config2.reminderMessage;
|
|
107
|
+
reminderMsg = reminderMsg.replace(/{user}/g, username).replace(/{id}/g, userId).replace(/{group}/g, guildId).replace(/{gname}/g, groupName).replace(/{question}/g, message).replace(/{answer}/g, matchedCount.toString()).replace(/{threshold}/g, requiredThreshold);
|
|
100
108
|
await ctx2.broadcast([guildId], reminderMsg);
|
|
101
109
|
}
|
|
102
110
|
__name(handleFailedVerification, "handleFailedVerification");
|
|
103
|
-
async function verifyApplication(config2, message) {
|
|
111
|
+
async function verifyApplication(config2, message, session) {
|
|
104
112
|
const matchedCount = config2.keywords.filter(
|
|
105
113
|
(keyword) => message.toLowerCase().includes(keyword.toLowerCase())
|
|
106
114
|
).length;
|
|
115
|
+
let isValid = false;
|
|
116
|
+
let requiredThreshold = "";
|
|
107
117
|
switch (config2.reviewMethod) {
|
|
108
118
|
case 0:
|
|
109
|
-
|
|
119
|
+
isValid = true;
|
|
120
|
+
requiredThreshold = "null";
|
|
121
|
+
break;
|
|
110
122
|
case 1:
|
|
111
|
-
|
|
123
|
+
isValid = matchedCount >= (config2.reviewParameters.threshold || 1);
|
|
124
|
+
requiredThreshold = `${config2.reviewParameters.threshold || 1}`;
|
|
125
|
+
break;
|
|
112
126
|
case 2:
|
|
113
127
|
const ratio = matchedCount / config2.keywords.length;
|
|
114
128
|
const requiredRatio = (config2.reviewParameters.threshold || 100) / 100;
|
|
115
|
-
|
|
129
|
+
isValid = ratio >= requiredRatio;
|
|
130
|
+
requiredThreshold = `${config2.reviewParameters.threshold || 100}%`;
|
|
131
|
+
break;
|
|
116
132
|
case 3:
|
|
117
|
-
|
|
133
|
+
isValid = false;
|
|
134
|
+
requiredThreshold = "null";
|
|
135
|
+
break;
|
|
118
136
|
default:
|
|
119
|
-
|
|
137
|
+
isValid = false;
|
|
138
|
+
requiredThreshold = "null";
|
|
120
139
|
}
|
|
140
|
+
return { isValid, matchedCount, requiredThreshold };
|
|
121
141
|
}
|
|
122
142
|
__name(verifyApplication, "verifyApplication");
|
|
123
143
|
async function updateStats(groupId, statType) {
|
|
@@ -157,29 +177,53 @@ function apply(ctx, config) {
|
|
|
157
177
|
__name(updateStats, "updateStats");
|
|
158
178
|
async function checkPermission(session, targetGroupId) {
|
|
159
179
|
const groupId = targetGroupId || session.guildId;
|
|
160
|
-
if (groupId) {
|
|
161
|
-
|
|
162
|
-
const member = await session.bot.getGuildMember(groupId, session.userId);
|
|
163
|
-
if (member && (member.roles?.includes("admin") || member.permissions?.includes("ADMINISTRATOR"))) {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
}
|
|
180
|
+
if (!groupId) {
|
|
181
|
+
return [false, "请在群聊中使用此命令或使用 -i 参数指定群号"];
|
|
168
182
|
}
|
|
183
|
+
console.log(`[权限检查] 用户ID: ${session.userId}, 群号: ${groupId}`);
|
|
184
|
+
console.log(`[权限检查] Koishi权限等级: ${session.author?.authority || "未获取到"}`);
|
|
169
185
|
if (session.author?.authority && session.author.authority >= 3) {
|
|
170
|
-
|
|
186
|
+
console.log(`[权限检查] 通过koishi权限检查: ${session.author.authority}`);
|
|
187
|
+
return [true];
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const member = await session.bot.getGuildMember(groupId, session.userId);
|
|
191
|
+
console.log(`[权限检查] 获取到成员信息:`, {
|
|
192
|
+
roles: member?.roles,
|
|
193
|
+
permissions: member?.permissions
|
|
194
|
+
});
|
|
195
|
+
if (member) {
|
|
196
|
+
if (member.permissions?.includes("OWNER")) {
|
|
197
|
+
console.log(`[权限检查] 用户是群主`);
|
|
198
|
+
return [true];
|
|
199
|
+
}
|
|
200
|
+
if (member.roles?.includes("admin") || member.permissions?.includes("ADMINISTRATOR")) {
|
|
201
|
+
console.log(`[权限检查] 用户是管理员`);
|
|
202
|
+
return [true];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.log(`[权限检查] 获取群成员信息失败:`, error);
|
|
207
|
+
return [false, `无法获取群 ${groupId} 的成员信息,请确认机器人已在该群中`];
|
|
171
208
|
}
|
|
172
|
-
|
|
209
|
+
console.log(`[权限检查] 权限不足`);
|
|
210
|
+
return [false, "权限不足:需要群主/管理员权限或koishi三级以上权限"];
|
|
173
211
|
}
|
|
174
212
|
__name(checkPermission, "checkPermission");
|
|
175
|
-
const groupVerify = ctx.command("group-verify", "群组验证管理命令");
|
|
176
|
-
groupVerify.subcommand(".config [keywords:text]", "配置群组验证规则").alias("cfg", "配置", "conf", "设置").option("groupId", "-i <groupId> 指定群号").option("method", "-m <method> 审核方式 (0-3)").option("threshold", "-t <threshold> 阈值参数").option("query", "-? 查询当前配置").option("remove", "-r 删除配置").action(async ({ session, options }, keywords) => {
|
|
213
|
+
const groupVerify = ctx.command("group-verify", "群组验证管理命令").alias("gv", "gverify");
|
|
214
|
+
groupVerify.subcommand(".config [keywords:text]", "配置群组验证规则").alias("cfg", "配置", "conf", "设置").option("groupId", "-i <groupId> 指定群号").option("method", "-m <method> 审核方式 (0-3)").option("threshold", "-t <threshold> 阈值参数").option("message", "-msg <message> 自定义提醒消息").option("query", "-? 查询当前配置").option("remove", "-r 删除配置").action(async ({ session, options }, keywords) => {
|
|
177
215
|
const targetGroupId = options.groupId || session.guildId;
|
|
178
|
-
|
|
179
|
-
|
|
216
|
+
const [hasPermission, errorMsg] = await checkPermission(session, targetGroupId);
|
|
217
|
+
if (!hasPermission) {
|
|
218
|
+
return errorMsg || "权限不足";
|
|
180
219
|
}
|
|
181
|
-
|
|
182
|
-
|
|
220
|
+
let groupName = "未知群组";
|
|
221
|
+
if (targetGroupId) {
|
|
222
|
+
try {
|
|
223
|
+
const guild = await session.bot.getGuild(targetGroupId);
|
|
224
|
+
groupName = guild.name || groupName;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
}
|
|
183
227
|
}
|
|
184
228
|
if (options.remove) {
|
|
185
229
|
const existingConfig2 = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
@@ -195,34 +239,41 @@ function apply(ctx, config) {
|
|
|
195
239
|
if (existingConfig2.length > 0) {
|
|
196
240
|
const config2 = existingConfig2[0];
|
|
197
241
|
let methodDesc = "";
|
|
242
|
+
let thresholdInfo = "";
|
|
198
243
|
switch (config2.reviewMethod) {
|
|
199
244
|
case 0:
|
|
200
245
|
methodDesc = "全部同意";
|
|
246
|
+
thresholdInfo = "null";
|
|
201
247
|
break;
|
|
202
248
|
case 1:
|
|
203
|
-
methodDesc =
|
|
249
|
+
methodDesc = `按数量同意`;
|
|
250
|
+
thresholdInfo = `${config2.reviewParameters.threshold || 1}`;
|
|
204
251
|
break;
|
|
205
252
|
case 2:
|
|
206
|
-
methodDesc =
|
|
253
|
+
methodDesc = `按比例同意`;
|
|
254
|
+
thresholdInfo = `${config2.reviewParameters.threshold || 100}%`;
|
|
207
255
|
break;
|
|
208
256
|
case 3:
|
|
209
257
|
methodDesc = "全部拒绝";
|
|
258
|
+
thresholdInfo = "null";
|
|
210
259
|
break;
|
|
211
260
|
}
|
|
261
|
+
const createTime = new Date(config2.createdAt).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
262
|
+
const updateTime = new Date(config2.updatedAt).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
|
212
263
|
return `群 ${targetGroupId} 配置:
|
|
213
264
|
关键词: ${config2.keywords.join(", ")}
|
|
214
|
-
审核方式: ${methodDesc}
|
|
265
|
+
审核方式: ${methodDesc}
|
|
266
|
+
阈值: ${thresholdInfo}
|
|
267
|
+
提醒消息: ${config2.reminderMessage}
|
|
268
|
+
创建时间: ${createTime}
|
|
269
|
+
更新时间: ${updateTime}
|
|
270
|
+
创建者: ${config2.createdBy}
|
|
271
|
+
更新者: ${config2.updatedBy}`;
|
|
215
272
|
} else {
|
|
216
273
|
return `群 ${targetGroupId} 无验证配置`;
|
|
217
274
|
}
|
|
218
275
|
}
|
|
219
|
-
|
|
220
|
-
return "请提供关键词参数,或使用 -? 查询配置,-r 删除配置";
|
|
221
|
-
}
|
|
222
|
-
const keywordList = keywords.split(",").map((k) => k.trim()).filter((k) => k);
|
|
223
|
-
if (keywordList.length === 0) {
|
|
224
|
-
return "请提供有效的关键词";
|
|
225
|
-
}
|
|
276
|
+
const keywordList = keywords ? keywords.split(",").map((k) => k.trim()).filter((k) => k) : [];
|
|
226
277
|
let reviewMethod = 0;
|
|
227
278
|
let reviewParameters = {};
|
|
228
279
|
if (options.method !== void 0) {
|
|
@@ -249,25 +300,30 @@ function apply(ctx, config) {
|
|
|
249
300
|
}
|
|
250
301
|
}
|
|
251
302
|
}
|
|
303
|
+
let reminderMessage = options.message || `用户 {user}({id}) 申请加入群 {gname}({group})
|
|
304
|
+
申请理由:{question}
|
|
305
|
+
答对情况:{answer}
|
|
306
|
+
阈值要求:{threshold}
|
|
307
|
+
请管理员使用 #同意 或 #拒绝 来处理此申请`;
|
|
308
|
+
reminderMessage = reminderMessage.replace(/\\n/g, "\n");
|
|
252
309
|
const existingConfig = await ctx.database.get("group_verification_config", { groupId: targetGroupId });
|
|
310
|
+
const configData = {
|
|
311
|
+
keywords: keywordList,
|
|
312
|
+
reviewMethod,
|
|
313
|
+
reviewParameters,
|
|
314
|
+
reminderMessage,
|
|
315
|
+
updatedBy: session.username || session.userId,
|
|
316
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
317
|
+
};
|
|
253
318
|
if (existingConfig.length > 0) {
|
|
254
|
-
await ctx.database.set("group_verification_config", { id: existingConfig[0].id },
|
|
255
|
-
keywords: keywordList,
|
|
256
|
-
reviewMethod,
|
|
257
|
-
reviewParameters,
|
|
258
|
-
reminderMessage: `New join request received. Please use #approve or #reject to handle this request`,
|
|
259
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
260
|
-
});
|
|
319
|
+
await ctx.database.set("group_verification_config", { id: existingConfig[0].id }, configData);
|
|
261
320
|
return `已更新群 ${targetGroupId} 的验证配置`;
|
|
262
321
|
} else {
|
|
263
322
|
await ctx.database.create("group_verification_config", {
|
|
323
|
+
...configData,
|
|
264
324
|
groupId: targetGroupId,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
reviewParameters,
|
|
268
|
-
reminderMessage: `New join request received. Please use #approve or #reject to handle this request`,
|
|
269
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
270
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
325
|
+
createdBy: session.username || session.userId,
|
|
326
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
271
327
|
});
|
|
272
328
|
return `已为群 ${targetGroupId} 创建验证配置`;
|
|
273
329
|
}
|
|
@@ -357,19 +413,42 @@ function apply(ctx, config) {
|
|
|
357
413
|
});
|
|
358
414
|
groupVerify.subcommand(".help", "显示帮助信息").alias("帮助", "hlp", "帮助信息").action(() => {
|
|
359
415
|
return `群组验证命令帮助:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
416
|
+
主指令别名:gv, gverify
|
|
417
|
+
|
|
418
|
+
配置命令 (.config):
|
|
419
|
+
选项:
|
|
420
|
+
-i <群号> 指定群号(私聊时必需)
|
|
421
|
+
-m <方式> 审核方式 (0=全部同意, 1=按数量, 2=按比例, 3=全部拒绝)
|
|
422
|
+
-t <阈值> 阈值参数(方式1或2时必需)
|
|
423
|
+
-msg <消息> 自定义提醒消息
|
|
424
|
+
-? 查询当前配置
|
|
425
|
+
-r 删除配置
|
|
426
|
+
|
|
427
|
+
示例:
|
|
428
|
+
gv.config 关键词1,关键词2 -m 1 -t 2
|
|
429
|
+
gv.config -i 123456789 -?
|
|
430
|
+
gv.config -r -i 123456789
|
|
431
|
+
|
|
432
|
+
提醒消息变量:
|
|
433
|
+
{user} - 用户名
|
|
434
|
+
{id} - 用户ID
|
|
435
|
+
{group} - 群号
|
|
436
|
+
{gname} - 群名称
|
|
437
|
+
{question} - 申请理由
|
|
438
|
+
{answer} - 答对数量/比例
|
|
439
|
+
{threshold} - 阈值要求
|
|
440
|
+
\\n - 换行符
|
|
364
441
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
.help - 显示此帮助信息
|
|
442
|
+
权限说明:
|
|
443
|
+
- 群主/管理员权限
|
|
444
|
+
- koishi三级以上权限
|
|
445
|
+
- 私聊时必须指定群号(-i参数)
|
|
370
446
|
|
|
371
|
-
|
|
372
|
-
|
|
447
|
+
其他命令:
|
|
448
|
+
.approve <用户ID> - 同意加群申请
|
|
449
|
+
.reject <用户ID> - 拒绝加群申请
|
|
450
|
+
.pending - 查看待审核列表
|
|
451
|
+
.stats [群号] - 查看统计信息`;
|
|
373
452
|
});
|
|
374
453
|
}
|
|
375
454
|
__name(apply, "apply");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-group-verification",
|
|
3
3
|
"description": "Koishi 群组加群验证插件,支持多关键词匹配审核、多种审核方式和详细统计功能",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"koishi": "^4.
|
|
37
|
+
"koishi": "^4.15.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"typescript": "^
|
|
41
|
-
"@types/node": "^
|
|
40
|
+
"typescript": "^4.9.0",
|
|
41
|
+
"@types/node": "^16.0.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface GroupVerificationConfig {
|
|
|
21
21
|
threshold?: number // 数量阈值或比例阈值
|
|
22
22
|
}
|
|
23
23
|
reminderMessage: string
|
|
24
|
+
createdBy: string
|
|
25
|
+
updatedBy: string
|
|
24
26
|
createdAt: Date
|
|
25
27
|
updatedAt: Date
|
|
26
28
|
}
|
|
@@ -58,6 +60,8 @@ export function apply(ctx: Context, config: Config) {
|
|
|
58
60
|
reviewMethod: 'integer',
|
|
59
61
|
reviewParameters: 'json',
|
|
60
62
|
reminderMessage: 'string',
|
|
63
|
+
createdBy: 'string',
|
|
64
|
+
updatedBy: 'string',
|
|
61
65
|
createdAt: 'date',
|
|
62
66
|
updatedAt: 'date'
|
|
63
67
|
}, {
|
|
@@ -110,7 +114,7 @@ export function apply(ctx: Context, config: Config) {
|
|
|
110
114
|
const config = groupConfig[0]
|
|
111
115
|
|
|
112
116
|
// 执行验证
|
|
113
|
-
const isValid = await verifyApplication(config, message)
|
|
117
|
+
const { isValid, matchedCount, requiredThreshold } = await verifyApplication(config, message, session)
|
|
114
118
|
|
|
115
119
|
if (isValid) {
|
|
116
120
|
// 验证成功,自动同意入群
|
|
@@ -131,6 +135,18 @@ export function apply(ctx: Context, config: Config) {
|
|
|
131
135
|
const username = session.username || '未知用户'
|
|
132
136
|
const message = session.content || ''
|
|
133
137
|
|
|
138
|
+
// 获取群信息
|
|
139
|
+
let groupName = '未知群组'
|
|
140
|
+
try {
|
|
141
|
+
const guild = await session.bot.getGuild(guildId)
|
|
142
|
+
groupName = guild.name || groupName
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// 无法获取群名称时使用默认值
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 执行验证获取详细信息
|
|
148
|
+
const { matchedCount, requiredThreshold } = await verifyApplication(config, message, session)
|
|
149
|
+
|
|
134
150
|
// 将申请加入待审核列表
|
|
135
151
|
await ctx.database.create('group_verification_pending', {
|
|
136
152
|
groupId: guildId,
|
|
@@ -140,32 +156,56 @@ export function apply(ctx: Context, config: Config) {
|
|
|
140
156
|
applyTime: new Date()
|
|
141
157
|
})
|
|
142
158
|
|
|
159
|
+
// 替换提醒消息中的变量
|
|
160
|
+
let reminderMsg = config.reminderMessage
|
|
161
|
+
reminderMsg = reminderMsg
|
|
162
|
+
.replace(/{user}/g, username)
|
|
163
|
+
.replace(/{id}/g, userId)
|
|
164
|
+
.replace(/{group}/g, guildId)
|
|
165
|
+
.replace(/{gname}/g, groupName)
|
|
166
|
+
.replace(/{question}/g, message)
|
|
167
|
+
.replace(/{answer}/g, matchedCount.toString())
|
|
168
|
+
.replace(/{threshold}/g, requiredThreshold)
|
|
169
|
+
|
|
143
170
|
// 发送提醒消息到群内
|
|
144
|
-
const reminderMsg = config.reminderMessage || `收到新的加群申请:${username}(${userId})\n申请理由:${message}\n请管理员使用 #同意 或 #拒绝 来处理此申请`
|
|
145
171
|
await ctx.broadcast([guildId], reminderMsg)
|
|
146
172
|
}
|
|
147
173
|
|
|
148
174
|
// 验证申请
|
|
149
|
-
async function verifyApplication(config: GroupVerificationConfig, message: string): Promise<boolean> {
|
|
175
|
+
async function verifyApplication(config: GroupVerificationConfig, message: string, session: any): Promise<{isValid: boolean, matchedCount: number, requiredThreshold: string}> {
|
|
150
176
|
// 统计匹配的关键词数量
|
|
151
177
|
const matchedCount = config.keywords.filter(keyword =>
|
|
152
178
|
message.toLowerCase().includes(keyword.toLowerCase())
|
|
153
179
|
).length
|
|
154
180
|
|
|
181
|
+
let isValid = false
|
|
182
|
+
let requiredThreshold = ''
|
|
183
|
+
|
|
155
184
|
switch (config.reviewMethod) {
|
|
156
185
|
case 0: // 全部同意
|
|
157
|
-
|
|
186
|
+
isValid = true
|
|
187
|
+
requiredThreshold = 'null'
|
|
188
|
+
break
|
|
158
189
|
case 1: // 按数量同意
|
|
159
|
-
|
|
190
|
+
isValid = matchedCount >= (config.reviewParameters.threshold || 1)
|
|
191
|
+
requiredThreshold = `${config.reviewParameters.threshold || 1}`
|
|
192
|
+
break
|
|
160
193
|
case 2: // 按比例同意
|
|
161
194
|
const ratio = matchedCount / config.keywords.length
|
|
162
195
|
const requiredRatio = (config.reviewParameters.threshold || 100) / 100
|
|
163
|
-
|
|
196
|
+
isValid = ratio >= requiredRatio
|
|
197
|
+
requiredThreshold = `${config.reviewParameters.threshold || 100}%`
|
|
198
|
+
break
|
|
164
199
|
case 3: // 全部拒绝
|
|
165
|
-
|
|
200
|
+
isValid = false
|
|
201
|
+
requiredThreshold = 'null'
|
|
202
|
+
break
|
|
166
203
|
default:
|
|
167
|
-
|
|
204
|
+
isValid = false
|
|
205
|
+
requiredThreshold = 'null'
|
|
168
206
|
}
|
|
207
|
+
|
|
208
|
+
return { isValid, matchedCount, requiredThreshold }
|
|
169
209
|
}
|
|
170
210
|
|
|
171
211
|
// 更新统计信息
|
|
@@ -210,31 +250,56 @@ export function apply(ctx: Context, config: Config) {
|
|
|
210
250
|
}
|
|
211
251
|
|
|
212
252
|
// 权限检查函数
|
|
213
|
-
async function checkPermission(session: any, targetGroupId?: string): Promise<boolean> {
|
|
253
|
+
async function checkPermission(session: any, targetGroupId?: string): Promise<[boolean, string?]> {
|
|
214
254
|
const groupId = targetGroupId || session.guildId
|
|
215
255
|
|
|
216
|
-
//
|
|
217
|
-
if (groupId) {
|
|
218
|
-
|
|
219
|
-
const member = await session.bot.getGuildMember(groupId, session.userId)
|
|
220
|
-
if (member && (member.roles?.includes('admin') || member.permissions?.includes('ADMINISTRATOR'))) {
|
|
221
|
-
return true
|
|
222
|
-
}
|
|
223
|
-
} catch (error) {
|
|
224
|
-
// 如果无法获取群成员信息,继续检查其他权限
|
|
225
|
-
}
|
|
256
|
+
// 私聊情况下必须指定群号
|
|
257
|
+
if (!groupId) {
|
|
258
|
+
return [false, '请在群聊中使用此命令或使用 -i 参数指定群号']
|
|
226
259
|
}
|
|
227
260
|
|
|
261
|
+
// 调试日志:输出权限检查信息
|
|
262
|
+
console.log(`[权限检查] 用户ID: ${session.userId}, 群号: ${groupId}`)
|
|
263
|
+
console.log(`[权限检查] Koishi权限等级: ${session.author?.authority || '未获取到'}`)
|
|
264
|
+
|
|
228
265
|
// 检查koishi权限等级(三级及以上)
|
|
229
266
|
if (session.author?.authority && session.author.authority >= 3) {
|
|
230
|
-
|
|
267
|
+
console.log(`[权限检查] 通过koishi权限检查: ${session.author.authority}`)
|
|
268
|
+
return [true]
|
|
231
269
|
}
|
|
232
270
|
|
|
233
|
-
|
|
271
|
+
// 检查是否为群主或管理员
|
|
272
|
+
try {
|
|
273
|
+
const member = await session.bot.getGuildMember(groupId, session.userId)
|
|
274
|
+
console.log(`[权限检查] 获取到成员信息:`, {
|
|
275
|
+
roles: member?.roles,
|
|
276
|
+
permissions: member?.permissions
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
if (member) {
|
|
280
|
+
// 检查群主权限
|
|
281
|
+
if (member.permissions?.includes('OWNER')) {
|
|
282
|
+
console.log(`[权限检查] 用户是群主`)
|
|
283
|
+
return [true]
|
|
284
|
+
}
|
|
285
|
+
// 检查管理员权限
|
|
286
|
+
if (member.roles?.includes('admin') || member.permissions?.includes('ADMINISTRATOR')) {
|
|
287
|
+
console.log(`[权限检查] 用户是管理员`)
|
|
288
|
+
return [true]
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.log(`[权限检查] 获取群成员信息失败:`, error)
|
|
293
|
+
return [false, `无法获取群 ${groupId} 的成员信息,请确认机器人已在该群中`]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log(`[权限检查] 权限不足`)
|
|
297
|
+
return [false, '权限不足:需要群主/管理员权限或koishi三级以上权限']
|
|
234
298
|
}
|
|
235
299
|
|
|
236
|
-
// Create main command
|
|
300
|
+
// Create main command with aliases
|
|
237
301
|
const groupVerify = ctx.command('group-verify', '群组验证管理命令')
|
|
302
|
+
.alias('gv', 'gverify')
|
|
238
303
|
|
|
239
304
|
// Subcommand: configure group verification
|
|
240
305
|
groupVerify
|
|
@@ -243,18 +308,27 @@ export function apply(ctx: Context, config: Config) {
|
|
|
243
308
|
.option('groupId', '-i <groupId> 指定群号')
|
|
244
309
|
.option('method', '-m <method> 审核方式 (0-3)')
|
|
245
310
|
.option('threshold', '-t <threshold> 阈值参数')
|
|
311
|
+
.option('message', '-msg <message> 自定义提醒消息')
|
|
246
312
|
.option('query', '-? 查询当前配置')
|
|
247
313
|
.option('remove', '-r 删除配置')
|
|
248
314
|
.action(async ({ session, options }, keywords) => {
|
|
249
315
|
const targetGroupId = options.groupId || session.guildId
|
|
250
316
|
|
|
251
317
|
// 权限检查
|
|
252
|
-
|
|
253
|
-
|
|
318
|
+
const [hasPermission, errorMsg] = await checkPermission(session, targetGroupId)
|
|
319
|
+
if (!hasPermission) {
|
|
320
|
+
return errorMsg || '权限不足'
|
|
254
321
|
}
|
|
255
322
|
|
|
256
|
-
|
|
257
|
-
|
|
323
|
+
// 获取群信息
|
|
324
|
+
let groupName = '未知群组'
|
|
325
|
+
if (targetGroupId) {
|
|
326
|
+
try {
|
|
327
|
+
const guild = await session.bot.getGuild(targetGroupId)
|
|
328
|
+
groupName = guild.name || groupName
|
|
329
|
+
} catch (error) {
|
|
330
|
+
// 无法获取群名称时使用默认值
|
|
331
|
+
}
|
|
258
332
|
}
|
|
259
333
|
|
|
260
334
|
// 处理删除配置
|
|
@@ -274,30 +348,47 @@ export function apply(ctx: Context, config: Config) {
|
|
|
274
348
|
if (existingConfig.length > 0) {
|
|
275
349
|
const config = existingConfig[0]
|
|
276
350
|
let methodDesc = ''
|
|
351
|
+
let thresholdInfo = ''
|
|
277
352
|
switch (config.reviewMethod) {
|
|
278
|
-
case 0:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
353
|
+
case 0:
|
|
354
|
+
methodDesc = '全部同意'
|
|
355
|
+
thresholdInfo = 'null'
|
|
356
|
+
break
|
|
357
|
+
case 1:
|
|
358
|
+
methodDesc = `按数量同意`
|
|
359
|
+
thresholdInfo = `${config.reviewParameters.threshold || 1}`
|
|
360
|
+
break
|
|
361
|
+
case 2:
|
|
362
|
+
methodDesc = `按比例同意`
|
|
363
|
+
thresholdInfo = `${config.reviewParameters.threshold || 100}%`
|
|
364
|
+
break
|
|
365
|
+
case 3:
|
|
366
|
+
methodDesc = '全部拒绝'
|
|
367
|
+
thresholdInfo = 'null'
|
|
368
|
+
break
|
|
282
369
|
}
|
|
283
|
-
|
|
370
|
+
|
|
371
|
+
const createTime = new Date(config.createdAt).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
372
|
+
const updateTime = new Date(config.updatedAt).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
|
373
|
+
|
|
374
|
+
return `群 ${targetGroupId} 配置:
|
|
375
|
+
关键词: ${config.keywords.join(', ')}
|
|
376
|
+
审核方式: ${methodDesc}
|
|
377
|
+
阈值: ${thresholdInfo}
|
|
378
|
+
提醒消息: ${config.reminderMessage}
|
|
379
|
+
创建时间: ${createTime}
|
|
380
|
+
更新时间: ${updateTime}
|
|
381
|
+
创建者: ${config.createdBy}
|
|
382
|
+
更新者: ${config.updatedBy}`
|
|
284
383
|
} else {
|
|
285
384
|
return `群 ${targetGroupId} 无验证配置`
|
|
286
385
|
}
|
|
287
386
|
}
|
|
288
387
|
|
|
289
388
|
// 处理配置创建/更新
|
|
290
|
-
|
|
291
|
-
return '请提供关键词参数,或使用 -? 查询配置,-r 删除配置'
|
|
292
|
-
}
|
|
389
|
+
const keywordList = keywords ? keywords.split(',').map(k => k.trim()).filter(k => k) : []
|
|
293
390
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (keywordList.length === 0) {
|
|
297
|
-
return '请提供有效的关键词'
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// 解析审核方式
|
|
391
|
+
// 解析审核方式和阈值
|
|
301
392
|
let reviewMethod: 0 | 1 | 2 | 3 = 0 // 默认全部同意
|
|
302
393
|
let reviewParameters: { threshold?: number } = {}
|
|
303
394
|
|
|
@@ -329,29 +420,40 @@ export function apply(ctx: Context, config: Config) {
|
|
|
329
420
|
}
|
|
330
421
|
}
|
|
331
422
|
|
|
423
|
+
// 处理提醒消息
|
|
424
|
+
let reminderMessage = options.message ||
|
|
425
|
+
`用户 {user}({id}) 申请加入群 {gname}({group})
|
|
426
|
+
申请理由:{question}
|
|
427
|
+
答对情况:{answer}
|
|
428
|
+
阈值要求:{threshold}
|
|
429
|
+
请管理员使用 #同意 或 #拒绝 来处理此申请`
|
|
430
|
+
|
|
431
|
+
// 替换换行符
|
|
432
|
+
reminderMessage = reminderMessage.replace(/\\n/g, '\n')
|
|
433
|
+
|
|
332
434
|
// Check if configuration already exists
|
|
333
435
|
const existingConfig = await ctx.database.get('group_verification_config', { groupId: targetGroupId })
|
|
334
436
|
|
|
437
|
+
const configData = {
|
|
438
|
+
keywords: keywordList,
|
|
439
|
+
reviewMethod: reviewMethod,
|
|
440
|
+
reviewParameters: reviewParameters,
|
|
441
|
+
reminderMessage: reminderMessage,
|
|
442
|
+
updatedBy: session.username || session.userId,
|
|
443
|
+
updatedAt: new Date()
|
|
444
|
+
}
|
|
445
|
+
|
|
335
446
|
if (existingConfig.length > 0) {
|
|
336
447
|
// Update existing configuration
|
|
337
|
-
await ctx.database.set('group_verification_config', { id: existingConfig[0].id },
|
|
338
|
-
keywords: keywordList,
|
|
339
|
-
reviewMethod: reviewMethod,
|
|
340
|
-
reviewParameters: reviewParameters,
|
|
341
|
-
reminderMessage: `New join request received. Please use #approve or #reject to handle this request`,
|
|
342
|
-
updatedAt: new Date()
|
|
343
|
-
})
|
|
448
|
+
await ctx.database.set('group_verification_config', { id: existingConfig[0].id }, configData)
|
|
344
449
|
return `已更新群 ${targetGroupId} 的验证配置`
|
|
345
450
|
} else {
|
|
346
451
|
// Create new configuration
|
|
347
452
|
await ctx.database.create('group_verification_config', {
|
|
453
|
+
...configData,
|
|
348
454
|
groupId: targetGroupId,
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
reviewParameters: reviewParameters,
|
|
352
|
-
reminderMessage: `New join request received. Please use #approve or #reject to handle this request`,
|
|
353
|
-
createdAt: new Date(),
|
|
354
|
-
updatedAt: new Date()
|
|
455
|
+
createdBy: session.username || session.userId,
|
|
456
|
+
createdAt: new Date()
|
|
355
457
|
})
|
|
356
458
|
return `已为群 ${targetGroupId} 创建验证配置`
|
|
357
459
|
}
|
|
@@ -486,17 +588,42 @@ export function apply(ctx: Context, config: Config) {
|
|
|
486
588
|
.subcommand('.help', '显示帮助信息')
|
|
487
589
|
.alias('帮助', 'hlp', '帮助信息')
|
|
488
590
|
.action(() => {
|
|
489
|
-
return
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
591
|
+
return `群组验证命令帮助:
|
|
592
|
+
主指令别名:gv, gverify
|
|
593
|
+
|
|
594
|
+
配置命令 (.config):
|
|
595
|
+
选项:
|
|
596
|
+
-i <群号> 指定群号(私聊时必需)
|
|
597
|
+
-m <方式> 审核方式 (0=全部同意, 1=按数量, 2=按比例, 3=全部拒绝)
|
|
598
|
+
-t <阈值> 阈值参数(方式1或2时必需)
|
|
599
|
+
-msg <消息> 自定义提醒消息
|
|
600
|
+
-? 查询当前配置
|
|
601
|
+
-r 删除配置
|
|
602
|
+
|
|
603
|
+
示例:
|
|
604
|
+
gv.config 关键词1,关键词2 -m 1 -t 2
|
|
605
|
+
gv.config -i 123456789 -?
|
|
606
|
+
gv.config -r -i 123456789
|
|
607
|
+
|
|
608
|
+
提醒消息变量:
|
|
609
|
+
{user} - 用户名
|
|
610
|
+
{id} - 用户ID
|
|
611
|
+
{group} - 群号
|
|
612
|
+
{gname} - 群名称
|
|
613
|
+
{question} - 申请理由
|
|
614
|
+
{answer} - 答对数量/比例
|
|
615
|
+
{threshold} - 阈值要求
|
|
616
|
+
\\n - 换行符
|
|
617
|
+
|
|
618
|
+
权限说明:
|
|
619
|
+
- 群主/管理员权限
|
|
620
|
+
- koishi三级以上权限
|
|
621
|
+
- 私聊时必须指定群号(-i参数)
|
|
622
|
+
|
|
623
|
+
其他命令:
|
|
624
|
+
.approve <用户ID> - 同意加群申请
|
|
625
|
+
.reject <用户ID> - 拒绝加群申请
|
|
626
|
+
.pending - 查看待审核列表
|
|
627
|
+
.stats [群号] - 查看统计信息`
|
|
501
628
|
})
|
|
502
629
|
}
|