mioku-plugin-admin 2.2.2 → 2.3.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/README.md +5 -0
- package/commands/verify.ts +253 -0
- package/config.md +124 -0
- package/config.ts +35 -0
- package/index.ts +59 -4
- package/notify/welcome.ts +313 -0
- package/package.json +54 -1
- package/verify/chiral.ts +95 -0
- package/verify/config.ts +137 -0
- package/verify/index.ts +359 -0
- package/verify/number.ts +42 -0
- package/verify/reaction.ts +38 -0
- package/verify/state.ts +31 -0
- package/verify/types.ts +46 -0
package/verify/index.ts
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import type { MiokiContext } from "mioki";
|
|
2
|
+
import { getMemberRole } from "../config";
|
|
3
|
+
import { resolveMemberName, triggerSingleWelcome } from "../notify/welcome";
|
|
4
|
+
import { getGroupVerifyConfig, upsertGroupVerifyConfig } from "./config";
|
|
5
|
+
import type {
|
|
6
|
+
MemberJoinInfo,
|
|
7
|
+
PendingVerify,
|
|
8
|
+
VerifyController,
|
|
9
|
+
VerifyControllerOptions,
|
|
10
|
+
} from "./types";
|
|
11
|
+
import { clearTimers, getPendingMap, pendingKey } from "./state";
|
|
12
|
+
import { isReactionPass, sendReactionPrompt } from "./reaction";
|
|
13
|
+
import { isNumberAnswerCorrect, sendNumberPrompt } from "./number";
|
|
14
|
+
import { checkChiralAnswer, prepareChiral } from "./chiral";
|
|
15
|
+
|
|
16
|
+
const PASS_REACTION_EMOJI_ID = "144";
|
|
17
|
+
|
|
18
|
+
const VERIFY_PASS_PROMPT_INJECTION = {
|
|
19
|
+
title: "新成员通过入群验证",
|
|
20
|
+
content: "这位新成员刚刚通过了入群验证,请在欢迎语中点到验证通过类似话语",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function createVerifyController(
|
|
24
|
+
options: VerifyControllerOptions,
|
|
25
|
+
): VerifyController {
|
|
26
|
+
const {
|
|
27
|
+
ctx,
|
|
28
|
+
aiService,
|
|
29
|
+
getConfig,
|
|
30
|
+
getVerifyConfig,
|
|
31
|
+
getWelcomeEnabled,
|
|
32
|
+
setVerifyConfig,
|
|
33
|
+
} = options;
|
|
34
|
+
const pending = getPendingMap();
|
|
35
|
+
|
|
36
|
+
async function disableGroupVerify(groupId: number, reason: string) {
|
|
37
|
+
ctx.logger.warn(`admin verify 关闭群 ${groupId} 验证:${reason}`);
|
|
38
|
+
try {
|
|
39
|
+
await setVerifyConfig(
|
|
40
|
+
upsertGroupVerifyConfig(getVerifyConfig(), groupId, { enabled: false }),
|
|
41
|
+
);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
ctx.logger.error(`admin verify 关闭群验证写回配置失败: ${err}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function recallMessage(bot: any, messageId: number) {
|
|
48
|
+
try {
|
|
49
|
+
await bot.api("delete_msg", { message_id: messageId });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
ctx.logger.warn(`admin verify 撤回消息失败: ${err}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function kickMember(bot: any, groupId: number, userId: number) {
|
|
56
|
+
try {
|
|
57
|
+
await bot.api("set_group_kick", {
|
|
58
|
+
group_id: groupId,
|
|
59
|
+
user_id: userId,
|
|
60
|
+
reject_add_request: false,
|
|
61
|
+
});
|
|
62
|
+
} catch (err) {
|
|
63
|
+
ctx.logger.warn(`admin verify 踢出成员失败: ${err}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function removePending(key: string) {
|
|
68
|
+
const p = pending.get(key);
|
|
69
|
+
if (p) {
|
|
70
|
+
clearTimers(p);
|
|
71
|
+
pending.delete(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function passVerification(p: PendingVerify) {
|
|
76
|
+
if (p.passed) return;
|
|
77
|
+
p.passed = true;
|
|
78
|
+
clearTimers(p);
|
|
79
|
+
pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
|
|
80
|
+
|
|
81
|
+
if (p.mode === "reaction" && p.promptMessageId) {
|
|
82
|
+
const bot = ctx.pickBot(p.selfId);
|
|
83
|
+
if (bot) {
|
|
84
|
+
try {
|
|
85
|
+
await bot.addReaction(p.promptMessageId, PASS_REACTION_EMOJI_ID);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
ctx.logger.warn(`admin verify 通过表态失败: ${err}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!getWelcomeEnabled()) return;
|
|
93
|
+
try {
|
|
94
|
+
await triggerSingleWelcome({
|
|
95
|
+
ctx,
|
|
96
|
+
aiService,
|
|
97
|
+
getConfig,
|
|
98
|
+
selfId: p.selfId,
|
|
99
|
+
groupId: p.groupId,
|
|
100
|
+
groupName: p.groupName,
|
|
101
|
+
userId: p.userId,
|
|
102
|
+
memberName: p.memberName,
|
|
103
|
+
promptInjections: [VERIFY_PASS_PROMPT_INJECTION],
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
ctx.logger.error(`admin verify 通过后欢迎失败: ${err}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function failKick(p: PendingVerify, reason: string) {
|
|
111
|
+
clearTimers(p);
|
|
112
|
+
pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
|
|
113
|
+
const cfg = getVerifyConfig();
|
|
114
|
+
if (!cfg.kickOnFail) return;
|
|
115
|
+
const bot = ctx.pickBot(p.selfId);
|
|
116
|
+
if (!bot) return;
|
|
117
|
+
ctx.logger.info(
|
|
118
|
+
`admin verify 踢出群 ${p.groupId} 用户 ${p.userId}(${reason})`,
|
|
119
|
+
);
|
|
120
|
+
await kickMember(bot, p.groupId, p.userId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function timeoutExpire(p: PendingVerify) {
|
|
124
|
+
p.timeoutTimer = null;
|
|
125
|
+
const cfg = getVerifyConfig();
|
|
126
|
+
if (!cfg.kickOnTimeout) {
|
|
127
|
+
pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const bot = ctx.pickBot(p.selfId);
|
|
131
|
+
if (!bot) {
|
|
132
|
+
pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await bot.sendGroupMsg(p.groupId, [
|
|
137
|
+
ctx.segment.at(String(p.userId)),
|
|
138
|
+
ctx.segment.text(" 验证超时啦,下次再来哦~"),
|
|
139
|
+
]);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
ctx.logger.warn(`admin verify 超时提示发送失败: ${err}`);
|
|
142
|
+
}
|
|
143
|
+
ctx.logger.info(`admin verify 超时踢出群 ${p.groupId} 用户 ${p.userId}`);
|
|
144
|
+
await kickMember(bot, p.groupId, p.userId);
|
|
145
|
+
pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function startVerification(
|
|
149
|
+
info: MemberJoinInfo,
|
|
150
|
+
skipDelay = false,
|
|
151
|
+
): Promise<boolean> {
|
|
152
|
+
const cfg = getVerifyConfig();
|
|
153
|
+
const groupCfg = getGroupVerifyConfig(cfg, info.groupId);
|
|
154
|
+
if (!groupCfg.enabled) return false;
|
|
155
|
+
|
|
156
|
+
const bot = ctx.pickBot(info.selfId);
|
|
157
|
+
if (!bot) return false;
|
|
158
|
+
|
|
159
|
+
const botRole = await getMemberRole(bot, info.groupId, info.selfId);
|
|
160
|
+
if (botRole !== "owner" && botRole !== "admin") {
|
|
161
|
+
try {
|
|
162
|
+
await bot.sendGroupMsg(info.groupId, [
|
|
163
|
+
ctx.segment.text("我在不是管理员,没法入群验证啦,本群验证已关闭~"),
|
|
164
|
+
]);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
ctx.logger.warn(`admin verify 提醒本群验证关闭失败: ${err}`);
|
|
167
|
+
}
|
|
168
|
+
await disableGroupVerify(
|
|
169
|
+
info.groupId,
|
|
170
|
+
`Bot 群内身份为 ${botRole},非群主/管理员`,
|
|
171
|
+
);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const mode = groupCfg.mode;
|
|
176
|
+
const memberName = await resolveMemberName(
|
|
177
|
+
ctx,
|
|
178
|
+
info.groupId,
|
|
179
|
+
info.userId,
|
|
180
|
+
info.selfId,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const entry: PendingVerify = {
|
|
184
|
+
selfId: info.selfId,
|
|
185
|
+
groupId: info.groupId,
|
|
186
|
+
userId: info.userId,
|
|
187
|
+
memberName,
|
|
188
|
+
groupName: info.groupName,
|
|
189
|
+
mode,
|
|
190
|
+
invalidCount: 0,
|
|
191
|
+
startedAt: Date.now(),
|
|
192
|
+
passed: false,
|
|
193
|
+
timeoutTimer: null,
|
|
194
|
+
delayTimer: null,
|
|
195
|
+
};
|
|
196
|
+
if (mode === "reaction") {
|
|
197
|
+
entry.reactionEmojiId = cfg.reactionEmojiId;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const key = pendingKey(info.selfId, info.groupId, info.userId);
|
|
201
|
+
pending.set(key, entry);
|
|
202
|
+
|
|
203
|
+
if (mode === "reaction") {
|
|
204
|
+
const delay = skipDelay ? 0 : Math.max(0, cfg.reactionDelayMs);
|
|
205
|
+
entry.delayTimer = setTimeout(() => {
|
|
206
|
+
entry.delayTimer = null;
|
|
207
|
+
void sendReactionPrompt(ctx, cfg, entry);
|
|
208
|
+
}, delay);
|
|
209
|
+
} else if (mode === "number") {
|
|
210
|
+
void sendNumberPrompt(ctx, cfg, entry);
|
|
211
|
+
} else if (mode === "chiral") {
|
|
212
|
+
const ok = await prepareChiral(ctx, cfg, entry);
|
|
213
|
+
if (!ok) {
|
|
214
|
+
// 验证服务不可用时放行,避免误伤
|
|
215
|
+
removePending(key);
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
entry.timeoutTimer = setTimeout(
|
|
221
|
+
() => {
|
|
222
|
+
void timeoutExpire(entry);
|
|
223
|
+
},
|
|
224
|
+
Math.max(1000, cfg.verifyTimeoutMs),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function onGroupMessage(event: any) {
|
|
231
|
+
if (event?.message_type !== "group") return;
|
|
232
|
+
const selfId = Number(event?.self_id || 0);
|
|
233
|
+
const groupId = Number(event?.group_id || 0);
|
|
234
|
+
const userId = Number(event?.user_id || 0);
|
|
235
|
+
if (!selfId || !groupId || !userId) return;
|
|
236
|
+
if (userId === selfId) return;
|
|
237
|
+
|
|
238
|
+
const key = pendingKey(selfId, groupId, userId);
|
|
239
|
+
const p = pending.get(key);
|
|
240
|
+
if (!p || p.passed) return;
|
|
241
|
+
|
|
242
|
+
const cfg = getVerifyConfig();
|
|
243
|
+
const text = ctx.text(event) || "";
|
|
244
|
+
const messageId = Number(event?.message_id || 0);
|
|
245
|
+
const bot = ctx.pickBot(selfId);
|
|
246
|
+
|
|
247
|
+
if (p.mode === "number" && isNumberAnswerCorrect(p, text)) {
|
|
248
|
+
await passVerification(p);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (p.mode === "chiral") {
|
|
253
|
+
const result = checkChiralAnswer(p, text);
|
|
254
|
+
if (result.status === "pass") {
|
|
255
|
+
await passVerification(p);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (result.status === "progress") {
|
|
259
|
+
if (bot) {
|
|
260
|
+
try {
|
|
261
|
+
await bot.sendGroupMsg(groupId, [
|
|
262
|
+
ctx.segment.at(String(userId)),
|
|
263
|
+
ctx.segment.text(` 答对一部分啦,还差 ${result.remaining} 个哦~`),
|
|
264
|
+
]);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
ctx.logger.warn(`admin verify 手性碳进度提示发送失败: ${err}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (messageId && bot) {
|
|
274
|
+
await recallMessage(bot, messageId);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
p.invalidCount += 1;
|
|
278
|
+
if (p.invalidCount >= cfg.maxInvalidMessages) {
|
|
279
|
+
await failKick(p, `连续 ${cfg.maxInvalidMessages} 次无关消息`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function onGroupReaction(event: any) {
|
|
284
|
+
const selfId = Number(event?.self_id || 0);
|
|
285
|
+
const groupId = Number(event?.group_id || 0);
|
|
286
|
+
const userId = Number(event?.user_id || 0);
|
|
287
|
+
if (!selfId || !groupId || !userId) return;
|
|
288
|
+
if (userId === selfId) return;
|
|
289
|
+
if (event?.is_add === false) return;
|
|
290
|
+
|
|
291
|
+
const key = pendingKey(selfId, groupId, userId);
|
|
292
|
+
const p = pending.get(key);
|
|
293
|
+
if (!p || p.passed || p.mode !== "reaction") return;
|
|
294
|
+
|
|
295
|
+
if (isReactionPass(p, event)) {
|
|
296
|
+
await passVerification(p);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const messageDispose = ctx.handle(
|
|
301
|
+
"message.group" as any,
|
|
302
|
+
async (event: any) => {
|
|
303
|
+
try {
|
|
304
|
+
await onGroupMessage(event);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
ctx.logger.error(`admin verify message 处理失败: ${err}`);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const reactionDispose = ctx.handle(
|
|
312
|
+
"notice.group.reaction" as any,
|
|
313
|
+
async (event: any) => {
|
|
314
|
+
try {
|
|
315
|
+
await onGroupReaction(event);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
ctx.logger.error(`admin verify reaction 处理失败: ${err}`);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const decreaseDispose = ctx.handle(
|
|
323
|
+
"notice.group.decrease" as any,
|
|
324
|
+
async (event: any) => {
|
|
325
|
+
const selfId = Number(event?.self_id || 0);
|
|
326
|
+
const groupId = Number(event?.group_id || 0);
|
|
327
|
+
const userId = Number(event?.user_id || 0);
|
|
328
|
+
if (!selfId || !groupId || !userId) return;
|
|
329
|
+
const key = pendingKey(selfId, groupId, userId);
|
|
330
|
+
if (!pending.has(key)) return;
|
|
331
|
+
removePending(key);
|
|
332
|
+
ctx.logger.info(
|
|
333
|
+
`admin verify 成员 ${userId} 退出群 ${groupId},清除验证队列`,
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
async function restartVerification(info: MemberJoinInfo): Promise<boolean> {
|
|
339
|
+
removePending(pendingKey(info.selfId, info.groupId, info.userId));
|
|
340
|
+
return startVerification(info, true);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function bypassVerification(info: MemberJoinInfo): Promise<void> {
|
|
344
|
+
removePending(pendingKey(info.selfId, info.groupId, info.userId));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
handleMemberJoin: startVerification,
|
|
349
|
+
restartVerification,
|
|
350
|
+
bypassVerification,
|
|
351
|
+
dispose() {
|
|
352
|
+
for (const p of pending.values()) clearTimers(p);
|
|
353
|
+
pending.clear();
|
|
354
|
+
messageDispose();
|
|
355
|
+
reactionDispose();
|
|
356
|
+
decreaseDispose();
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
package/verify/number.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MiokiContext } from "mioki";
|
|
2
|
+
import type { VerifyConfig } from "./config";
|
|
3
|
+
import type { PendingVerify } from "./types";
|
|
4
|
+
|
|
5
|
+
function genNumberQuestion(): { question: string; answer: number } {
|
|
6
|
+
const a = Math.floor(Math.random() * 99) + 1;
|
|
7
|
+
const b = Math.floor(Math.random() * 99) + 1;
|
|
8
|
+
if (Math.random() < 0.5 && a >= b) {
|
|
9
|
+
return { question: `${a} - ${b} = ?`, answer: a - b };
|
|
10
|
+
}
|
|
11
|
+
return { question: `${a} + ${b} = ?`, answer: a + b };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function extractNumbers(text: string): number[] {
|
|
15
|
+
const matches = String(text || "").match(/-?\d+/g);
|
|
16
|
+
return matches ? matches.map(Number) : [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function sendNumberPrompt(
|
|
20
|
+
ctx: MiokiContext,
|
|
21
|
+
cfg: VerifyConfig,
|
|
22
|
+
p: PendingVerify,
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const bot = ctx.pickBot(p.selfId);
|
|
25
|
+
if (!bot) return;
|
|
26
|
+
const { question, answer } = genNumberQuestion();
|
|
27
|
+
p.numberAnswer = answer;
|
|
28
|
+
const prompt = cfg.numberPrompt.replace("{question}", question);
|
|
29
|
+
try {
|
|
30
|
+
await bot.sendGroupMsg(p.groupId, [
|
|
31
|
+
ctx.segment.at(String(p.userId)),
|
|
32
|
+
ctx.segment.text(` ${prompt}`),
|
|
33
|
+
]);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
ctx.logger.warn(`admin verify 发送数字提示失败: ${err}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isNumberAnswerCorrect(p: PendingVerify, text: string): boolean {
|
|
40
|
+
if (p.numberAnswer == null) return false;
|
|
41
|
+
return extractNumbers(text).includes(p.numberAnswer);
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MiokiContext } from "mioki";
|
|
2
|
+
import type { VerifyConfig } from "./config";
|
|
3
|
+
import type { PendingVerify } from "./types";
|
|
4
|
+
|
|
5
|
+
export async function sendReactionPrompt(
|
|
6
|
+
ctx: MiokiContext,
|
|
7
|
+
cfg: VerifyConfig,
|
|
8
|
+
p: PendingVerify,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const bot = ctx.pickBot(p.selfId);
|
|
11
|
+
if (!bot) return;
|
|
12
|
+
let messageId: number | undefined;
|
|
13
|
+
try {
|
|
14
|
+
const res = await bot.sendGroupMsg(p.groupId, [
|
|
15
|
+
ctx.segment.at(String(p.userId)),
|
|
16
|
+
ctx.segment.text(` ${cfg.reactionPrompt}`),
|
|
17
|
+
]);
|
|
18
|
+
messageId = Number(res?.message_id || 0) || undefined;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
ctx.logger.warn(`admin verify 发送回应提示失败: ${err}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!messageId) return;
|
|
24
|
+
p.promptMessageId = messageId;
|
|
25
|
+
try {
|
|
26
|
+
await bot.addReaction(messageId, cfg.reactionEmojiId);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
ctx.logger.warn(`admin verify 添加表态失败: ${err}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isReactionPass(p: PendingVerify, event: any): boolean {
|
|
33
|
+
if (!p.promptMessageId) return false;
|
|
34
|
+
if (Number(event?.message_id || 0) !== p.promptMessageId) return false;
|
|
35
|
+
const emojiId = String(p.reactionEmojiId || "");
|
|
36
|
+
const likes: any[] = Array.isArray(event?.likes) ? event.likes : [];
|
|
37
|
+
return likes.some((l) => String(l?.emoji_id || "") === emojiId);
|
|
38
|
+
}
|
package/verify/state.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getPluginRuntimeState } from "mioku";
|
|
2
|
+
import type { PendingVerify } from "./types";
|
|
3
|
+
|
|
4
|
+
const RUNTIME_KEY = "verifyPending";
|
|
5
|
+
|
|
6
|
+
export function getPendingMap(): Map<string, PendingVerify> {
|
|
7
|
+
const state = getPluginRuntimeState("admin");
|
|
8
|
+
if (!state[RUNTIME_KEY]) {
|
|
9
|
+
state[RUNTIME_KEY] = new Map<string, PendingVerify>();
|
|
10
|
+
}
|
|
11
|
+
return state[RUNTIME_KEY] as Map<string, PendingVerify>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function pendingKey(
|
|
15
|
+
selfId: number,
|
|
16
|
+
groupId: number,
|
|
17
|
+
userId: number,
|
|
18
|
+
): string {
|
|
19
|
+
return `${selfId}:${groupId}:${userId}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function clearTimers(p: PendingVerify) {
|
|
23
|
+
if (p.timeoutTimer) {
|
|
24
|
+
clearTimeout(p.timeoutTimer);
|
|
25
|
+
p.timeoutTimer = null;
|
|
26
|
+
}
|
|
27
|
+
if (p.delayTimer) {
|
|
28
|
+
clearTimeout(p.delayTimer);
|
|
29
|
+
p.delayTimer = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/verify/types.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { MiokiContext } from "mioki";
|
|
2
|
+
import type { AIService } from "mioku";
|
|
3
|
+
import type { AdminConfig } from "../config";
|
|
4
|
+
import type { VerifyConfig, VerifyMode } from "./config";
|
|
5
|
+
|
|
6
|
+
export interface VerifyControllerOptions {
|
|
7
|
+
ctx: MiokiContext;
|
|
8
|
+
aiService?: AIService;
|
|
9
|
+
getConfig: () => AdminConfig;
|
|
10
|
+
getVerifyConfig: () => VerifyConfig;
|
|
11
|
+
getWelcomeEnabled: () => boolean;
|
|
12
|
+
setVerifyConfig: (next: VerifyConfig) => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MemberJoinInfo {
|
|
16
|
+
selfId: number;
|
|
17
|
+
groupId: number;
|
|
18
|
+
userId: number;
|
|
19
|
+
groupName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PendingVerify {
|
|
23
|
+
selfId: number;
|
|
24
|
+
groupId: number;
|
|
25
|
+
userId: number;
|
|
26
|
+
memberName: string;
|
|
27
|
+
groupName: string;
|
|
28
|
+
mode: VerifyMode;
|
|
29
|
+
promptMessageId?: number;
|
|
30
|
+
reactionEmojiId?: string;
|
|
31
|
+
numberAnswer?: number;
|
|
32
|
+
requiredRegions?: string[];
|
|
33
|
+
matchedRegions?: Set<string>;
|
|
34
|
+
invalidCount: number;
|
|
35
|
+
startedAt: number;
|
|
36
|
+
passed: boolean;
|
|
37
|
+
timeoutTimer: ReturnType<typeof setTimeout> | null;
|
|
38
|
+
delayTimer: ReturnType<typeof setTimeout> | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface VerifyController {
|
|
42
|
+
handleMemberJoin(info: MemberJoinInfo): Promise<boolean>;
|
|
43
|
+
restartVerification(info: MemberJoinInfo): Promise<boolean>;
|
|
44
|
+
bypassVerification(info: MemberJoinInfo): Promise<void>;
|
|
45
|
+
dispose(): void;
|
|
46
|
+
}
|