koishi-plugin-onebot-verifier 0.0.1
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 +22 -0
- package/lib/index.js +312 -0
- package/lib/request.d.ts +84 -0
- package/package.json +35 -0
- package/readme.md +5 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "onebot-verifier";
|
|
3
|
+
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
|
+
export interface Config {
|
|
5
|
+
enable?: boolean;
|
|
6
|
+
notifyTarget?: string;
|
|
7
|
+
enableDebug?: boolean;
|
|
8
|
+
FriendLevel?: number;
|
|
9
|
+
FriendRequestAutoRegex?: string;
|
|
10
|
+
MemberRequestAutoRules?: {
|
|
11
|
+
guildId: string;
|
|
12
|
+
keyword: string;
|
|
13
|
+
minLevel: number;
|
|
14
|
+
}[];
|
|
15
|
+
GuildAllowUsers?: string[];
|
|
16
|
+
GuildMinMemberCount?: number;
|
|
17
|
+
GuildMaxCapacity?: number;
|
|
18
|
+
manualTimeout?: number;
|
|
19
|
+
manualTimeoutAction?: 'accept' | 'reject';
|
|
20
|
+
}
|
|
21
|
+
export declare const Config: Schema<Config>;
|
|
22
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Config: () => Config,
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
name: () => name,
|
|
26
|
+
usage: () => usage
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(src_exports);
|
|
29
|
+
var import_koishi = require("koishi");
|
|
30
|
+
var name = "onebot-verifier";
|
|
31
|
+
var usage = `
|
|
32
|
+
<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
|
+
<h2 style="margin-top: 0; color: #4a6ee0;">📌 插件说明</h2>
|
|
34
|
+
<p>📖 <strong>使用文档</strong>:请点击左上角的 <strong>插件主页</strong> 查看插件使用文档</p>
|
|
35
|
+
<p>🔍 <strong>更多插件</strong>:可访问 <a href="https://github.com/YisRime" style="color:#4a6ee0;text-decoration:none;">苡淞的 GitHub</a> 查看本人的所有插件</p>
|
|
36
|
+
</div>
|
|
37
|
+
<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);">
|
|
38
|
+
<h2 style="margin-top: 0; color: #e0574a;">❤️ 支持与反馈</h2>
|
|
39
|
+
<p>🌟 喜欢这个插件?请在 <a href="https://github.com/YisRime" style="color:#e0574a;text-decoration:none;">GitHub</a> 上给我一个 Star!</p>
|
|
40
|
+
<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
|
+
</div>
|
|
42
|
+
`;
|
|
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("请求处理配置");
|
|
63
|
+
function extractAnswers(message) {
|
|
64
|
+
if (!message) return "";
|
|
65
|
+
const lines = message.split(/[\r\n]+/);
|
|
66
|
+
const answers = lines.map((line) => line.trim()).filter((line) => /^(回答)[::]/i.test(line)).map((line) => line.replace(/^(回答)[::]\s*/i, ""));
|
|
67
|
+
if (answers.length > 0) return answers.join("\n");
|
|
68
|
+
return message;
|
|
69
|
+
}
|
|
70
|
+
__name(extractAnswers, "extractAnswers");
|
|
71
|
+
function apply(ctx, config = {}) {
|
|
72
|
+
const logger = new import_koishi.Logger("onebot-verifier");
|
|
73
|
+
const requestNumberMap = /* @__PURE__ */ new Map();
|
|
74
|
+
let nextRequestNumber = 1;
|
|
75
|
+
const activeRequests = /* @__PURE__ */ new Map();
|
|
76
|
+
const processedFlags = /* @__PURE__ */ new Set();
|
|
77
|
+
const cleanupActiveRequest = /* @__PURE__ */ __name((requestKey) => {
|
|
78
|
+
const activeRequest = activeRequests.get(requestKey);
|
|
79
|
+
if (!activeRequest) return;
|
|
80
|
+
activeRequest.disposer?.();
|
|
81
|
+
if (activeRequest.timeoutTimer) clearTimeout(activeRequest.timeoutTimer);
|
|
82
|
+
requestNumberMap.delete(activeRequest.requestNumber);
|
|
83
|
+
activeRequests.delete(requestKey);
|
|
84
|
+
}, "cleanupActiveRequest");
|
|
85
|
+
const processRequestAction = /* @__PURE__ */ __name(async (session, type, approve, reason = "", remark = "") => {
|
|
86
|
+
try {
|
|
87
|
+
const eventData = session.event?._data || {};
|
|
88
|
+
if (!approve && type === "guild" && session.guildId && (session.event?.type === "guild-added" || eventData.notice_type === "group_increase")) {
|
|
89
|
+
if (reason) {
|
|
90
|
+
try {
|
|
91
|
+
await session.bot.sendMessage(session.guildId, `将退出该群 ${reason}`);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
logger.warn(`发送退群通知失败: ${e}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
if (session.onebot) await session.onebot.setGroupLeave(session.guildId, false);
|
|
98
|
+
return true;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
logger.error(`退出群组 ${session.guildId} 失败: ${e}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const flag = eventData.flag;
|
|
105
|
+
if (!flag || !session.onebot) return false;
|
|
106
|
+
if (type === "friend") await session.onebot.setFriendAddRequest(flag, approve, remark);
|
|
107
|
+
else await session.onebot.setGroupAddRequest(flag, eventData.sub_type ?? "add", approve, approve ? "" : reason);
|
|
108
|
+
return true;
|
|
109
|
+
} catch (e) {
|
|
110
|
+
logger.error(`请求处理失败: ${e}`);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}, "processRequestAction");
|
|
114
|
+
const sendRequestNotification = /* @__PURE__ */ __name(async (session, type, status, details = {}) => {
|
|
115
|
+
const { notifyTarget = "" } = config;
|
|
116
|
+
if (!notifyTarget) return;
|
|
117
|
+
const [targetType, targetId] = notifyTarget.split(":");
|
|
118
|
+
if (!targetId || targetType !== "guild" && targetType !== "private") return;
|
|
119
|
+
try {
|
|
120
|
+
const eventData = session.event?._data || {};
|
|
121
|
+
const user = session.userId ? await session.bot.getUser?.(session.userId)?.catch(() => null) : null;
|
|
122
|
+
const userName = user?.name || session.userId || "未知用户";
|
|
123
|
+
const guild = type !== "friend" && session.guildId ? await session.bot.getGuild?.(session.guildId)?.catch(() => null) : null;
|
|
124
|
+
const operator = eventData.operator_id && session.userId && eventData.operator_id.toString() !== session.userId ? await session.bot.getUser?.(eventData.operator_id.toString())?.catch(() => null) : null;
|
|
125
|
+
const msgLines = [];
|
|
126
|
+
if (user?.avatar) msgLines.push(`<image url="${user.avatar}"/>`);
|
|
127
|
+
let requestTypeText = type === "friend" ? "好友申请" : type === "member" ? "加群请求" : "群组邀请";
|
|
128
|
+
msgLines.push(`类型:${requestTypeText}`);
|
|
129
|
+
if (type !== "guild" || session.userId !== session.selfId) {
|
|
130
|
+
msgLines.push(`用户:${userName}${session.userId ? `(${session.userId})` : ""}`);
|
|
131
|
+
}
|
|
132
|
+
if (operator) msgLines.push(`管理:${operator.name ? `${operator.name}(${eventData.operator_id})` : eventData.operator_id}`);
|
|
133
|
+
if (guild) msgLines.push(`群组:${guild.name ? `${guild.name}(${session.guildId})` : session.guildId}`);
|
|
134
|
+
if (eventData.comment) msgLines.push(`验证信息:${eventData.comment}`);
|
|
135
|
+
const sendFunc = targetType === "private" ? (m) => session.bot.sendPrivateMessage(targetId, m) : (m) => session.bot.sendMessage(targetId, m);
|
|
136
|
+
await sendFunc(msgLines.join("\n"));
|
|
137
|
+
if (status === "pending" && details.requestNumber) {
|
|
138
|
+
await sendFunc(`请回复以下命令处理请求 #${details.requestNumber}:
|
|
139
|
+
通过[y/ya]${details.requestNumber} [备注] | 拒绝[n/na]${details.requestNumber} [理由]`);
|
|
140
|
+
}
|
|
141
|
+
} catch (e) {
|
|
142
|
+
logger.error(`发送通知失败: ${e}`);
|
|
143
|
+
}
|
|
144
|
+
}, "sendRequestNotification");
|
|
145
|
+
const shouldAutoAccept = /* @__PURE__ */ __name(async (session, type) => {
|
|
146
|
+
const validationMessage = extractAnswers(session.event?._data?.comment);
|
|
147
|
+
const parseRegex = /* @__PURE__ */ __name((input) => {
|
|
148
|
+
const slashMatch = input.match(/^\/(.+)\/([a-z]*)$/);
|
|
149
|
+
if (slashMatch) return new RegExp(slashMatch[1], slashMatch[2]);
|
|
150
|
+
return new RegExp(input, "i");
|
|
151
|
+
}, "parseRegex");
|
|
152
|
+
switch (type) {
|
|
153
|
+
case "member": {
|
|
154
|
+
const rule = (config.MemberRequestAutoRules || []).find((r) => r.guildId === session.guildId);
|
|
155
|
+
if (!rule) return false;
|
|
156
|
+
if (rule.keyword) {
|
|
157
|
+
try {
|
|
158
|
+
if (!parseRegex(rule.keyword).test(validationMessage)) return false;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if ((rule.minLevel ?? -1) >= 0 && session.onebot && session.userId) {
|
|
164
|
+
try {
|
|
165
|
+
const userInfo = await session.onebot.getStrangerInfo(session.userId, true);
|
|
166
|
+
if ((userInfo.qqLevel ?? 0) < rule.minLevel) return `QQ 等级低于${rule.minLevel}级`;
|
|
167
|
+
} catch (e) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
case "friend": {
|
|
174
|
+
if (config.FriendRequestAutoRegex) {
|
|
175
|
+
try {
|
|
176
|
+
if (parseRegex(config.FriendRequestAutoRegex).test(validationMessage)) return true;
|
|
177
|
+
} catch (e) {
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if ((config.FriendLevel ?? -1) >= 0 && session.onebot && session.userId) {
|
|
181
|
+
try {
|
|
182
|
+
const userInfo = await session.onebot.getStrangerInfo(session.userId, true);
|
|
183
|
+
if ((userInfo.qqLevel ?? 0) < config.FriendLevel) return `QQ 等级低于${config.FriendLevel}级`;
|
|
184
|
+
return true;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
case "guild": {
|
|
192
|
+
if (session.userId && (config.GuildAllowUsers || []).includes(session.userId)) return true;
|
|
193
|
+
try {
|
|
194
|
+
if (session.userId) {
|
|
195
|
+
const user = await ctx.database.getUser(session.platform, session.userId);
|
|
196
|
+
if (user && user.authority > 1) return true;
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
if ((config.GuildMinMemberCount ?? -1) >= 0 || (config.GuildMaxCapacity ?? -1) >= 0) {
|
|
201
|
+
if (session.onebot && session.guildId) {
|
|
202
|
+
try {
|
|
203
|
+
const info = await session.onebot.getGroupInfo(session.guildId, true);
|
|
204
|
+
if (config.GuildMinMemberCount >= 0 && info.member_count < config.GuildMinMemberCount) return `群成员不足${config.GuildMinMemberCount}人`;
|
|
205
|
+
if (config.GuildMaxCapacity >= 0 && info.max_member_count < config.GuildMaxCapacity) return `群容量不足${config.GuildMaxCapacity}`;
|
|
206
|
+
return true;
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}, "shouldAutoAccept");
|
|
216
|
+
const setupManualHandling = /* @__PURE__ */ __name(async (session, type, requestId) => {
|
|
217
|
+
const requestNumber = nextRequestNumber++;
|
|
218
|
+
requestNumberMap.set(requestNumber, requestId);
|
|
219
|
+
const activeRequest = { session, type, requestNumber };
|
|
220
|
+
activeRequests.set(requestId, activeRequest);
|
|
221
|
+
await sendRequestNotification(session, type, "pending", { requestNumber });
|
|
222
|
+
const timeoutMin = config.manualTimeout ?? 60;
|
|
223
|
+
if (timeoutMin > 0) {
|
|
224
|
+
activeRequest.timeoutTimer = setTimeout(async () => {
|
|
225
|
+
if (!activeRequests.has(requestId)) return;
|
|
226
|
+
cleanupActiveRequest(requestId);
|
|
227
|
+
const action = config.manualTimeoutAction || "accept";
|
|
228
|
+
await processRequestAction(session, type, action === "accept", action === "reject" ? "处理超时自动拒绝" : "");
|
|
229
|
+
const [tType, tId] = (config.notifyTarget || "").split(":");
|
|
230
|
+
if (tId) {
|
|
231
|
+
const send = tType === "private" ? (m) => session.bot.sendPrivateMessage(tId, m) : (m) => session.bot.sendMessage(tId, m);
|
|
232
|
+
await send(`请求 #${requestNumber} 超时,已自动${action === "accept" ? "通过" : "拒绝"}`);
|
|
233
|
+
}
|
|
234
|
+
}, timeoutMin * 60 * 1e3);
|
|
235
|
+
}
|
|
236
|
+
const [targetType, targetId] = (config.notifyTarget || "").split(":");
|
|
237
|
+
if (!targetId) return;
|
|
238
|
+
activeRequest.disposer = ctx.middleware(async (s, next) => {
|
|
239
|
+
if (typeof s.content !== "string") return next();
|
|
240
|
+
if (targetType === "private" ? s.userId !== targetId : s.guildId !== targetId) return next();
|
|
241
|
+
const bulkMatch = s.content.trim().match(/^(ya|na|全部同意|全部拒绝)\s*(.*)$/);
|
|
242
|
+
if (bulkMatch && activeRequests.size > 0) {
|
|
243
|
+
const reqs = [...activeRequests.values()];
|
|
244
|
+
activeRequests.clear();
|
|
245
|
+
requestNumberMap.clear();
|
|
246
|
+
const isApprove2 = bulkMatch[1] === "ya" || bulkMatch[1] === "全部同意";
|
|
247
|
+
const extra2 = bulkMatch[2]?.trim() || "";
|
|
248
|
+
for (const r of reqs) {
|
|
249
|
+
r.disposer?.();
|
|
250
|
+
if (r.timeoutTimer) clearTimeout(r.timeoutTimer);
|
|
251
|
+
await processRequestAction(r.session, r.type, isApprove2, !isApprove2 ? extra2 : "", isApprove2 && r.type === "friend" ? extra2 : "");
|
|
252
|
+
}
|
|
253
|
+
const send2 = targetType === "private" ? (m) => s.bot.sendPrivateMessage(targetId, m) : (m) => s.bot.sendMessage(targetId, m);
|
|
254
|
+
await send2(`已${isApprove2 ? "通过" : "拒绝"} ${reqs.length} 个请求`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const match = s.content.trim().match(new RegExp(`^(y|n|通过|拒绝)(${requestNumber})\\s*(.*)$`));
|
|
258
|
+
if (!match) return next();
|
|
259
|
+
cleanupActiveRequest(requestId);
|
|
260
|
+
const isApprove = match[1] === "y" || match[1] === "通过";
|
|
261
|
+
const extra = match[3]?.trim() || "";
|
|
262
|
+
const success = await processRequestAction(session, type, isApprove, !isApprove ? extra : "", isApprove && type === "friend" ? extra : "");
|
|
263
|
+
const send = targetType === "private" ? (m) => s.bot.sendPrivateMessage(targetId, m) : (m) => s.bot.sendMessage(targetId, m);
|
|
264
|
+
if (success) await send(`请求 #${requestNumber} 已${isApprove ? "通过" : "拒绝"}`);
|
|
265
|
+
else await send(`处理请求 #${requestNumber} 失败`);
|
|
266
|
+
});
|
|
267
|
+
}, "setupManualHandling");
|
|
268
|
+
const processRequest = /* @__PURE__ */ __name(async (session, type) => {
|
|
269
|
+
const flag = session.event?._data?.flag;
|
|
270
|
+
if (flag) {
|
|
271
|
+
if (processedFlags.has(flag)) return;
|
|
272
|
+
processedFlags.add(flag);
|
|
273
|
+
setTimeout(() => processedFlags.delete(flag), 6e4);
|
|
274
|
+
}
|
|
275
|
+
const requestKey = type === "friend" ? `friend:${session.userId}` : type === "guild" ? `guild:${session.guildId}` : `member:${session.userId}:${session.guildId}`;
|
|
276
|
+
cleanupActiveRequest(requestKey);
|
|
277
|
+
try {
|
|
278
|
+
const autoResult = await shouldAutoAccept(session, type);
|
|
279
|
+
if (autoResult === true) {
|
|
280
|
+
await processRequestAction(session, type, true);
|
|
281
|
+
await sendRequestNotification(session, type, "approved");
|
|
282
|
+
} else if (typeof autoResult === "string") {
|
|
283
|
+
await processRequestAction(session, type, false, autoResult);
|
|
284
|
+
await sendRequestNotification(session, type, "rejected", { reason: autoResult });
|
|
285
|
+
} else {
|
|
286
|
+
await setupManualHandling(session, type, requestKey);
|
|
287
|
+
}
|
|
288
|
+
} catch (e) {
|
|
289
|
+
logger.error(`处理流程出错: ${e}`);
|
|
290
|
+
}
|
|
291
|
+
}, "processRequest");
|
|
292
|
+
if (config.enable !== false) {
|
|
293
|
+
const handle = /* @__PURE__ */ __name((type) => async (session) => {
|
|
294
|
+
const data = session.event?._data || {};
|
|
295
|
+
session.userId = data.user_id?.toString();
|
|
296
|
+
if (type !== "friend") session.guildId = data.group_id?.toString();
|
|
297
|
+
await processRequest(session, type);
|
|
298
|
+
}, "handle");
|
|
299
|
+
ctx.on("friend-request", handle("friend"));
|
|
300
|
+
ctx.on("guild-request", handle("guild"));
|
|
301
|
+
ctx.on("guild-member-request", handle("member"));
|
|
302
|
+
ctx.on("guild-added", handle("guild"));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
__name(apply, "apply");
|
|
306
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
307
|
+
0 && (module.exports = {
|
|
308
|
+
Config,
|
|
309
|
+
apply,
|
|
310
|
+
name,
|
|
311
|
+
usage
|
|
312
|
+
});
|
package/lib/request.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Context, Logger, Session } from 'koishi';
|
|
2
|
+
import { Config } from './index';
|
|
3
|
+
/**
|
|
4
|
+
* 请求类型
|
|
5
|
+
* - friend: 好友请求
|
|
6
|
+
* - guild: 群组请求
|
|
7
|
+
* - member: 群成员请求
|
|
8
|
+
*/
|
|
9
|
+
export type RequestType = 'friend' | 'guild' | 'member';
|
|
10
|
+
/**
|
|
11
|
+
* OneBot 用户信息接口
|
|
12
|
+
*/
|
|
13
|
+
export interface OneBotUserInfo {
|
|
14
|
+
/** 用户 ID */
|
|
15
|
+
user_id: number;
|
|
16
|
+
/** QQ等级 */
|
|
17
|
+
qqLevel?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* OneBot 群组信息接口
|
|
21
|
+
*/
|
|
22
|
+
export interface OneBotGroupInfo {
|
|
23
|
+
/** 群组 ID */
|
|
24
|
+
group_id: number;
|
|
25
|
+
/** 群组名称 */
|
|
26
|
+
group_name: string;
|
|
27
|
+
/** 成员数量 */
|
|
28
|
+
member_count: number;
|
|
29
|
+
/** 群组最大成员数 */
|
|
30
|
+
max_member_count: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* OneBot 请求处理类
|
|
34
|
+
* 处理好友请求、群组请求和群成员请求
|
|
35
|
+
*/
|
|
36
|
+
export declare class OnebotRequest {
|
|
37
|
+
private ctx;
|
|
38
|
+
private logger;
|
|
39
|
+
private config;
|
|
40
|
+
private requestNumberMap;
|
|
41
|
+
private nextRequestNumber;
|
|
42
|
+
private activeRequests;
|
|
43
|
+
private processedFlags;
|
|
44
|
+
/**
|
|
45
|
+
* 创建 OneBot 请求处理实例
|
|
46
|
+
* @param ctx - Koishi 上下文
|
|
47
|
+
* @param logger - 日志记录器
|
|
48
|
+
* @param config - 配置项
|
|
49
|
+
*/
|
|
50
|
+
constructor(ctx: Context, logger: Logger, config?: Config);
|
|
51
|
+
/**
|
|
52
|
+
* 清理并取消一个活动中的请求
|
|
53
|
+
*/
|
|
54
|
+
private cleanupActiveRequest;
|
|
55
|
+
/**
|
|
56
|
+
* 从验证消息中提取用户的回答
|
|
57
|
+
* 如果消息包含 "回答:" 格式,则只返回回答部分
|
|
58
|
+
*/
|
|
59
|
+
private extractAnswers;
|
|
60
|
+
/**
|
|
61
|
+
* 发送请求通知
|
|
62
|
+
*/
|
|
63
|
+
private sendRequestNotification;
|
|
64
|
+
/**
|
|
65
|
+
* 处理收到的请求
|
|
66
|
+
*/
|
|
67
|
+
processRequest(session: Session, type: RequestType): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* 判断是否应自动接受请求
|
|
70
|
+
*/
|
|
71
|
+
private shouldAutoAccept;
|
|
72
|
+
/**
|
|
73
|
+
* 处理请求操作(接受或拒绝)
|
|
74
|
+
*/
|
|
75
|
+
private processRequestAction;
|
|
76
|
+
/**
|
|
77
|
+
* 设置手动处理流程:通知、响应监听和超时回退
|
|
78
|
+
*/
|
|
79
|
+
private setupManualHandling;
|
|
80
|
+
/**
|
|
81
|
+
* 注册请求类事件监听器
|
|
82
|
+
*/
|
|
83
|
+
registerEventListeners(): void;
|
|
84
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-onebot-verifier",
|
|
3
|
+
"description": "适用于 Onebot 的审核插件,支持自动审核好友/加群/邀请请求",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"contributors": [
|
|
6
|
+
"Yis_Rime <yis_rime@outlook.com>"
|
|
7
|
+
],
|
|
8
|
+
"homepage": "https://github.com/Koishi-Plugin/onebot-verifier",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Koishi-Plugin/onebot-verifier.git"
|
|
12
|
+
},
|
|
13
|
+
"main": "lib/index.js",
|
|
14
|
+
"typings": "lib/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"license": "AGPL-3.0-only",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"chatbot",
|
|
22
|
+
"koishi",
|
|
23
|
+
"plugin",
|
|
24
|
+
"onebot",
|
|
25
|
+
"verifier",
|
|
26
|
+
"group",
|
|
27
|
+
"user"
|
|
28
|
+
],
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"koishi-plugin-adapter-onebot": "^6.1.3"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"koishi": "^4.18.3"
|
|
34
|
+
}
|
|
35
|
+
}
|