koishi-plugin-onebot-verifier 1.2.9 → 1.2.11

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.
Files changed (2) hide show
  1. package/lib/index.js +66 -70
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -109,36 +109,47 @@ function apply(ctx, config) {
109
109
  const answers = lines.filter((s) => /^(回答|答案)[::]/i.test(s)).map((s) => s.replace(/^(回答|答案)[::]\s*/i, ""));
110
110
  return answers.length > 0 ? answers.join("\n") : comment;
111
111
  }, "getComment");
112
+ const getQQLevel = /* @__PURE__ */ __name((stats) => +(stats?.qqLevel ?? stats?.qq_level ?? stats?.level ?? 0), "getQQLevel");
113
+ const pushText = /* @__PURE__ */ __name(async (session, target, content) => {
114
+ const [type, id] = (target || "").split(":");
115
+ if (!id || !session.bot) return;
116
+ await (type === "private" ? session.bot.sendPrivateMessage(id, content) : session.bot.sendMessage(id, content)).catch(() => {
117
+ });
118
+ }, "pushText");
112
119
  const executeAction = /* @__PURE__ */ __name(async (session, kind, pass, reason = "", remark = "") => {
113
120
  try {
114
121
  const eventData = session.event?._data || {};
115
122
  if (config.debugMode) logger.info(`[操作] 类型: ${kind} 结果: ${pass ? "同意" : "拒绝"} 原因: ${reason || "无"}`);
116
- if (pass && kind === "guild" && session.guildId && session.userId) inviterMap.set(session.guildId, session.userId);
117
- if (!pass && kind === "guild" && session.bot) {
118
- const targetId = eventData.operator_id || session.event?.operator?.id || session.userId;
119
- if (targetId) {
120
- const rejectMsg = `已拒绝该群组邀请${reason ? `,原因:${reason}` : ""}`;
121
- await session.bot.sendPrivateMessage(targetId, rejectMsg).catch(() => {
122
- });
123
- }
123
+ if (pass && kind === "guild" && session.guildId && session.userId && session.userId !== session.selfId) inviterMap.set(session.guildId, session.userId);
124
+ if (!pass && kind === "guild") {
125
+ const targetId = eventData.operator_id || session.userId;
126
+ if (targetId) await pushText(session, `private:${targetId}`, `已拒绝该群组邀请${reason ? `,原因:${reason}` : ""}`);
124
127
  }
125
- if (!pass && kind === "guild" && session.guildId && (session.event?.type === "guild-added" || eventData.notice_type === "group_increase")) {
126
- if (reason) await session.bot?.sendMessage(session.guildId, `${reason},将退出该群`).catch(() => {
127
- });
128
- await session.onebot?.setGroupLeave(session.guildId, false);
129
- if (config.debugMode) logger.info(`[操作] 退出群组: ${session.guildId}`);
130
- return true;
128
+ if (!pass && kind === "guild" && session.guildId && eventData.notice_type === "group_increase") {
129
+ if (reason) await pushText(session, `guild:${session.guildId}`, `${reason},将退出该群`);
130
+ const left = await session.onebot?.setGroupLeave(session.guildId, false).then(() => true, () => false);
131
+ if (config.debugMode) logger.info(`[操作] ${left ? "退出群组" : "退出群组失败"}: ${session.guildId}`);
132
+ return !!left;
131
133
  }
132
134
  const flag = eventData.flag;
133
135
  if (!flag || !session.onebot) return false;
134
136
  if (kind === "friend") {
135
137
  await session.onebot.setFriendAddRequest(flag, pass, remark);
138
+ if (pass && remark) setTimeout(async () => {
139
+ const onebot = session.onebot;
140
+ try {
141
+ if (onebot.setFriendRemark) await onebot.setFriendRemark(session.userId, remark);
142
+ else await onebot._get("set_friend_remark", { user_id: session.userId, remark });
143
+ } catch (error) {
144
+ logger.warn(`设置好友备注失败: ${error}`);
145
+ }
146
+ }, 1e3);
136
147
  } else {
137
148
  await session.onebot.setGroupAddRequest(flag, eventData.sub_type ?? "add", pass, pass ? "" : reason);
138
149
  }
139
150
  return true;
140
151
  } catch (error) {
141
- logger.error(`操作失败: ${error}`);
152
+ logger.error(`操作失败(${kind}${session.guildId ? ` 群 ${session.guildId}` : ""}${session.userId ? ` 用户 ${session.userId}` : ""}): ${error}`);
142
153
  return false;
143
154
  }
144
155
  }, "executeAction");
@@ -150,7 +161,7 @@ function apply(ctx, config) {
150
161
  const eventData = session.event?._data || {};
151
162
  const userInfo = session.userId ? await session.bot.getUser?.(session.userId).catch(() => null) : null;
152
163
  const groupInfo = kind !== "friend" && session.guildId ? await session.bot.getGuild?.(session.guildId).catch(() => null) : null;
153
- const adminId = String(eventData.operator_id || session.event?.operator?.id || "");
164
+ const adminId = String(eventData.operator_id || "");
154
165
  const adminInfo = adminId && adminId !== session.userId ? await session.bot.getUser?.(adminId).catch(() => null) : null;
155
166
  const typeMap = { friend: "好友申请", member: "加群请求", guild: eventData.post_type === "notice" ? "群组邀请 (审核)" : "群组邀请 (请求)", removed: eventData.sub_type === "kick_me" ? "移出群组" : "退出群组" };
156
167
  const statusMap = { auto_pass: " [自动通过]", auto_reject: " [自动拒绝]", waiting: " [等待处理]" };
@@ -160,7 +171,7 @@ function apply(ctx, config) {
160
171
  if (kind !== "guild" && kind !== "removed" || session.userId && session.userId !== session.selfId) infoLines.push(`用户:${userInfo?.name || session.userId}${session.userId ? `(${session.userId})` : ""}`);
161
172
  if (adminId) infoLines.push(`管理:${adminInfo?.name ? `${adminInfo.name}(${adminId})` : adminId}`);
162
173
  if (session.guildId) infoLines.push(`群组:${groupInfo?.name ? `${groupInfo.name}(${session.guildId})` : session.guildId}`);
163
- if (eventData.invitor_id && String(eventData.invitor_id) !== "0") infoLines.push(`邀请者:${eventData.invitor_id}`);
174
+ if (eventData.inviter_id && String(eventData.inviter_id) !== "0") infoLines.push(`邀请者:${eventData.invitor_id}`);
164
175
  if (eventData.comment) infoLines.push(`验证信息:${eventData.comment}`);
165
176
  if (eventData.via) infoLines.push(`来源:${eventData.via}`);
166
177
  if (status === "waiting" && kind !== "removed") {
@@ -175,6 +186,17 @@ function apply(ctx, config) {
175
186
  return [];
176
187
  }
177
188
  }, "sendNotice");
189
+ const startTimer = /* @__PURE__ */ __name((task, session, timeout, forcePass, target) => {
190
+ if (typeof timeout !== "number") return;
191
+ const finalPass = task.specialMode === "vote" ? false : forcePass ?? timeout > 0;
192
+ task.timer = setTimeout(async () => {
193
+ if (!activeTasks.has(task.messages[0])) return;
194
+ task.messages.forEach((id) => activeTasks.delete(id));
195
+ await executeAction(session, task.kind, finalPass, finalPass ? "" : "等待超时,自动拒绝");
196
+ await pushText(session, target, `已自动${finalPass ? "通过" : "拒绝"}该请求`);
197
+ if (config.debugMode) logger.info(`[操作] 等待超时,默认${finalPass ? "通过" : "拒绝"}`);
198
+ }, Math.abs(timeout) * 6e4);
199
+ }, "startTimer");
178
200
  const setupManual = /* @__PURE__ */ __name(async (session, kind, specialMode, useInSitu, forceTimeoutResult) => {
179
201
  const timeoutCfg = kind === "member" ? config.memberTimeout : config.friendTimeout;
180
202
  let targetStr = config.notifyTarget || "";
@@ -188,32 +210,18 @@ function apply(ctx, config) {
188
210
  task.votes = { yes: /* @__PURE__ */ new Set(), no: /* @__PURE__ */ new Set() };
189
211
  }
190
212
  msgIds.forEach((id) => activeTasks.set(id, task));
191
- if (typeof timeoutCfg === "number") {
192
- const waitMinutes = Math.abs(timeoutCfg);
193
- const isPass = forceTimeoutResult !== void 0 ? forceTimeoutResult : timeoutCfg > 0;
194
- task.timer = setTimeout(async () => {
195
- if (!activeTasks.has(msgIds[0])) return;
196
- msgIds.forEach((id) => activeTasks.delete(id));
197
- let finalActionPass = isPass;
198
- if (specialMode === "vote") finalActionPass = false;
199
- await executeAction(session, kind, finalActionPass, finalActionPass ? "" : "等待超时,自动拒绝");
200
- const [targetType, targetId] = targetStr.split(":");
201
- if (targetId && session.bot) {
202
- const statusText = `已自动${finalActionPass ? "通过" : "拒绝"}该请求`;
203
- await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, statusText) : session.bot.sendMessage(targetId, statusText)).catch(() => {
204
- });
205
- }
206
- if (config.debugMode) logger.info(`[操作] 等待超时,默认${finalActionPass ? "通过" : "拒绝"}`);
207
- }, waitMinutes * 6e4);
208
- }
213
+ startTimer(task, session, timeoutCfg, forceTimeoutResult, targetStr);
209
214
  }, "setupManual");
210
215
  const hookEvent = /* @__PURE__ */ __name((kind) => async (session) => {
211
216
  const eventData = session.event?._data || {};
212
217
  if (eventData.user_id) session.userId = String(eventData.user_id);
213
218
  if (eventData.group_id) session.guildId = String(eventData.group_id);
219
+ const isApply = kind === "guild" && eventData.post_type === "request" && !!eventData.invited_id && String(eventData.invited_id) !== String(eventData.self_id);
220
+ if (isApply) session.userId = String(eventData.invited_id);
214
221
  if (session.guildId && config.blacklist?.includes(session.guildId)) return;
222
+ const realKind = isApply ? "member" : kind;
215
223
  try {
216
- if (config.debugMode) logger.info(`[请求] 类型: ${kind} 数据: ${JSON.stringify(eventData)}`);
224
+ if (config.debugMode) logger.info(`[请求] 类型: ${realKind} 数据: ${JSON.stringify(eventData)}`);
217
225
  const verifyText = getComment(eventData.comment);
218
226
  if (kind === "friend") {
219
227
  const existingTask = [...activeTasks.values()].find((t) => t.kind === "friend" && t.session.userId === session.userId);
@@ -222,34 +230,20 @@ function apply(ctx, config) {
222
230
  const oldVerifyText = getComment(existingTask.session.event?._data?.comment);
223
231
  if (oldVerifyText === verifyText) {
224
232
  existingTask.session = session;
225
- const timeoutCfg = config.friendTimeout;
226
- if (typeof timeoutCfg === "number") {
227
- const isPass = timeoutCfg > 0;
228
- existingTask.timer = setTimeout(async () => {
229
- if (!activeTasks.has(existingTask.messages[0])) return;
230
- existingTask.messages.forEach((id) => activeTasks.delete(id));
231
- await executeAction(session, "friend", isPass, isPass ? "" : "等待超时,自动拒绝");
232
- const [targetType, targetId] = (config.notifyTarget || "").split(":");
233
- if (targetId && session.bot) {
234
- const statusText = `已自动${isPass ? "通过" : "拒绝"}该请求`;
235
- await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, statusText) : session.bot.sendMessage(targetId, statusText)).catch(() => {
236
- });
237
- }
238
- }, Math.abs(timeoutCfg) * 6e4);
239
- }
233
+ startTimer(existingTask, session, config.friendTimeout, void 0, config.notifyTarget);
240
234
  return;
241
235
  }
242
236
  existingTask.messages.forEach((id) => activeTasks.delete(id));
243
237
  }
244
238
  }
245
- if (kind === "member") {
239
+ if (realKind === "member") {
246
240
  const rules = config.verifyRules?.filter((r) => r.guildId === session.guildId) || [];
247
241
  for (const rule of rules) {
248
242
  const stats = (rule.minLevel ?? 0) > 0 && session.onebot && session.userId ? await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({})) : null;
249
- const levelMatch = (stats?.qqLevel ?? 0) >= (rule.minLevel ?? 0);
243
+ const levelMatch = getQQLevel(stats) >= (rule.minLevel ?? 0);
250
244
  const keywordMatch = !rule.keyword || new RegExp(rule.keyword, "i").test(verifyText);
251
245
  if (config.debugMode) {
252
- if ((rule.minLevel ?? 0) > 0) logger.info(`[加群请求] ${session.userId} 等级 ${stats?.qqLevel ?? 0} ${levelMatch ? ">" : "<"} ${rule.minLevel ?? 0}`);
246
+ if ((rule.minLevel ?? 0) > 0) logger.info(`[加群请求] ${session.userId} 等级 ${getQQLevel(stats)} ${levelMatch ? ">" : "<"} ${rule.minLevel ?? 0}`);
253
247
  if (rule.keyword) logger.info(`[加群请求] ${session.userId} 内容 "${verifyText}" ${keywordMatch ? "=" : "≠"} "${rule.keyword}"`);
254
248
  }
255
249
  if (levelMatch && keywordMatch) {
@@ -257,40 +251,40 @@ function apply(ctx, config) {
257
251
  const isFrequent = rule.frequency && Date.now() - lastLeaveTime < rule.frequency * 6e4;
258
252
  if (isFrequent) {
259
253
  if (config.frequencyMode === "reject") {
260
- await executeAction(session, kind, false, "频繁申请,自动拒绝");
261
- await sendNotice(session, kind, "auto_reject");
254
+ await executeAction(session, realKind, false, "频繁申请,自动拒绝");
255
+ await sendNotice(session, realKind, "auto_reject");
262
256
  return;
263
257
  } else if (config.frequencyMode === "ignore") {
264
- return await setupManual(session, kind);
258
+ return await setupManual(session, realKind);
265
259
  } else if (config.frequencyMode === "delay") {
266
- return await setupManual(session, kind, void 0, false, rule.action === "accept");
260
+ return await setupManual(session, realKind, void 0, false, rule.action && rule.action === "accept");
267
261
  }
268
262
  }
269
263
  if (rule.action) {
270
- await executeAction(session, kind, rule.action === "accept", rule.action === "accept" ? "" : "错误回答,自动拒绝");
271
- await sendNotice(session, kind, rule.action === "accept" ? "auto_pass" : "auto_reject");
264
+ await executeAction(session, realKind, rule.action === "accept", rule.action === "accept" ? "" : "错误回答,自动拒绝");
265
+ await sendNotice(session, realKind, rule.action === "accept" ? "auto_pass" : "auto_reject");
272
266
  return;
273
267
  }
274
268
  }
275
269
  }
276
270
  const specialRule = config.specialRules?.find((r) => r.guildId === session.guildId);
277
271
  if (specialRule) {
278
- if (specialRule.mode === "vote") return await setupManual(session, kind, "vote", config.voteInSitu);
272
+ if (specialRule.mode === "vote") return await setupManual(session, realKind, "vote", config.voteInSitu);
279
273
  if (specialRule.mode === "captcha") {
280
- await executeAction(session, kind, true, "验证码验证,自动通过");
281
- await sendNotice(session, kind, "auto_pass");
274
+ await executeAction(session, realKind, true, "验证码验证,自动通过");
275
+ await sendNotice(session, realKind, "auto_pass");
282
276
  return;
283
277
  }
284
278
  }
285
- return await setupManual(session, kind);
279
+ return await setupManual(session, realKind);
286
280
  }
287
281
  let verdict = false;
288
282
  if (kind === "friend") {
289
283
  let levelPass = true, regexPass = true;
290
284
  if (config.friendLevel && config.friendLevel > 0 && session.onebot && session.userId) {
291
285
  const stats = await session.onebot.getStrangerInfo(session.userId, true).catch(() => ({}));
292
- levelPass = (stats.qqLevel ?? 0) >= config.friendLevel;
293
- if (config.debugMode) logger.info(`[好友验证] ${session.userId} 等级 ${stats.qqLevel ?? 0} ${levelPass ? ">" : "<"} ${config.friendLevel}`);
286
+ levelPass = getQQLevel(stats) >= config.friendLevel;
287
+ if (config.debugMode) logger.info(`[好友验证] ${session.userId} 等级 ${getQQLevel(stats)} ${levelPass ? ">" : "<"} ${config.friendLevel}`);
294
288
  }
295
289
  if (config.friendRegex) {
296
290
  regexPass = new RegExp(config.friendRegex, "i").test(verifyText);
@@ -368,7 +362,7 @@ function apply(ctx, config) {
368
362
  if (config.verifyRules?.some((r) => r.guildId === session.guildId)) historyMap.set(`${session.userId}:${session.guildId}`, Date.now());
369
363
  });
370
364
  ctx.on("guild-member-added", async (session) => {
371
- if (!config.specialRules || !session.guildId || !session.userId || config.blacklist?.includes(session.guildId)) return;
365
+ if (!config.specialRules || !session.guildId || !session.userId || session.userId === session.selfId || config.blacklist?.includes(session.guildId)) return;
372
366
  const rule = config.specialRules.find((r) => r.guildId === session.guildId);
373
367
  if (rule?.mode === "captcha") {
374
368
  let a, b, op = "+", answer;
@@ -398,8 +392,10 @@ function apply(ctx, config) {
398
392
  const timer = setTimeout(async () => {
399
393
  if (activeCaptchas.has(`${session.userId}:${session.guildId}`)) {
400
394
  activeCaptchas.delete(`${session.userId}:${session.guildId}`);
401
- await session.send(`<at id="${session.userId}"/> 验证失败,将被移出本群。`);
402
- await session.onebot?.setGroupKick(session.guildId, session.userId, false);
395
+ await session.send(`<at id="${session.userId}"/> 验证失败,将被移出本群。`).catch(() => {
396
+ });
397
+ await session.onebot?.setGroupKick(session.guildId, session.userId, false).catch(() => {
398
+ });
403
399
  }
404
400
  }, 6e4);
405
401
  activeCaptchas.set(`${session.userId}:${session.guildId}`, { guildId: session.guildId, userId: session.userId, answer, timer });
@@ -418,7 +414,7 @@ function apply(ctx, config) {
418
414
  inviterMap.delete(session.guildId);
419
415
  if (config.debugMode) logger.info(`[操作] 删除邀请者好友: ${inviterId}`);
420
416
  }
421
- const adminId = String(eventData.operator_id || session.event?.operator?.id || "");
417
+ const adminId = String(eventData.operator_id || "");
422
418
  if (adminId && adminId !== inviterId) {
423
419
  await session.onebot?.deleteFriend(adminId).catch(() => {
424
420
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-onebot-verifier",
3
3
  "description": "[仅支持 Onebot 平台]支持好友申请/群组邀请/加群请求等的自动审核,可根据等级与正则进行筛选,并有入群验证码、投票表决、被踢清理等功能。",
4
- "version": "1.2.9",
4
+ "version": "1.2.11",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],