koishi-plugin-onebot-verifier 1.0.0 → 1.0.2

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
@@ -1,22 +1,26 @@
1
1
  import { Context, Schema } from 'koishi';
2
2
  export declare const name = "onebot-verifier";
3
+ export declare const inject: {
4
+ optional: string[];
5
+ };
3
6
  export declare const usage = "\n<div style=\"border-radius: 10px; border: 1px solid #ddd; padding: 16px; margin-bottom: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);\">\n <h2 style=\"margin-top: 0; color: #4a6ee0;\">\uD83D\uDCCC \u63D2\u4EF6\u8BF4\u660E</h2>\n <p>\uD83D\uDCD6 <strong>\u4F7F\u7528\u6587\u6863</strong>\uFF1A\u8BF7\u70B9\u51FB\u5DE6\u4E0A\u89D2\u7684 <strong>\u63D2\u4EF6\u4E3B\u9875</strong> \u67E5\u770B\u63D2\u4EF6\u4F7F\u7528\u6587\u6863</p>\n <p>\uD83D\uDD0D <strong>\u66F4\u591A\u63D2\u4EF6</strong>\uFF1A\u53EF\u8BBF\u95EE <a href=\"https://github.com/YisRime\" style=\"color:#4a6ee0;text-decoration:none;\">\u82E1\u6DDE\u7684 GitHub</a> \u67E5\u770B\u672C\u4EBA\u7684\u6240\u6709\u63D2\u4EF6</p>\n</div>\n<div style=\"border-radius: 10px; border: 1px solid #ddd; padding: 16px; margin-bottom: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);\">\n <h2 style=\"margin-top: 0; color: #e0574a;\">\u2764\uFE0F \u652F\u6301\u4E0E\u53CD\u9988</h2>\n <p>\uD83C\uDF1F \u559C\u6B22\u8FD9\u4E2A\u63D2\u4EF6\uFF1F\u8BF7\u5728 <a href=\"https://github.com/YisRime\" style=\"color:#e0574a;text-decoration:none;\">GitHub</a> \u4E0A\u7ED9\u6211\u4E00\u4E2A Star\uFF01</p>\n <p>\uD83D\uDC1B \u9047\u5230\u95EE\u9898\uFF1F\u8BF7\u901A\u8FC7 <strong>Issues</strong> \u63D0\u4EA4\u53CD\u9988\uFF0C\u6216\u52A0\u5165 QQ \u7FA4 <a href=\"https://qm.qq.com/q/PdLMx9Jowq\" style=\"color:#e0574a;text-decoration:none;\"><strong>855571375</strong></a> \u8FDB\u884C\u4EA4\u6D41</p>\n</div>\n";
4
7
  export interface Config {
5
- enable?: boolean;
6
8
  notifyTarget?: string;
7
- enableDebug?: boolean;
8
- FriendLevel?: number;
9
- FriendRequestAutoRegex?: string;
10
- MemberRequestAutoRules?: {
9
+ notifyAuto?: boolean;
10
+ debugMode?: boolean;
11
+ timeout?: number;
12
+ timeoutAction?: 'accept' | 'reject';
13
+ friendLevel?: number;
14
+ friendRegex?: string;
15
+ minMembers?: number;
16
+ maxCapacity?: number;
17
+ verifyMode?: 'strict' | 'assist' | 'manual';
18
+ verifyRules?: {
11
19
  guildId: string;
12
- keyword: string;
13
- minLevel: number;
20
+ keyword?: string;
21
+ minLevel?: number;
22
+ groupAction?: 'accept' | 'reject';
14
23
  }[];
15
- GuildAllowUsers?: string[];
16
- GuildMinMemberCount?: number;
17
- GuildMaxCapacity?: number;
18
- manualTimeout?: number;
19
- manualTimeoutAction?: 'accept' | 'reject';
20
24
  }
21
25
  export declare const Config: Schema<Config>;
22
26
  export declare function apply(ctx: Context, config?: Config): void;
package/lib/index.js CHANGED
@@ -22,12 +22,14 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  Config: () => Config,
24
24
  apply: () => apply,
25
+ inject: () => inject,
25
26
  name: () => name,
26
27
  usage: () => usage
27
28
  });
28
29
  module.exports = __toCommonJS(src_exports);
