koishi-plugin-onebot-verifier 1.0.9 → 1.1.0

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,6 +21,13 @@ export interface Config {
21
21
  minLevel?: number;
22
22
  action?: 'accept' | 'reject';
23
23
  }[];
24
+ syncNotify?: boolean;
25
+ specialRules?: {
26
+ guildId: string;
27
+ enabled: boolean;
28
+ mode: 'vote' | 'captcha';
29
+ }[];
30
+ voteRatio?: string;
24
31
  }
25
32
  export declare const Config: Schema<Config>;
26
33
  export declare function apply(ctx: Context, config?: Config): void;
package/lib/index.js CHANGED
@@ -73,12 +73,25 @@ var Config = import_koishi.Schema.intersect([
73
73
  import_koishi.Schema.const("accept").description("同意"),
74
74
  import_koishi.Schema.const("reject").description("拒绝")
75
75
  ]).description("操作")
76
- })).description("加群验证配置").role("table")
77
- }).description("加群请求配置")
76
+ })).description("普通验证配置").role("table")
77
+ }).description("加群请求配置"),
78
+ import_koishi.Schema.object({
79
+ syncNotify: import_koishi.Schema.boolean().description("同步通知目标").default(true),
80
+ specialRules: import_koishi.Schema.array(import_koishi.Schema.object({
81
+ guildId: import_koishi.Schema.string().description("群号").required(),
82
+ mode: import_koishi.Schema.union([
83
+ import_koishi.Schema.const("vote").description("投票"),
84
+ import_koishi.Schema.const("captcha").description("验证码")
85
+ ]).description("模式").default("vote"),
86
+ enabled: import_koishi.Schema.boolean().description("前置规则").default(true)
87
+ })).description("配置列表").role("table"),
88
+ voteRatio: import_koishi.Schema.string().description("投票比例").default("3:2")
89
+ }).description("特殊验证配置")
78
90
  ]);
79
91
  function apply(ctx, config = {}) {
80
92
  const logger = new import_koishi.Logger("onebot-verifier");
81
93
  const activeTasks = /* @__PURE__ */ new Map();
94
+ const activeCaptchas = /* @__PURE__ */ new Map();
82
95
  const inviterMap = /* @__PURE__ */ new Map();
83
96
  const getComment = /* @__PURE__ */ __name((comment) => {
84
97
  if (!comment) return "";
@@ -130,6 +143,7 @@ function apply(ctx, config = {}) {
130
143
  if (adminId) infoLines.push(`管理:${adminInfo?.name ? `${adminInfo.name}(${adminId})` : adminId}`);
131
144
  if (session.guildId) infoLines.push(`群组:${groupInfo?.name ? `${groupInfo.name}(${session.guildId})` : session.guildId}`);
132
145
  if (eventData.comment) infoLines.push(`验证信息:${eventData.comment}`);
146
+ if (status === "waiting") infoLines.push(`使用"y/n"回复本消息,以同意/拒绝该请求`);
133
147
  const content = infoLines.join("\n");
134
148
  const msgIds = await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, content) : session.bot.sendMessage(targetId, content)) || [];
135
149
  return msgIds;
@@ -194,9 +208,29 @@ function apply(ctx, config = {}) {
194
208
  return;
195
209
  }
196
210
  }
211
+ const specialRule = config.specialRules?.find((r) => String(r.guildId) === String(session.guildId) && r.enabled);
212
+ if (specialRule) {
213
+ if (specialRule.mode === "vote") {
214
+ const [yesStr, noStr] = config.voteRatio.split(":");
215
+ const targetYes = parseInt(yesStr) || 0;
216
+ const targetNo = parseInt(noStr) || 0;
217
+ let msgIds = [];
218
+ if (config.syncNotify !== false) msgIds = await sendNotice(session, kind, "waiting");
219
+ if (msgIds.length > 0) {
220
+ const task = { session, kind, messages: msgIds, specialMode: "vote", voteTarget: { yes: targetYes, no: targetNo }, votes: { yes: /* @__PURE__ */ new Set(), no: /* @__PURE__ */ new Set() } };
221
+ msgIds.forEach((id) => activeTasks.set(id, task));
222
+ }
223
+ return;
224
+ }
225
+ if (specialRule.mode === "captcha") {
226
+ await executeAction(session, kind, true, "验证码验证,自动通过");
227
+ if (config.syncNotify !== false) await sendNotice(session, kind, "auto_pass");
228
+ return;
229
+ }
230
+ }
197
231
  if (config.verifyMode && config.verifyMode !== "manual") {
198
232
  const isApprove = config.verifyMode === "accept";
199
- await executeAction(session, kind, isApprove, "等待超时,自动处理");
233
+ await executeAction(session, kind, isApprove, "默认规则,自动处理");
200
234
  await sendNotice(session, kind, isApprove ? "auto_pass" : "auto_reject");
201
235
  return;
202
236
  }
@@ -255,10 +289,57 @@ function apply(ctx, config = {}) {
255
289
  logger.error(`处理失败: ${error}`);
256
290
  }
257
291
  }, "hookEvent");
