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 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("[投票]人数").default("3:2"),
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("[验证码]难度").default("simple")
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(`使用"y/n"回复本消息,以同意/拒绝该请求`);
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
- if (waitMinutes > 0) {
164
- const msgIds = await sendNotice(session, kind, "waiting");
165
- if (!msgIds?.length) return;
166
- const task = { session, kind, messages: msgIds };
167
- msgIds.forEach((id) => activeTasks.set(id, task));
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
- const finalAction = kind === "member" ? config.verifyMode ?? "manual" : config.timeoutAction ?? "accept";
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] = (config.notifyTarget || "").split(":");
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(`[收到请求] 类型: ${kind} 数据: ${JSON.stringify(session.event?._data || {})}`);
197
- const verifyText = getComment(session.event?._data?.comment);
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 minL = rule.minLevel ?? 0;
202
- const stats = minL > 0 && session.onebot && session.userId ? await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({})) : null;
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.keyword) logger.info(`[加群验证] 内容 "${verifyText}" ${keywordMatch ? "匹配" : "不匹配"}正则 "${rule.keyword}"`);
207
- if (stats) logger.info(`[加群验证] 用户 ${session.userId} 等级 ${stats.qqLevel ?? 0}${levelMatch ? ">" : "<"}${minL}`);
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 (keywordMatch && levelMatch && rule.action) {
210
- const isApprove = rule.action === "accept";
211
- await executeAction(session, kind, isApprove, isApprove ? "" : "命中规则,自动拒绝");
212
- await sendNotice(session, kind, isApprove ? "auto_pass" : "auto_reject");
213
- return;
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
- if (config.syncNotify !== false) await sendNotice(session, kind, "auto_pass");
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
- if (config.friendRegex) {
241
- const isRegexMatched = new RegExp(config.friendRegex, "i").test(verifyText);
242
- if (config.debugMode) logger.info(`[好友验证] 内容 "${verifyText}" ${isRegexMatched ? "匹配" : "不匹配"}正则 "${config.friendRegex}" `);
243
- if (isRegexMatched) verdict = true;
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 (verdict !== true) {
246
- const fLevel = config.friendLevel ?? 0;
247
- if (fLevel > 0 && session.onebot && session.userId) {
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 userData = await ctx.database.getUser(session.platform, session.userId, ["authority"]).catch(() => null);
257
- if (userData && userData.authority > 3) {
258
- if (config.debugMode) logger.info(`[群组邀请] 用户 ${session.userId} 权限 ${userData.authority}>3`);
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 minM = config.minMembers ?? 0;
265
- const maxC = config.maxCapacity ?? 0;
266
- if (minM > 0 && (stats.member_count ?? 0) < minM) {
267
- verdict = `群成员不足 ${minM} 人`;
268
- if (config.debugMode) logger.info(`[群组邀请] 群组 ${session.guildId} 成员数 ${stats.member_count ?? 0}<${minM}`);
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, targetType, targetId) => {
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
- if (session.bot) await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, replyText) : session.bot.sendMessage(targetId, replyText)).catch(() => {
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.event?._data?.sub_type === "kick_me") {
364
- const gid = session.guildId;
365
- if (gid) {
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(gid);
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 [targetType, targetId] = (config.notifyTarget || "").split(":");
398
- const isMatched = targetType === "private" ? session.userId === targetId : session.guildId === targetId;
399
- if (!isMatched) return next();
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, targetType, targetId);
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
- if (session.bot) await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, replyText) : session.bot.sendMessage(targetId, replyText)).catch(() => {
401
+ await session.send(replyText).catch(() => {
415
402
  });
416
403
  });
417
404
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-onebot-verifier",
3
3
  "description": "适用于 Onebot 的审核插件,支持自动审核好友/加群/邀请请求",
4
- "version": "1.1.2",
4
+ "version": "1.1.4",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],