koishi-plugin-onebot-verifier 1.1.2 → 1.1.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 +1 -1
- package/lib/index.js +72 -85
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface Config {
|
|
|
21
21
|
minLevel?: number;
|
|
22
22
|
action?: 'accept' | 'reject';
|
|
23
23
|
}[];
|
|
24
|
-
syncNotify?: boolean;
|
|
25
24
|
specialRules?: {
|
|
26
25
|
guildId: string;
|
|
27
26
|
enabled: boolean;
|
|
@@ -29,6 +28,7 @@ export interface Config {
|
|
|
29
28
|
}[];
|
|
30
29
|
captchaDiff?: 'simple' | 'medium' | 'hard';
|
|
31
30
|
voteRatio?: string;
|
|
31
|
+
voteInSitu?: boolean;
|
|
32
32
|
}
|
|
33
33
|
export declare const Config: Schema<Config>;
|
|
34
34
|
export declare function apply(ctx: Context, config?: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -76,7 +76,6 @@ var Config = import_koishi.Schema.intersect([
|
|
|
76
76
|
})).description("普通验证配置").role("table")
|
|
77
77
|
}).description("加群请求配置"),
|
|
78
78
|
import_koishi.Schema.object({
|
|
79
|
-
syncNotify: import_koishi.Schema.boolean().description("同步通知目标").default(true),
|
|
80
79
|
specialRules: import_koishi.Schema.array(import_koishi.Schema.object({
|
|
81
80
|
guildId: import_koishi.Schema.string().description("群号").required(),
|
|
82
81
|
mode: import_koishi.Schema.union([
|
|
@@ -85,12 +84,13 @@ var Config = import_koishi.Schema.intersect([
|
|
|
85
84
|
]).description("模式").default("vote"),
|
|
86
85
|
enabled: import_koishi.Schema.boolean().description("前置规则").default(true)
|
|
87
86
|
})).description("配置列表").role("table"),
|
|
88
|
-
voteRatio: import_koishi.Schema.string().description("[投票]
|
|
87
|
+
voteRatio: import_koishi.Schema.string().description("[投票]支持/反对人数").default("3:2"),
|
|
88
|
+
voteInSitu: import_koishi.Schema.boolean().description("[投票]对应群中发起").default(true),
|
|
89
89
|
captchaDiff: import_koishi.Schema.union([
|
|
90
90
|
import_koishi.Schema.const("simple").description("简单"),
|
|
91
91
|
import_koishi.Schema.const("medium").description("中等"),
|
|
92
92
|
import_koishi.Schema.const("hard").description("困难")
|
|
93
|
-
]).description("[
|
|
93
|
+
]).description("[验证]难度").default("simple")
|
|
94
94
|
}).description("特殊验证配置")
|
|
95
95
|
]);
|
|
96
96
|
function apply(ctx, config = {}) {
|
|
@@ -129,8 +129,8 @@ function apply(ctx, config = {}) {
|
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
131
131
|
}, "executeAction");
|
|
132
|
-
const sendNotice = /* @__PURE__ */ __name(async (session, kind, status = "waiting") => {
|
|
133
|
-
const notifyConfig = config.notifyTarget || "";
|
|
132
|
+
const sendNotice = /* @__PURE__ */ __name(async (session, kind, status = "waiting", overrideTarget) => {
|
|
133
|
+
const notifyConfig = overrideTarget || config.notifyTarget || "";
|
|
134
134
|
const [targetType, targetId] = notifyConfig.split(":");
|
|
135
135
|
if (!targetId || !session.bot) return [];
|
|
136
136
|
try {
|
|
@@ -148,7 +148,7 @@ function apply(ctx, config = {}) {
|
|
|
148
148
|
if (adminId) infoLines.push(`管理:${adminInfo?.name ? `${adminInfo.name}(${adminId})` : adminId}`);
|
|
149
149
|
if (session.guildId) infoLines.push(`群组:${groupInfo?.name ? `${groupInfo.name}(${session.guildId})` : session.guildId}`);
|
|
150
150
|
if (eventData.comment) infoLines.push(`验证信息:${eventData.comment}`);
|
|
151
|
-
if (status === "waiting") infoLines.push(
|
|
151
|
+
if (status === "waiting" && kind !== "removed") infoLines.push(`回复"y/n"以同意/拒绝该请求`);
|
|
152
152
|
const content = infoLines.join("\n");
|
|
153
153
|
const msgIds = await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, content) : session.bot.sendMessage(targetId, content)) || [];
|
|
154
154
|
return msgIds;
|
|
@@ -157,22 +157,30 @@ function apply(ctx, config = {}) {
|
|
|
157
157
|
return [];
|
|
158
158
|
}
|
|
159
159
|
}, "sendNotice");
|
|
160
|
-
const setupManual = /* @__PURE__ */ __name(async (session, kind) => {
|
|
160
|
+
const setupManual = /* @__PURE__ */ __name(async (session, kind, specialMode, useInSitu) => {
|
|
161
161
|
const waitMinutes = config.timeout ?? 0;
|
|
162
162
|
const action = kind === "member" ? config.verifyMode : config.timeoutAction;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
let targetStr = config.notifyTarget || "";
|
|
164
|
+
if (useInSitu && kind === "member" && session.guildId) targetStr = `guild:${session.guildId}`;
|
|
165
|
+
const msgIds = await sendNotice(session, kind, "waiting", targetStr);
|
|
166
|
+
if (!msgIds?.length) return;
|
|
167
|
+
const task = { session, kind, messages: msgIds, specialMode, inSitu: useInSitu };
|
|
168
|
+
if (specialMode === "vote") {
|
|
169
|
+
const [yesStr, noStr] = config.voteRatio.split(":");
|
|
170
|
+
task.voteTarget = { yes: parseInt(yesStr) || 0, no: parseInt(noStr) || 0 };
|
|
171
|
+
task.votes = { yes: /* @__PURE__ */ new Set(), no: /* @__PURE__ */ new Set() };
|
|
172
|
+
}
|
|
173
|
+
msgIds.forEach((id) => activeTasks.set(id, task));
|
|
174
|
+
if (waitMinutes > 0 && (specialMode === "vote" || action !== "manual")) {
|
|
168
175
|
task.timer = setTimeout(async () => {
|
|
169
176
|
if (!activeTasks.has(msgIds[0])) return;
|
|
170
177
|
msgIds.forEach((id) => activeTasks.delete(id));
|
|
171
|
-
|
|
178
|
+
let finalAction = action;
|
|
179
|
+
if (specialMode === "vote" && finalAction === "manual") finalAction = "reject";
|
|
172
180
|
if (finalAction === "manual") return;
|
|
173
181
|
const isPass = finalAction === "accept";
|
|
174
182
|
await executeAction(session, kind, isPass, isPass ? "" : "等待超时,自动拒绝");
|
|
175
|
-
const [targetType, targetId] =
|
|
183
|
+
const [targetType, targetId] = targetStr.split(":");
|
|
176
184
|
if (targetId && session.bot) {
|
|
177
185
|
const statusText = `已自动${isPass ? "通过" : "拒绝"}该请求`;
|
|
178
186
|
await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, statusText) : session.bot.sendMessage(targetId, statusText)).catch(() => {
|
|
@@ -180,12 +188,6 @@ function apply(ctx, config = {}) {
|
|
|
180
188
|
}
|
|
181
189
|
if (config.debugMode) logger.info(`[操作] 等待超时,默认${isPass ? "通过" : "拒绝"}`);
|
|
182
190
|
}, waitMinutes * 6e4);
|
|
183
|
-
} else {
|
|
184
|
-
if (action === "manual" || !action) return await sendNotice(session, kind, "waiting");
|
|
185
|
-
const isPass = action === "accept";
|
|
186
|
-
await executeAction(session, kind, isPass, "等待超时,自动处理");
|
|
187
|
-
await sendNotice(session, kind, isPass ? "auto_pass" : "auto_reject");
|
|
188
|
-
if (config.debugMode) logger.info(`[操作] 无需等待,默认${isPass ? "通过" : "拒绝"}`);
|
|
189
191
|
}
|
|
190
192
|
}, "setupManual");
|
|
191
193
|
const hookEvent = /* @__PURE__ */ __name((kind) => async (session) => {
|
|
@@ -193,43 +195,32 @@ function apply(ctx, config = {}) {
|
|
|
193
195
|
if (eventData.user_id) session.userId = String(eventData.user_id);
|
|
194
196
|
if (eventData.group_id) session.guildId = String(eventData.group_id);
|
|
195
197
|
try {
|
|
196
|
-
if (config.debugMode) logger.info(`[
|
|
197
|
-
const verifyText = getComment(
|
|
198
|
+
if (config.debugMode) logger.info(`[请求] 类型: ${kind} 数据: ${JSON.stringify(eventData)}`);
|
|
199
|
+
const verifyText = getComment(eventData.comment);
|
|
198
200
|
if (kind === "member") {
|
|
199
201
|
const rules = config.verifyRules?.filter((r) => String(r.guildId) === String(session.guildId)) || [];
|
|
200
202
|
for (const rule of rules) {
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
+
const stats = (rule.minLevel ?? 0) > 0 && session.onebot && session.userId ? await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({})) : null;
|
|
204
|
+
const levelMatch = (stats?.qqLevel ?? 0) >= (rule.minLevel ?? 0);
|
|
203
205
|
const keywordMatch = !rule.keyword || new RegExp(rule.keyword, "i").test(verifyText);
|
|
204
|
-
const levelMatch = !stats || (stats.qqLevel ?? 0) >= minL;
|
|
205
206
|
if (config.debugMode) {
|
|
206
|
-
if (rule.
|
|
207
|
-
if (
|
|
207
|
+
if ((rule.minLevel ?? 0) > 0) logger.info(`[加群请求] ${session.userId} 等级 ${stats?.qqLevel ?? 0} ${levelMatch ? ">" : "<"} "${rule.minLevel ?? 0}"`);
|
|
208
|
+
if (rule.keyword) logger.info(`[加群请求] ${session.userId} 内容 "${verifyText}" ${keywordMatch ? "=" : "≠"} "${rule.keyword}"`);
|
|
208
209
|
}
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
if (levelMatch && keywordMatch) {
|
|
211
|
+
if (rule.action) {
|
|
212
|
+
await executeAction(session, kind, rule.action === "accept", rule.action === "accept" ? "" : "命中规则,自动拒绝");
|
|
213
|
+
await sendNotice(session, kind, rule.action === "accept" ? "auto_pass" : "auto_reject");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
218
|
const specialRule = config.specialRules?.find((r) => String(r.guildId) === String(session.guildId) && r.enabled);
|
|
217
219
|
if (specialRule) {
|
|
218
|
-
if (specialRule.mode === "vote")
|
|
219
|
-
const [yesStr, noStr] = config.voteRatio.split(":");
|
|
220
|
-
const targetYes = parseInt(yesStr) || 0;
|
|
221
|
-
const targetNo = parseInt(noStr) || 0;
|
|
222
|
-
let msgIds = [];
|
|
223
|
-
if (config.syncNotify !== false) msgIds = await sendNotice(session, kind, "waiting");
|
|
224
|
-
if (msgIds.length > 0) {
|
|
225
|
-
const task = { session, kind, messages: msgIds, specialMode: "vote", voteTarget: { yes: targetYes, no: targetNo }, votes: { yes: /* @__PURE__ */ new Set(), no: /* @__PURE__ */ new Set() } };
|
|
226
|
-
msgIds.forEach((id) => activeTasks.set(id, task));
|
|
227
|
-
}
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
220
|
+
if (specialRule.mode === "vote") return await setupManual(session, kind, "vote", config.voteInSitu);
|
|
230
221
|
if (specialRule.mode === "captcha") {
|
|
231
222
|
await executeAction(session, kind, true, "验证码验证,自动通过");
|
|
232
|
-
|
|
223
|
+
await sendNotice(session, kind, "auto_pass");
|
|
233
224
|
return;
|
|
234
225
|
}
|
|
235
226
|
}
|
|
@@ -237,42 +228,36 @@ function apply(ctx, config = {}) {
|
|
|
237
228
|
}
|
|
238
229
|
let verdict = false;
|
|
239
230
|
if (kind === "friend") {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
231
|
+
let levelPass = true, regexPass = true;
|
|
232
|
+
if (config.friendLevel && config.friendLevel > 0 && session.onebot && session.userId) {
|
|
233
|
+
const stats = await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({}));
|
|
234
|
+
levelPass = (stats.qqLevel ?? 0) >= config.friendLevel;
|
|
235
|
+
if (config.debugMode) logger.info(`[好友验证] ${session.userId} 等级 ${stats.qqLevel ?? 0} ${levelPass ? ">" : "<"} "${config.friendLevel}"`);
|
|
244
236
|
}
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
if (
|
|
248
|
-
const stats = await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({}));
|
|
249
|
-
const isLevelMatched = (stats.qqLevel ?? 0) >= fLevel;
|
|
250
|
-
if (config.debugMode) logger.info(`[好友验证] 用户 ${session.userId} 等级 ${stats.qqLevel ?? 0}${isLevelMatched ? ">" : "<"}${fLevel}`);
|
|
251
|
-
if (isLevelMatched) verdict = true;
|
|
252
|
-
}
|
|
237
|
+
if (config.friendRegex) {
|
|
238
|
+
regexPass = new RegExp(config.friendRegex, "i").test(verifyText);
|
|
239
|
+
if (config.debugMode) logger.info(`[好友验证] ${session.userId} 内容 "${verifyText}" ${regexPass ? "=" : "≠"} "${config.friendRegex}"`);
|
|
253
240
|
}
|
|
241
|
+
verdict = levelPass && regexPass;
|
|
254
242
|
} else if (kind === "guild") {
|
|
255
243
|
if (ctx.database && session.userId) {
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
if (config.debugMode) logger.info(`[群组邀请]
|
|
244
|
+
const auth = (await ctx.database.getUser(session.platform, session.userId, ["authority"]).catch(() => null))?.authority ?? 0;
|
|
245
|
+
if (auth > 3) {
|
|
246
|
+
if (config.debugMode) logger.info(`[群组邀请] ${session.userId} 权限 ${auth} > 3`);
|
|
259
247
|
verdict = true;
|
|
260
248
|
}
|
|
261
249
|
}
|
|
262
250
|
if (verdict !== true && session.onebot && session.guildId) {
|
|
263
251
|
const stats = await session.onebot.getGroupInfo(session.guildId, true).catch(() => ({}));
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
if (config.
|
|
269
|
-
} else if (maxC > 0 && (stats.max_member_count ?? 0) < maxC) {
|
|
270
|
-
verdict = `群容量不足 ${maxC} 人`;
|
|
271
|
-
if (config.debugMode) logger.info(`[群组邀请] 群组 ${session.guildId} 受邀容量 ${stats.max_member_count ?? 0}<${maxC}`);
|
|
272
|
-
} else {
|
|
273
|
-
verdict = minM > 0 || maxC > 0;
|
|
274
|
-
if (config.debugMode && verdict) logger.info(`[群组邀请] 群组 ${session.guildId} 成员数 ${stats.member_count ?? 0}>${minM},受邀容量 ${stats.max_member_count ?? 0}>${maxC}`);
|
|
252
|
+
const minPass = (stats.member_count ?? 0) >= (config.minMembers ?? 0);
|
|
253
|
+
const maxPass = (stats.max_member_count ?? 0) >= (config.maxCapacity ?? 0);
|
|
254
|
+
if (config.debugMode) {
|
|
255
|
+
if ((config.minMembers ?? 0) > 0) logger.info(`[群组邀请] ${session.guildId} 人数 ${stats.member_count ?? 0} ${minPass ? ">" : "<"} "${config.minMembers ?? 0}"`);
|
|
256
|
+
if ((config.maxCapacity ?? 0) > 0) logger.info(`[群组邀请] ${session.guildId} 容量 ${stats.max_member_count ?? 0} ${maxPass ? ">" : "<"} "${config.maxCapacity ?? 0}"`);
|
|
275
257
|
}
|
|
258
|
+
if (!minPass) verdict = `群人数不足 ${config.minMembers ?? 0} 人`;
|
|
259
|
+
else if (!maxPass) verdict = `群容量不足 ${config.maxCapacity ?? 0} 人`;
|
|
260
|
+
else verdict = (config.minMembers ?? 0) > 0 || (config.maxCapacity ?? 0) > 0;
|
|
276
261
|
}
|
|
277
262
|
}
|
|
278
263
|
if (verdict === true) {
|
|
@@ -288,7 +273,7 @@ function apply(ctx, config = {}) {
|
|
|
288
273
|
logger.error(`处理失败: ${error}`);
|
|
289
274
|
}
|
|
290
275
|
}, "hookEvent");
|
|
291
|
-
const handleSpecialVote = /* @__PURE__ */ __name(async (session, task, isApprove, extraInfo
|
|
276
|
+
const handleSpecialVote = /* @__PURE__ */ __name(async (session, task, isApprove, extraInfo) => {
|
|
292
277
|
if (!task.voteTarget || !task.votes) return;
|
|
293
278
|
const voterId = session.userId;
|
|
294
279
|
if (!voterId) return;
|
|
@@ -310,10 +295,11 @@ function apply(ctx, config = {}) {
|
|
|
310
295
|
finalVerdict = false;
|
|
311
296
|
}
|
|
312
297
|
if (!thresholdMet) return;
|
|
298
|
+
if (task.timer) clearTimeout(task.timer);
|
|
313
299
|
task.messages.forEach((msg) => activeTasks.delete(msg));
|
|
314
300
|
const isSuccess = await executeAction(task.session, task.kind, finalVerdict, finalVerdict ? "" : extraInfo);
|
|
315
301
|
const replyText = isSuccess ? `已${finalVerdict ? "通过" : "拒绝"}该投票` : `处理投票失败`;
|
|
316
|
-
|
|
302
|
+
await session.send(replyText).catch(() => {
|
|
317
303
|
});
|
|
318
304
|
}, "handleSpecialVote");
|
|
319
305
|
ctx.on("friend-request", hookEvent("friend"));
|
|
@@ -360,19 +346,19 @@ function apply(ctx, config = {}) {
|
|
|
360
346
|
}
|
|
361
347
|
});
|
|
362
348
|
ctx.on("guild-removed", async (session) => {
|
|
363
|
-
if (session.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const inviterId = inviterMap.get(gid);
|
|
349
|
+
if (session.guildId) {
|
|
350
|
+
if (session.event?._data?.sub_type === "kick_me") {
|
|
351
|
+
const inviterId = inviterMap.get(session.guildId);
|
|
367
352
|
if (inviterId) {
|
|
368
353
|
await session.onebot?.deleteFriend(inviterId).catch(() => {
|
|
369
354
|
});
|
|
370
|
-
inviterMap.delete(
|
|
355
|
+
inviterMap.delete(session.guildId);
|
|
371
356
|
if (config.debugMode) logger.info(`[操作] 删除好友: ${inviterId}`);
|
|
372
357
|
}
|
|
373
|
-
await session.execute(`analyse.clear -g ${gid}`).catch(() => {
|
|
374
|
-
});
|
|
375
358
|
}
|
|
359
|
+
await session.execute(`analyse.clear -g ${session.guildId}`).catch(() => {
|
|
360
|
+
});
|
|
361
|
+
if (config.debugMode) logger.info(`[操作] 清理群组数据: ${session.guildId}`);
|
|
376
362
|
}
|
|
377
363
|
await sendNotice(session, "removed");
|
|
378
364
|
});
|
|
@@ -394,9 +380,10 @@ function apply(ctx, config = {}) {
|
|
|
394
380
|
if (!session.quote?.id) return next();
|
|
395
381
|
const task = activeTasks.get(session.quote.id);
|
|
396
382
|
if (!task) return next();
|
|
397
|
-
const [
|
|
398
|
-
const
|
|
399
|
-
|
|
383
|
+
const [ntType, ntId] = (config.notifyTarget || "").split(":");
|
|
384
|
+
const isGlobalNotifyTarget = ntType === "private" ? session.userId === ntId : session.guildId === ntId;
|
|
385
|
+
const isInSituGuild = task.inSitu && session.guildId && session.guildId === task.session.guildId;
|
|
386
|
+
if (!isGlobalNotifyTarget && !isInSituGuild) return next();
|
|
400
387
|
const input = session.content.replace(/<(quote|at)\s+[^>]*\/>/gi, "").trim();
|
|
401
388
|
const cmdMatch = input.match(/^(y|n|通过|拒绝)(?:\s+(.*))?$/i);
|
|
402
389
|
if (!cmdMatch) return next();
|
|
@@ -404,14 +391,14 @@ function apply(ctx, config = {}) {
|
|
|
404
391
|
const extraInfo = cmdMatch[2]?.trim() || "";
|
|
405
392
|
if (config.debugMode) logger.info(`[操作] 收到指令: ${isApprove ? "同意" : "拒绝"}`);
|
|
406
393
|
if (task.specialMode === "vote") {
|
|
407
|
-
await handleSpecialVote(session, task, isApprove, extraInfo
|
|
394
|
+
await handleSpecialVote(session, task, isApprove, extraInfo);
|
|
408
395
|
return;
|
|
409
396
|
}
|
|
410
397
|
if (task.timer) clearTimeout(task.timer);
|
|
411
398
|
task.messages.forEach((msg) => activeTasks.delete(msg));
|
|
412
399
|
const isSuccess = await executeAction(task.session, task.kind, isApprove, isApprove ? "" : extraInfo, isApprove && task.kind === "friend" ? extraInfo : "");
|
|
413
400
|
const replyText = isSuccess ? `已${isApprove ? "通过" : "拒绝"}该请求` : `处理请求失败`;
|
|
414
|
-
|
|
401
|
+
await session.send(replyText).catch(() => {
|
|
415
402
|
});
|
|
416
403
|
});
|
|
417
404
|
}
|