29
30
  var import_koishi = require("koishi");
30
31
  var name = "onebot-verifier";
32
+ var inject = { optional: ["database"] };
31
33
  var usage = `
32
34
  <div style="border-radius: 10px; border: 1px solid #ddd; padding: 16px; margin-bottom: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
33
35
  <h2 style="margin-top: 0; color: #4a6ee0;">📌 插件说明</h2>
@@ -40,98 +42,110 @@ var usage = `
40
42
  <p>🐛 遇到问题?请通过 <strong>Issues</strong> 提交反馈,或加入 QQ 群 <a href="https://qm.qq.com/q/PdLMx9Jowq" style="color:#e0574a;text-decoration:none;"><strong>855571375</strong></a> 进行交流</p>
41
43
  </div>
42
44
  `;
43
- var Config = import_koishi.Schema.object({
44
- enable: import_koishi.Schema.boolean().description("开启请求监听").default(true),
45
- notifyTarget: import_koishi.Schema.string().description("通知目标(guild/private:number)").required(),
46
- enableDebug: import_koishi.Schema.boolean().description("开启调试日志").default(false),
47
- manualTimeout: import_koishi.Schema.number().description("请求超时时长").default(360).min(0),
48
- manualTimeoutAction: import_koishi.Schema.union([
49
- import_koishi.Schema.const("accept").description("同意"),
50
- import_koishi.Schema.const("reject").description("拒绝")
51
- ]).description("默认超时操作").default("accept"),
52
- FriendLevel: import_koishi.Schema.number().description("最低好友等级").default(-1).min(-1).max(256),
53
- GuildMinMemberCount: import_koishi.Schema.number().description("最低群成员数").default(-1).min(-1).max(3e3),
54
- GuildMaxCapacity: import_koishi.Schema.number().description("最低受邀容量").default(-1).min(-1).max(3e3),
55
- FriendRequestAutoRegex: import_koishi.Schema.string().description("好友验证正则"),
56
- MemberRequestAutoRules: import_koishi.Schema.array(import_koishi.Schema.object({
57
- guildId: import_koishi.Schema.string().description("群号"),
58
- keyword: import_koishi.Schema.string().description("正则"),
59
- minLevel: import_koishi.Schema.number().description("等级").default(-1)
60
- })).description("加群验证规则").role("table"),
61
- GuildAllowUsers: import_koishi.Schema.array(String).description("邀请加群白名单").role("table")
62
- }).description("请求处理配置");
45
+ var Config = import_koishi.Schema.intersect([
46
+ import_koishi.Schema.object({
47
+ notifyTarget: import_koishi.Schema.string().description("通知目标(guild/private:number)").required(),
48
+ notifyAuto: import_koishi.Schema.boolean().description("发送全部通知").default(true),
49
+ debugMode: import_koishi.Schema.boolean().description("输出调试日志").default(false)
50
+ }).description("基础配置"),
51
+ import_koishi.Schema.object({
52
+ timeout: import_koishi.Schema.number().description("请求超时时长").default(360).min(0),
53
+ timeoutAction: import_koishi.Schema.union([
54
+ import_koishi.Schema.const("accept").description("同意"),
55
+ import_koishi.Schema.const("reject").description("拒绝")
56
+ ]).description("默认超时操作").default("accept"),
57
+ friendLevel: import_koishi.Schema.number().description("最低好友等级").default(-1).min(-1).max(256),
58
+ friendRegex: import_koishi.Schema.string().description("好友验证正则"),
59
+ minMembers: import_koishi.Schema.number().description("最低群成员数").default(-1).min(-1).max(3e3),
60
+ maxCapacity: import_koishi.Schema.number().description("最低受邀容量").default(-1).min(-1).max(3e3)
61
+ }).description("好友邀群配置"),
62
+ import_koishi.Schema.object({
63
+ verifyMode: import_koishi.Schema.union([
64
+ import_koishi.Schema.const("strict").description("规则"),
65
+ import_koishi.Schema.const("assist").description("超时"),
66
+ import_koishi.Schema.const("manual").description("手动")
67
+ ]).description("处理模式").default("assist"),
68
+ verifyRules: import_koishi.Schema.array(import_koishi.Schema.object({
69
+ guildId: import_koishi.Schema.string().description("群号").required(),
70
+ keyword: import_koishi.Schema.string().description("正则"),
71
+ minLevel: import_koishi.Schema.number().description("等级").default(-1),
72
+ groupAction: import_koishi.Schema.union([
73
+ import_koishi.Schema.const("accept").description("同意"),
74
+ import_koishi.Schema.const("reject").description("拒绝")
75
+ ]).description("操作")
76
+ })).description("加群验证配置").role("table")
77
+ }).description("加群请求配置")
78
+ ]);
63
79
  function apply(ctx, config = {}) {
64
80
  const logger = new import_koishi.Logger("onebot-verifier");
65
- const tasks = /* @__PURE__ */ new Map();
66
- const doAction = /* @__PURE__ */ __name(async (session, kind, pass, reason = "", remark = "") => {
81
+ const activeTasks = /* @__PURE__ */ new Map();
82
+ const executeAction = /* @__PURE__ */ __name(async (session, kind, pass, reason = "", remark = "") => {
67
83
  try {
68
- const data = session.event?._data || {};
69
- if (!pass && kind === "guild" && session.guildId && (session.event?.type === "guild-added" || data.notice_type === "group_increase")) {
84
+ const eventData = session.event?._data || {};
85
+ if (!pass && kind === "guild" && session.guildId && (session.event?.type === "guild-added" || eventData.notice_type === "group_increase")) {
70
86
  if (reason) {
71
87
  try {
72
- await session.bot.sendMessage(session.guildId, `将退出该群:${reason}`);
88
+ await session.bot.sendMessage(session.guildId, `${reason},将退出该群`);
73
89
  } catch (error) {
74
- logger.warn(`发送提醒失败: ${error}`);
90
+ logger.warn(`发送退群通知失败: ${error}`);
75
91
  }
76
92
  }
77
- try {
78
- await session.onebot?.setGroupLeave(session.guildId, false);
79
- return true;
80
- } catch (error) {
81
- logger.error(`退出群组 ${session.guildId} 失败: ${error}`);
82
- return false;
83
- }
93
+ await session.onebot?.setGroupLeave(session.guildId, false);
94
+ return true;
84
95
  }
85
- if (!data.flag || !session.onebot) return false;
86
- if (kind === "friend") await session.onebot.setFriendAddRequest(data.flag, pass, remark);
87
- else await session.onebot.setGroupAddRequest(data.flag, data.sub_type ?? "add", pass, pass ? "" : reason);
96
+ if (!eventData.flag || !session.onebot) return false;
97
+ if (kind === "friend") await session.onebot.setFriendAddRequest(eventData.flag, pass, remark);
98
+ else await session.onebot.setGroupAddRequest(eventData.flag, eventData.sub_type ?? "add", pass, pass ? "" : reason);
88
99
  return true;
89
100
  } catch (error) {
90
- logger.error(`请求操作失败: ${error}`);
101
+ logger.error(`操作失败: ${error}`);
91
102
  return false;
92
103
  }
93
- }, "doAction");
94
- const notify = /* @__PURE__ */ __name(async (session, kind) => {
95
- const target = config.notifyTarget || "";
96
- if (!target) return [];
97
- const [type, id] = target.split(":");
98
- if (!id || type !== "guild" && type !== "private") return [];
104
+ }, "executeAction");
105
+ const sendNotice = /* @__PURE__ */ __name(async (session, kind, status = "waiting") => {
106
+ const notifyConfig = config.notifyTarget || "";
107
+ if (!notifyConfig) return [];
108
+ const [targetType, targetId] = notifyConfig.split(":");
109
+ if (!targetId || targetType !== "guild" && targetType !== "private") return [];
110
+ if (status !== "waiting" && !config.notifyAuto) return [];
99
111
  try {
100
- const data = session.event?._data || {};
101
- const user = session.userId ? await session.bot.getUser?.(session.userId)?.catch(() => null) : null;
102
- const guild = kind !== "friend" && session.guildId ? await session.bot.getGuild?.(session.guildId)?.catch(() => null) : null;
103
- const admin = data.operator_id && session.userId && data.operator_id.toString() !== session.userId ? await session.bot.getUser?.(data.operator_id.toString())?.catch(() => null) : null;
104
- const lines = [];
105
- if (user?.avatar) lines.push(`<image url="${user.avatar}"/>`);
112
+ const eventData = session.event?._data || {};
113
+ const userInfo = session.userId ? await session.bot.getUser?.(session.userId)?.catch(() => null) : null;
114
+ const groupInfo = kind !== "friend" && session.guildId ? await session.bot.getGuild?.(session.guildId)?.catch(() => null) : null;
115
+ const adminInfo = eventData.operator_id && session.userId && eventData.operator_id !== session.userId ? await session.bot.getUser?.(eventData.operator_id)?.catch(() => null) : null;
116
+ const infoLines = [];
117
+ if (userInfo?.avatar) infoLines.push(`<image url="${userInfo.avatar}"/>`);
106
118
  const typeName = kind === "friend" ? "好友申请" : kind === "member" ? "加群请求" : "群组邀请";
107
- lines.push(`类型:${typeName}`);
108
- if (kind !== "guild" || session.userId !== session.selfId) lines.push(`用户:${user?.name || session.userId}${session.userId ? `(${session.userId})` : ""}`);
109
- if (admin) lines.push(`管理:${admin.name ? `${admin.name}(${data.operator_id})` : data.operator_id}`);
110
- if (guild) lines.push(`群组:${guild.name ? `${guild.name}(${session.guildId})` : session.guildId}`);
111
- if (data.comment) lines.push(`验证信息:${data.comment}`);
112
- const text = lines.join("\n");
113
- return await (type === "private" ? session.bot.sendPrivateMessage(id, text) : session.bot.sendMessage(id, text)) || [];
119
+ const statusText = status === "auto_pass" ? "[自动通过]" : status === "auto_reject" ? "[自动拒绝]" : "[等待处理]";
120
+ infoLines.push(`类型:${typeName} ${statusText}`);
121
+ if (kind !== "guild" || session.userId !== session.selfId) infoLines.push(`用户:${userInfo?.name || session.userId}${session.userId ? `(${session.userId})` : ""}`);
122
+ if (adminInfo) infoLines.push(`管理:${adminInfo.name ? `${adminInfo.name}(${eventData.operator_id})` : eventData.operator_id}`);
123
+ if (groupInfo) infoLines.push(`群组:${groupInfo.name ? `${groupInfo.name}(${session.guildId})` : session.guildId}`);
124
+ if (eventData.comment) infoLines.push(`验证信息:${eventData.comment}`);
125
+ const content = infoLines.join("\n");
126
+ return await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, content) : session.bot.sendMessage(targetId, content)) || [];
114
127
  } catch (error) {
115
- logger.error(`发送通知失败: ${error}`);
128
+ logger.error(`通知失败: ${error}`);
116
129
  return [];
117
130
  }
118
- }, "notify");
119
- const checkAuto = /* @__PURE__ */ __name(async (session, kind) => {
120
- const raw = session.event?._data?.comment || "";
121
- const lines = raw.split(/[\r\n]+/).map((s) => s.trim()).filter((s) => /^(回答)[::]/i.test(s)).map((s) => s.replace(/^(回答)[::]\s*/i, ""));
122
- const answer = lines.length > 0 ? lines.join("\n") : raw;
123
- const makeRegex = /* @__PURE__ */ __name((text) => {
131
+ }, "sendNotice");
132
+ const checkCriteria = /* @__PURE__ */ __name(async (session, kind) => {
133
+ const rawText = session.event?._data?.comment || "";
134
+ const cleanLines = rawText.split(/[\r\n]+/).map((s) => s.trim()).filter((s) => /^(回答)[::]/i.test(s)).map((s) => s.replace(/^(回答)[::]\s*/i, ""));
135
+ const verifyText = cleanLines.length > 0 ? cleanLines.join("\n") : rawText;
136
+ const toRegex = /* @__PURE__ */ __name((text) => {
124
137
  const match = text.match(/^\/(.+)\/([a-z]*)$/);
125
138
  return match ? new RegExp(match[1], match[2]) : new RegExp(text, "i");
126
- }, "makeRegex");
139
+ }, "toRegex");
127
140
  if (kind === "member") {
128
- const rule = config.MemberRequestAutoRules?.find((rule2) => rule2.guildId === session.guildId);
129
- if (!rule) return false;
141
+ const groupRule = config.verifyRules?.find((r) => r.guildId === session.guildId);
142
+ if (!groupRule) return false;
130
143
  try {
131
- if (rule.keyword && !makeRegex(rule.keyword).test(answer)) return false;
132
- if ((rule.minLevel ?? -1) >= 0 && session.onebot && session.userId) {
133
- const info = await session.onebot.getStrangerInfo(session.userId, true);
134
- if ((info.qqLevel ?? 0) < rule.minLevel) return `QQ 等级低于${rule.minLevel}级`;
144
+ if (groupRule.keyword && !toRegex(groupRule.keyword).test(verifyText)) return false;
145
+ const limitLevel = groupRule.minLevel ?? -1;
146
+ if (limitLevel >= 0 && session.onebot && session.userId) {
147
+ const stats = await session.onebot.getStrangerInfo(session.userId, true);
148
+ if ((stats.qqLevel ?? 0) < limitLevel) return `QQ 等级低于 ${limitLevel} 级`;
135
149
  }
136
150
  } catch {
137
151
  return false;
@@ -140,10 +154,11 @@ function apply(ctx, config = {}) {
140
154
  }
141
155
  if (kind === "friend") {
142
156
  try {
143
- if (config.FriendRequestAutoRegex && makeRegex(config.FriendRequestAutoRegex).test(answer)) return true;
144
- if ((config.FriendLevel ?? -1) >= 0 && session.onebot && session.userId) {
145
- const info = await session.onebot.getStrangerInfo(session.userId, true);
146
- if ((info.qqLevel ?? 0) < config.FriendLevel) return `QQ 等级低于${config.FriendLevel}级`;
157
+ if (config.friendRegex && toRegex(config.friendRegex).test(verifyText)) return true;
158
+ const limitLevel = config.friendLevel ?? -1;
159
+ if (limitLevel >= 0 && session.onebot && session.userId) {
160
+ const stats = await session.onebot.getStrangerInfo(session.userId, true);
161
+ if ((stats.qqLevel ?? 0) < limitLevel) return `QQ 等级低于 ${limitLevel} 级`;
147
162
  return true;
148
163
  }
149
164
  } catch {
@@ -152,17 +167,16 @@ function apply(ctx, config = {}) {
152
167
  return false;
153
168
  }
154
169
  if (kind === "guild") {
155
- if (session.userId && config.GuildAllowUsers?.includes(session.userId)) return true;
156
170
  try {
157
- const user = session.userId ? await ctx.database.getUser(session.platform, session.userId) : null;
158
- if (user && user.authority > 1) return true;
171
+ const userData = session.userId ? await ctx.database.getUser(session.platform, session.userId) : null;
172
+ if (userData && userData.authority > 1) return true;
159
173
  } catch {
160
174
  }
161
- if (session.onebot && session.guildId && ((config.GuildMinMemberCount ?? -1) >= 0 || (config.GuildMaxCapacity ?? -1) >= 0)) {
175
+ if (session.onebot && session.guildId && ((config.minMembers ?? -1) >= 0 || (config.maxCapacity ?? -1) >= 0)) {
162
176
  try {
163
- const info = await session.onebot.getGroupInfo(session.guildId, true);
164
- if (config.GuildMinMemberCount >= 0 && info.member_count < config.GuildMinMemberCount) return `群成员不足${config.GuildMinMemberCount}人`;
165
- if (config.GuildMaxCapacity >= 0 && info.max_member_count < config.GuildMaxCapacity) return `群容量不足${config.GuildMaxCapacity}人`;
177
+ const stats = await session.onebot.getGroupInfo(session.guildId, true);
178
+ if ((config.minMembers ?? -1) >= 0 && stats.member_count < (config.minMembers ?? 0)) return `群成员不足 ${config.minMembers} 人`;
179
+ if ((config.maxCapacity ?? -1) >= 0 && stats.max_member_count < (config.maxCapacity ?? 0)) return `群容量不足 ${config.maxCapacity} 人`;
166
180
  return true;
167
181
  } catch {
168
182
  return false;
@@ -171,81 +185,95 @@ function apply(ctx, config = {}) {
171
185
  return false;
172
186
  }
173
187
  return false;
174
- }, "checkAuto");
175
- const setManual = /* @__PURE__ */ __name(async (session, kind) => {
176
- const messages = await notify(session, kind);
177
- if (!messages?.length) return;
178
- const task = { session, kind, messages };
179
- messages.forEach((id) => tasks.set(id, task));
180
- const limit = config.manualTimeout ?? 60;
181
- if (limit > 0) {
188
+ }, "checkCriteria");
189
+ const setupManual = /* @__PURE__ */ __name(async (session, kind) => {
190
+ const msgIds = await sendNotice(session, kind, "waiting");
191
+ if (!msgIds?.length) return;
192
+ const task = { session, kind, messages: msgIds };
193
+ msgIds.forEach((id) => activeTasks.set(id, task));
194
+ const waitMinutes = config.timeout ?? 0;
195
+ if (waitMinutes > 0) {
182
196
  task.timer = setTimeout(async () => {
183
- if (!tasks.has(messages[0])) return;
184
- messages.forEach((id2) => tasks.delete(id2));
185
- const action = config.manualTimeoutAction || "accept";
186
- const pass = action === "accept";
187
- await doAction(session, kind, pass, pass ? "" : "等待超时,自动拒绝");
188
- const target = config.notifyTarget || "";
189
- const [type, id] = target.split(":");
190
- if (id) {
191
- const text = `等待超时,已自动${pass ? "通过" : "拒绝"}`;
192
- await (type === "private" ? session.bot.sendPrivateMessage(id, text) : session.bot.sendMessage(id, text));
197
+ if (!activeTasks.has(msgIds[0])) return;
198
+ msgIds.forEach((id) => activeTasks.delete(id));
199
+ let finalAction = config.timeoutAction;
200
+ if (kind === "member") {
201
+ const groupRule = config.verifyRules?.find((r) => r.guildId === session.guildId);
202
+ if (groupRule?.groupAction) finalAction = groupRule.groupAction;
203
+ }
204
+ const isPass = finalAction === "accept";
205
+ await executeAction(session, kind, isPass, isPass ? "" : "等待人工超时,自动拒绝");
206
+ const notifyConfig = config.notifyTarget || "";
207
+ const [targetType, targetId] = notifyConfig.split(":");
208
+ if (targetId) {
209
+ const statusText = `已自动${isPass ? "通过" : "拒绝"}该请求`;
210
+ await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, statusText) : session.bot.sendMessage(targetId, statusText));
193
211
  }
194
- }, limit * 6e4);
212
+ }, waitMinutes * 6e4);
195
213
  }
196
- }, "setManual");
197
- const process = /* @__PURE__ */ __name(async (session, kind) => {
214
+ }, "setupManual");
215
+ const handleEvent = /* @__PURE__ */ __name(async (session, kind) => {
198
216
  try {
199
- const result = await checkAuto(session, kind);
200
- if (result === true) {
201
- await doAction(session, kind, true);
202
- await notify(session, kind);
203
- } else if (typeof result === "string") {
204
- await doAction(session, kind, false, result);
205
- await notify(session, kind);
217
+ if (kind === "member") {
218
+ if (config.verifyMode === "manual") {
219
+ await setupManual(session, kind);
220
+ return;
221
+ }
222
+ if (config.verifyMode === "strict") {
223
+ const matched = config.verifyRules?.some((r) => r.guildId === session.guildId);
224
+ if (!matched) return;
225
+ }
226
+ }
227
+ const verdict = await checkCriteria(session, kind);
228
+ if (verdict === true) {
229
+ await executeAction(session, kind, true);
230
+ await sendNotice(session, kind, "auto_pass");
231
+ } else if (typeof verdict === "string") {
232
+ await executeAction(session, kind, false, verdict);
233
+ await sendNotice(session, kind, "auto_reject");
206
234
  } else {
207
- await setManual(session, kind);
235
+ if (kind === "member" && config.verifyMode === "strict") return;
236
+ await setupManual(session, kind);
208
237
  }
209
238
  } catch (error) {
210
- logger.error(`处理请求出错: ${error}`);
239
+ logger.error(`处理失败: ${error}`);
211
240
  }
212
- }, "process");
213
- if (config.enable !== false) {
214
- const bind = /* @__PURE__ */ __name((kind) => async (session) => {
215
- const data = session.event?._data || {};
216
- session.userId = data.user_id?.toString();
217
- if (kind !== "friend") session.guildId = data.group_id?.toString();
218
- await process(session, kind);
219
- }, "bind");
220
- ctx.on("friend-request", bind("friend"));
221
- ctx.on("guild-request", bind("guild"));
222
- ctx.on("guild-member-request", bind("member"));
223
- ctx.on("guild-added", bind("guild"));
224
- ctx.middleware(async (session, next) => {
225
- if (typeof session.content !== "string" || !session.quote?.id) return next();
226
- const task = tasks.get(session.quote.id);
227
- if (!task) return next();
228
- const target = config.notifyTarget || "";
229
- const [type, id] = target.split(":");
230
- if (type === "private" ? session.userId !== id : session.guildId !== id) return next();
231
- const text = session.content.replace(/<(quote|at)\s+[^>]*\/>/gi, "").trim();
232
- const match = text.match(/^(y|n|通过|拒绝)(?:\s+(.*))?$/);
233
- if (!match) return next();
234
- if (task.timer) clearTimeout(task.timer);
235
- task.messages.forEach((msg) => tasks.delete(msg));
236
- const pass = match[1] === "y" || match[1] === "通过";
237
- const extra = match[2]?.trim() || "";
238
- const ok = await doAction(task.session, task.kind, pass, pass ? "" : extra, pass && task.kind === "friend" ? extra : "");
239
- const reply = ok ? `已${pass ? "通过" : "拒绝"}该请求` : `处理该请求时出错`;
240
- await (type === "private" ? session.bot.sendPrivateMessage(id, reply) : session.bot.sendMessage(id, reply));
241
- });
242
- }
241
+ }, "handleEvent");
242
+ const hookEvent = /* @__PURE__ */ __name((kind) => async (session) => {
243
+ const eventData = session.event?._data || {};
244
+ session.userId = eventData.user_id;
245
+ if (kind !== "friend") session.guildId = eventData.group_id;
246
+ await handleEvent(session, kind);
247
+ }, "hookEvent");
248
+ ctx.on("friend-request", hookEvent("friend"));
249
+ ctx.on("guild-request", hookEvent("guild"));
250
+ ctx.on("guild-member-request", hookEvent("member"));
251
+ ctx.on("guild-added", hookEvent("guild"));
252
+ ctx.middleware(async (session, next) => {
253
+ if (typeof session.content !== "string" || !session.quote?.id) return next();
254
+ const activeTask = activeTasks.get(session.quote.id);
255
+ if (!activeTask) return next();
256
+ const notifyConfig = config.notifyTarget || "";
257
+ const [targetType, targetId] = notifyConfig.split(":");
258
+ if (targetType === "private" ? session.userId !== targetId : session.guildId !== targetId) return next();
259
+ const input = session.content.replace(/<(quote|at)\s+[^>]*\/>/gi, "").trim();
260
+ const cmdMatch = input.match(/^(y|n|通过|拒绝)(?:\s+(.*))?$/);
261
+ if (!cmdMatch) return next();
262
+ if (activeTask.timer) clearTimeout(activeTask.timer);
263
+ activeTask.messages.forEach((msg) => activeTasks.delete(msg));
264
+ const isApprove = cmdMatch[1] === "y" || cmdMatch[1] === "通过";
265
+ const extraInfo = cmdMatch[2]?.trim() || "";
266
+ const isSuccess = await executeAction(activeTask.session, activeTask.kind, isApprove, isApprove ? "" : extraInfo, isApprove && activeTask.kind === "friend" ? extraInfo : "");
267
+ const replyText = isSuccess ? `已${isApprove ? "通过" : "拒绝"}该请求` : `处理请求失败`;
268
+ await (targetType === "private" ? session.bot.sendPrivateMessage(targetId, replyText) : session.bot.sendMessage(targetId, replyText));
269
+ });
243
270
  }
244
271
  __name(apply, "apply");
245
272
  // Annotate the CommonJS export names for ESM import in node:
246
273
  0 && (module.exports = {
247
274
  Config,
248
275
  apply,
276
+ inject,
249
277
  name,
250
278
  usage
251
279
  });
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.0",
4
+ "version": "1.0.2",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],
package/readme.md CHANGED
@@ -2,4 +2,66 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/koishi-plugin-onebot-verifier?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-onebot-verifier)
4
4
 
5
- 适用于 Onebot 的审核插件,支持自动审核好友/加群/邀请请求
5
+ **适用于 OneBot 协议的综合审核管理插件**。本插件支持自动处理好友申请、加群申请以及进群邀请,并提供灵活的人工审核通知与指令操作功能。
6
+
7
+ ## ✨ 功能特性
8
+
9
+ - **多场景覆盖**:支持好友申请、他人加群、邀请机器人进群三种场景。
10
+ - **自动审核机制**:
11
+ - **等级校验**:根据 QQ 等级进行初步筛选。
12
+ - **正则匹配**:对验证信息进行关键词正则匹配。
13
+ - **群规模校验**:针对机器人被邀请进群,可设置最小成员数或最小群容量要求。
14
+ - **三种工作模式**:
15
+ - `strict`(规则模式):仅处理配置了规则的请求。
16
+ - `assist`(辅助模式):规则不匹配时转为人工审核。
17
+ - `manual`(手动模式):全人工介入。
18
+ - **交互式人工审核**:通过引用通知消息并回复 `y/n` 即可快速通过或拒绝申请。
19
+ - **超时自动处理**:防止人工审核积压,支持超时后自动通过或拒绝。
20
+
21
+ ## ⚙️ 配置项说明
22
+
23
+ ### 1. 基础配置
24
+
25
+ - **通知目标 (`notifyTarget`)**: 审核信息的推送位置。格式为 `private:QQ号` 或 `guild:群号`。
26
+ - **发送全部通知 (`notifyAuto`)**: 开启后,即便被插件自动通过/拒绝的请求也会发送通知。
27
+ - **调试模式 (`debugMode`)**: 开启后将输出更详细的日志。
28
+
29
+ ### 2. 好友与邀群配置
30
+
31
+ - **请求超时时长 (`timeout`)**: 人工审核的等待时间(单位:分钟)。
32
+ - **默认超时操作 (`timeoutAction`)**: 超时未处理时执行的操作(同意/拒绝)。
33
+ - **最低好友等级 (`friendLevel`)**: 申请人 QQ 等级需达到的标准(-1 为不限制)。
34
+ - **好友验证正则 (`friendRegex`)**: 匹配好友申请时的验证信息。
35
+ - **最低群成员数 (`minMembers`)**: 机器人被邀请进群时,该群的现有人数要求。
36
+ - **最低受邀容量 (`maxCapacity`)**: 机器人被邀请进群时,该群的群上限人数要求。
37
+
38
+ ### 3. 加群请求配置 (用户加群)
39
+
40
+ - **处理模式 (`verifyMode`)**:
41
+ - `规则`: 仅处理在下方“加群验证配置”中列出的群。
42
+ - `超时`: 匹配不到规则时,发送人工审核,超时后执行默认操作。
43
+ - `手动`: 所有申请直接进入人工审核。
44
+ - **加群验证配置 (`verifyRules`)**:
45
+ - 为特定群设置专属的验证正则、最低等级要求及超时处理动作。
46
+
47
+ ## 🎮 使用方法
48
+
49
+ ### 人工审核交互
50
+
51
+ 当有新的申请触发人工审核时,插件会向 `notifyTarget` 发送通知。你只需**引用该通知消息**并回复:
52
+
53
+ - **通过申请**:回复 `y` 或 `通过`。
54
+ - *提示:若是好友申请,后面可跟备注(如:`y 朋友`)。*
55
+ - **拒绝申请**:回复 `n` 或 `拒绝`。
56
+ - *提示:后面可以跟拒绝理由(如:`n 等级太低`)。*
57
+
58
+ ### 自动处理逻辑
59
+
60
+ 1. **好友申请**:先匹配正则,再校验等级。
61
+ 2. **机器人进群**:管理员(权限 > 1)邀请直接通过;普通用户邀请则校验成员数和群容量。
62
+ 3. **用户进群**:按照 `verifyRules` 中的设置执行。若不满足条件,则根据 `verifyMode` 决定直接拒绝或转人工。
63
+
64
+ ## 🛠️ 技术细节
65
+
66
+ - 插件会自动剥离 OneBot 验证信息中的 `回答:` 等前缀,直接提取核心答案进行正则匹配。
67
+ - 对于已经进入群组但被判定不符合条件的邀请,插件会自动发送通知并执行退群操作。