292
+ const handleSpecialVote = /* @__PURE__ */ __name(async (session, task, isApprove, extraInfo, targetType, targetId) => {
293
+ if (!task.voteTarget || !task.votes) return;
294
+ const voterId = session.userId;
295
+ if (!voterId) return;
296
+ task.votes.yes.delete(voterId);
297
+ task.votes.no.delete(voterId);
298
+ if (isApprove) {
299
+ task.votes.yes.add(voterId);
300
+ } else {
301
+ task.votes.no.add(voterId);
302
+ }
303
+ if (config.debugMode) logger.info(`[投票] 赞成: ${task.votes.yes.size}/${task.voteTarget.yes} | 反对: ${task.votes.no.size}/${task.voteTarget.no}`);
304
+ let thresholdMet = false;
305
+ let finalVerdict = false;
306
+ if (task.voteTarget.yes > 0 && task.votes.yes.size >= task.voteTarget.yes) {
307
+ thresholdMet = true;
308
+ finalVerdict = true;
309
+ } else if (task.voteTarget.no > 0 && task.votes.no.size >= task.voteTarget.no) {
310
+ thresholdMet = true;
311
+ finalVerdict = false;
312
+ }
313
+ if (!thresholdMet) return;
314
+ task.messages.forEach((msg) => activeTasks.delete(msg));
315
+ const isSuccess = await executeAction(task.session, task.kind, finalVerdict, finalVerdict ? "" : extraInfo);
316
+ const replyText = isSuccess ? `已${finalVerdict ? "通过" : "拒绝"}该投票` : `处理投票失败`;
317
+ if (session.bot) await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, replyText) : session.bot.sendMessage(targetId, replyText)).catch(() => {
318
+ });
319
+ }, "handleSpecialVote");
258
320
  ctx.on("friend-request", hookEvent("friend"));
259
321
  ctx.on("guild-request", hookEvent("guild"));
260
322
  ctx.on("guild-member-request", hookEvent("member"));
261
323
  ctx.on("guild-added", hookEvent("guild"));
324
+ ctx.on("guild-member-added", async (session) => {
325
+ if (!config.specialRules || !session.guildId || !session.userId) return;
326
+ const rule = config.specialRules.find((r) => String(r.guildId) === String(session.guildId) && r.enabled);
327
+ if (rule?.mode === "captcha") {
328
+ const a = Math.floor(Math.random() * 20) + 1;
329
+ const b = Math.floor(Math.random() * 20) + 1;
330
+ const answer = (a + b).toString();
331
+ const captchaKey = `${session.guildId}:${session.userId}`;
332
+ await session.send(`<at id="${session.userId}"/> 请在 60 秒内回复计算结果,以进行验证:${a} + ${b} =`);
333
+ const timer = setTimeout(async () => {
334
+ if (activeCaptchas.has(captchaKey)) {
335
+ activeCaptchas.delete(captchaKey);
336
+ await session.send(`<at id="${session.userId}"/> 验证失败,将被移出本群。`);
337
+ await session.onebot?.setGroupKick(session.guildId, session.userId, false);
338
+ }
339
+ }, 6e4);
340
+ activeCaptchas.set(captchaKey, { guildId: session.guildId, userId: session.userId, answer, timer });
341
+ }
342
+ });
262
343
  ctx.on("guild-removed", async (session) => {
263
344
  if (session.event?._data?.sub_type === "kick_me") {
264
345
  const gid = session.guildId;
@@ -277,7 +358,21 @@ function apply(ctx, config = {}) {
277
358
  await sendNotice(session, "removed");
278
359
  });
279
360
  ctx.middleware(async (session, next) => {
280
- if (typeof session.content !== "string" || !session.quote?.id) return next();
361
+ if (typeof session.content !== "string") return next();
362
+ if (session.guildId && session.userId) {
363
+ const captchaKey = `${session.guildId}:${session.userId}`;
364
+ const captcha = activeCaptchas.get(captchaKey);
365
+ if (captcha) {
366
+ const input2 = session.content.trim();
367
+ if (input2 === captcha.answer) {
368
+ clearTimeout(captcha.timer);
369
+ activeCaptchas.delete(captchaKey);
370
+ await session.send(`<at id="${session.userId}"/> 验证成功,欢迎加入本群!`);
371
+ return;
372
+ }
373
+ }
374
+ }
375
+ if (!session.quote?.id) return next();
281
376
  const task = activeTasks.get(session.quote.id);
282
377
  if (!task) return next();
283
378
  const [targetType, targetId] = (config.notifyTarget || "").split(":");
@@ -286,11 +381,15 @@ function apply(ctx, config = {}) {
286
381
  const input = session.content.replace(/<(quote|at)\s+[^>]*\/>/gi, "").trim();
287
382
  const cmdMatch = input.match(/^(y|n|通过|拒绝)(?:\s+(.*))?$/i);
288
383
  if (!cmdMatch) return next();
289
- if (task.timer) clearTimeout(task.timer);
290
- task.messages.forEach((msg) => activeTasks.delete(msg));
291
384
  const isApprove = ["y", "通过"].includes(cmdMatch[1].toLowerCase());
292
385
  const extraInfo = cmdMatch[2]?.trim() || "";
293
386
  if (config.debugMode) logger.info(`[操作] 收到指令: ${isApprove ? "同意" : "拒绝"}`);
387
+ if (task.specialMode === "vote") {
388
+ await handleSpecialVote(session, task, isApprove, extraInfo, targetType, targetId);
389
+ return;
390
+ }
391
+ if (task.timer) clearTimeout(task.timer);
392
+ task.messages.forEach((msg) => activeTasks.delete(msg));
294
393
  const isSuccess = await executeAction(task.session, task.kind, isApprove, isApprove ? "" : extraInfo, isApprove && task.kind === "friend" ? extraInfo : "");
295
394
  const replyText = isSuccess ? `已${isApprove ? "通过" : "拒绝"}该请求` : `处理请求失败`;
296
395
  if (session.bot) await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, replyText) : session.bot.sendMessage(targetId, replyText)).catch(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-onebot-verifier",
3
3
  "description": "适用于 Onebot 的审核插件,支持自动审核好友/加群/邀请请求",
4
- "version": "1.0.9",
4
+ "version": "1.1.0",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],