mioku-plugin-admin 2.3.5 → 3.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/verify/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { Bot, MiokuContext, RouteEvent } from "mioku";
2
+
2
3
  import { existsSync } from "fs";
3
4
  import { getMemberRole } from "../config";
4
5
  import { resolveMemberName, triggerSingleWelcome } from "../notify/welcome";
@@ -50,21 +51,17 @@ export function createVerifyController(
50
51
  }
51
52
  }
52
53
 
53
- async function recallMessage(bot: any, messageId: number) {
54
+ async function recallMessage(bot: Bot, messageId: number) {
54
55
  try {
55
- await bot.api("delete_msg", { message_id: messageId });
56
+ await bot.recallMessage(messageId);
56
57
  } catch (err) {
57
58
  ctx.logger.warn(`admin verify 撤回消息失败: ${err}`);
58
59
  }
59
60
  }
60
61
 
61
- async function kickMember(bot: any, groupId: number, userId: number) {
62
+ async function kickMember(bot: Bot, groupId: number, userId: number) {
62
63
  try {
63
- await bot.api("set_group_kick", {
64
- group_id: groupId,
65
- user_id: userId,
66
- reject_add_request: false,
67
- });
64
+ await bot.kickMember(groupId, userId, false);
68
65
  } catch (err) {
69
66
  ctx.logger.warn(`admin verify 踢出成员失败: ${err}`);
70
67
  }
@@ -78,20 +75,32 @@ export function createVerifyController(
78
75
  }
79
76
  }
80
77
 
81
- async function passVerification(p: PendingVerify) {
78
+ async function passVerification(p: PendingVerify, bot?: Bot) {
82
79
  if (p.passed) return;
83
80
  p.passed = true;
84
81
  clearTimers(p);
85
82
  pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
86
83
 
87
- if (p.mode === "reaction" && p.promptMessageId) {
88
- const bot = ctx.pickBot(p.selfId);
89
- if (bot) {
90
- try {
91
- await bot.addReaction(p.promptMessageId, PASS_REACTION_EMOJI_ID);
92
- } catch (err) {
93
- ctx.logger.warn(`admin verify 通过表态失败: ${err}`);
84
+ if (
85
+ p.mode === "reaction" &&
86
+ p.promptMessageId &&
87
+ bot &&
88
+ (bot.adapter === "onebotv11" || bot.adapter === "icqq")
89
+ ) {
90
+ try {
91
+ if (bot.adapter === "onebotv11") {
92
+ // onebot:set_msg_emoji_like 专属 action
93
+ await bot.sendApi("set_msg_emoji_like", {
94
+ message_id: p.promptMessageId,
95
+ emoji_id: PASS_REACTION_EMOJI_ID,
96
+ set: true,
97
+ });
98
+ } else {
99
+ // icqq:Group.setReaction(0x9082)
100
+ await bot.setReaction(p.promptMessageId, PASS_REACTION_EMOJI_ID, true);
94
101
  }
102
+ } catch (err) {
103
+ ctx.logger.warn(`admin verify 通过表态失败: ${err}`);
95
104
  }
96
105
  }
97
106
 
@@ -102,7 +111,7 @@ export function createVerifyController(
102
111
  groupId: p.groupId,
103
112
  userId: p.userId,
104
113
  groupName: p.groupName,
105
- });
114
+ }, bot);
106
115
  if (customSent) return;
107
116
  await triggerSingleWelcome({
108
117
  ctx,
@@ -120,33 +129,33 @@ export function createVerifyController(
120
129
  }
121
130
  }
122
131
 
123
- async function failKick(p: PendingVerify, reason: string) {
132
+ async function failKick(p: PendingVerify, reason: string, bot?: Bot) {
124
133
  clearTimers(p);
125
134
  pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
126
135
  const cfg = getVerifyConfig();
127
136
  if (!cfg.kickOnFail) return;
128
- const bot = ctx.pickBot(p.selfId);
129
- if (!bot) return;
137
+ const target = bot ?? p.bot;
138
+ if (!target) return;
130
139
  ctx.logger.info(
131
140
  `admin verify 踢出群 ${p.groupId} 用户 ${p.userId}(${reason})`,
132
141
  );
133
- await kickMember(bot, p.groupId, p.userId);
142
+ await kickMember(target, p.groupId, p.userId);
134
143
  }
135
144
 
136
- async function timeoutExpire(p: PendingVerify) {
145
+ async function timeoutExpire(p: PendingVerify, bot?: Bot) {
137
146
  p.timeoutTimer = null;
138
147
  const cfg = getVerifyConfig();
139
148
  if (!cfg.kickOnTimeout) {
140
149
  pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
141
150
  return;
142
151
  }
143
- const bot = ctx.pickBot(p.selfId);
144
- if (!bot) {
152
+ const target = bot ?? p.bot;
153
+ if (!target) {
145
154
  pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
146
155
  return;
147
156
  }
148
157
  try {
149
- await bot.sendGroupMsg(p.groupId, [
158
+ await target.sendMessage({ type: "group", group_id: p.groupId}, [
150
159
  ctx.segment.at(String(p.userId)),
151
160
  ctx.segment.text(" 验证超时啦,下次再来哦~"),
152
161
  ]);
@@ -154,25 +163,25 @@ export function createVerifyController(
154
163
  ctx.logger.warn(`admin verify 超时提示发送失败: ${err}`);
155
164
  }
156
165
  ctx.logger.info(`admin verify 超时踢出群 ${p.groupId} 用户 ${p.userId}`);
157
- await kickMember(bot, p.groupId, p.userId);
166
+ await kickMember(target, p.groupId, p.userId);
158
167
  pending.delete(pendingKey(p.selfId, p.groupId, p.userId));
159
168
  }
160
169
 
161
170
  async function startVerification(
162
171
  info: MemberJoinInfo,
172
+ bot?: Bot,
163
173
  skipDelay = false,
164
174
  ): Promise<boolean> {
165
175
  const cfg = getVerifyConfig();
166
176
  const groupCfg = getGroupVerifyConfig(cfg, info.groupId);
167
177
  if (!groupCfg.enabled) return false;
168
178
 
169
- const bot = ctx.pickBot(info.selfId);
170
179
  if (!bot) return false;
171
180
 
172
181
  const botRole = await getMemberRole(bot, info.groupId, info.selfId);
173
182
  if (botRole !== "owner" && botRole !== "admin") {
174
183
  try {
175
- await bot.sendGroupMsg(info.groupId, [
184
+ await bot.sendMessage({ type: "group", group_id: info.groupId}, [
176
185
  ctx.segment.text("我在不是管理员,没法入群验证啦,本群验证已关闭~"),
177
186
  ]);
178
187
  } catch (err) {
@@ -188,9 +197,9 @@ export function createVerifyController(
188
197
  const mode = groupCfg.mode;
189
198
  const memberName = await resolveMemberName(
190
199
  ctx,
200
+ bot,
191
201
  info.groupId,
192
202
  info.userId,
193
- info.selfId,
194
203
  );
195
204
 
196
205
  const entry: PendingVerify = {
@@ -200,6 +209,7 @@ export function createVerifyController(
200
209
  memberName,
201
210
  groupName: info.groupName,
202
211
  mode,
212
+ bot,
203
213
  invalidCount: 0,
204
214
  startedAt: Date.now(),
205
215
  passed: false,
@@ -232,7 +242,7 @@ export function createVerifyController(
232
242
 
233
243
  entry.timeoutTimer = setTimeout(
234
244
  () => {
235
- void timeoutExpire(entry);
245
+ void timeoutExpire(entry, bot);
236
246
  },
237
247
  Math.max(1000, cfg.verifyTimeoutMs),
238
248
  );
@@ -240,7 +250,7 @@ export function createVerifyController(
240
250
  return true;
241
251
  }
242
252
 
243
- async function onGroupMessage(event: any) {
253
+ async function onGroupMessage(event: RouteEvent<"message.group">) {
244
254
  if (event?.message_type !== "group") return;
245
255
  const selfId = Number(event?.self_id || 0);
246
256
  const groupId = Number(event?.group_id || 0);
@@ -255,23 +265,23 @@ export function createVerifyController(
255
265
  const cfg = getVerifyConfig();
256
266
  const text = ctx.text(event) || "";
257
267
  const messageId = Number(event?.message_id || 0);
258
- const bot = ctx.pickBot(selfId);
268
+ const bot = event.bot;
259
269
 
260
270
  if (p.mode === "number" && isNumberAnswerCorrect(p, text)) {
261
- await passVerification(p);
271
+ await passVerification(p, bot);
262
272
  return;
263
273
  }
264
274
 
265
275
  if (p.mode === "chiral") {
266
276
  const result = checkChiralAnswer(p, text);
267
277
  if (result.status === "pass") {
268
- await passVerification(p);
278
+ await passVerification(p, bot);
269
279
  return;
270
280
  }
271
281
  if (result.status === "progress") {
272
282
  if (bot) {
273
283
  try {
274
- await bot.sendGroupMsg(groupId, [
284
+ await bot.sendMessage({ type: "group", group_id: groupId}, [
275
285
  ctx.segment.at(String(userId)),
276
286
  ctx.segment.text(` 答对一部分啦,还差 ${result.remaining} 个哦~`),
277
287
  ]);
@@ -289,24 +299,25 @@ export function createVerifyController(
289
299
 
290
300
  p.invalidCount += 1;
291
301
  if (p.invalidCount >= cfg.maxInvalidMessages) {
292
- await failKick(p, `连续 ${cfg.maxInvalidMessages} 次无关消息`);
302
+ await failKick(p, `连续 ${cfg.maxInvalidMessages} 次无关消息`, bot);
293
303
  }
294
304
  }
295
305
 
296
- async function onGroupReaction(event: any) {
306
+ async function onGroupReaction(event: RouteEvent<"notice.group.reaction">) {
297
307
  const selfId = Number(event?.self_id || 0);
298
308
  const groupId = Number(event?.group_id || 0);
299
309
  const userId = Number(event?.user_id || 0);
300
310
  if (!selfId || !groupId || !userId) return;
301
311
  if (userId === selfId) return;
302
- if (event?.is_add === false) return;
312
+ const raw = event.raw as { is_add?: boolean } | undefined;
313
+ if (raw?.is_add === false) return;
303
314
 
304
315
  const key = pendingKey(selfId, groupId, userId);
305
316
  const p = pending.get(key);
306
317
  if (!p || p.passed || p.mode !== "reaction") return;
307
318
 
308
319
  if (isReactionPass(p, event)) {
309
- await passVerification(p);
320
+ await passVerification(p, event.bot);
310
321
  }
311
322
  }
312
323
 
@@ -348,20 +359,23 @@ export function createVerifyController(
348
359
  },
349
360
  );
350
361
 
351
- async function restartVerification(info: MemberJoinInfo): Promise<boolean> {
362
+ async function restartVerification(info: MemberJoinInfo, bot?: Bot): Promise<boolean> {
352
363
  removePending(pendingKey(info.selfId, info.groupId, info.userId));
353
- return startVerification(info, true);
364
+ return startVerification(info, bot, true);
354
365
  }
355
366
 
356
367
  async function bypassVerification(info: MemberJoinInfo): Promise<void> {
357
368
  removePending(pendingKey(info.selfId, info.groupId, info.userId));
358
369
  }
359
370
 
360
- async function trySendCustomWelcome(info: MemberJoinInfo): Promise<boolean> {
371
+ async function trySendCustomWelcome(
372
+ info: MemberJoinInfo,
373
+ bot?: Bot,
374
+ ): Promise<boolean> {
361
375
  const groupCfg = getGroupVerifyConfig(getVerifyConfig(), info.groupId);
362
376
  if (!hasCustomPrompt(groupCfg)) return false;
363
- const bot = ctx.pickBot(info.selfId);
364
377
  if (!bot) return false;
378
+ const target = bot;
365
379
  const segments: any[] = [];
366
380
  const prompt = String(groupCfg.customPrompt || "").trim();
367
381
  if (prompt) {
@@ -380,7 +394,7 @@ export function createVerifyController(
380
394
  }
381
395
  if (!segments.length) return false;
382
396
  try {
383
- await bot.sendGroupMsg(info.groupId, segments);
397
+ await target.sendMessage({ type: "group", group_id: info.groupId}, segments);
384
398
  return true;
385
399
  } catch (err) {
386
400
  ctx.logger.error(`admin verify 发送自定义入群提示失败: ${err}`);
package/verify/number.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { MiokuContext } from "mioku";
2
2
  import type { VerifyConfig } from "./config";
3
3
  import type { PendingVerify } from "./types";
4
4
 
@@ -17,17 +17,17 @@ function extractNumbers(text: string): number[] {
17
17
  }
18
18
 
19
19
  export async function sendNumberPrompt(
20
- ctx: MiokiContext,
20
+ ctx: MiokuContext,
21
21
  cfg: VerifyConfig,
22
22
  p: PendingVerify,
23
23
  ): Promise<void> {
24
- const bot = ctx.pickBot(p.selfId);
24
+ const bot = p.bot;
25
25
  if (!bot) return;
26
26
  const { question, answer } = genNumberQuestion();
27
27
  p.numberAnswer = answer;
28
28
  const prompt = cfg.numberPrompt.replace("{question}", question);
29
29
  try {
30
- await bot.sendGroupMsg(p.groupId, [
30
+ await bot.sendMessage({ type: "group", group_id: p.groupId}, [
31
31
  ctx.segment.at(String(p.userId)),
32
32
  ctx.segment.text(` ${prompt}`),
33
33
  ]);
@@ -1,38 +1,86 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { MiokuContext } from "mioku";
2
2
  import type { VerifyConfig } from "./config";
3
3
  import type { PendingVerify } from "./types";
4
4
 
5
5
  export async function sendReactionPrompt(
6
- ctx: MiokiContext,
6
+ ctx: MiokuContext,
7
7
  cfg: VerifyConfig,
8
8
  p: PendingVerify,
9
9
  ): Promise<void> {
10
- const bot = ctx.pickBot(p.selfId);
10
+ const bot = p.bot;
11
11
  if (!bot) return;
12
- let messageId: number | undefined;
12
+ let messageId: string | number | undefined;
13
13
  try {
14
- const res = await bot.sendGroupMsg(p.groupId, [
14
+ const res = await bot.sendMessage({ type: "group", group_id: p.groupId }, [
15
15
  ctx.segment.at(String(p.userId)),
16
16
  ctx.segment.text(` ${cfg.reactionPrompt}`),
17
17
  ]);
18
- messageId = Number(res?.message_id || 0) || undefined;
18
+ messageId = res?.message_id;
19
19
  } catch (err) {
20
20
  ctx.logger.warn(`admin verify 发送回应提示失败: ${err}`);
21
21
  return;
22
22
  }
23
- if (!messageId) return;
23
+ if (messageId == null || messageId === "") return;
24
24
  p.promptMessageId = messageId;
25
+ if (bot.adapter === "onebotv11") {
26
+ try {
27
+ await bot.sendApi("set_msg_emoji_like", {
28
+ message_id: messageId,
29
+ emoji_id: cfg.reactionEmojiId,
30
+ set: true,
31
+ });
32
+ } catch (err) {
33
+ ctx.logger.warn(`admin verify 添加表态失败: ${err}`);
34
+ }
35
+ } else if (bot.adapter === "icqq") {
36
+ // icqq:Group.setReaction(0x9082),message_id 为群消息 cqhttp 格式
37
+ try {
38
+ await bot.setReaction(messageId, cfg.reactionEmojiId, true);
39
+ } catch (err) {
40
+ ctx.logger.warn(`admin verify 添加表态失败: ${err}`);
41
+ }
42
+ }
43
+ }
44
+
45
+ /** icqq GroupReactionEvent 的 seq 提取(cqhttp message_id 为 base64,seq 在第 9-12 字节,与 icqq parseGroupMessageId 同布局) */
46
+ function seqOfIcqqMessageId(messageId: string | number): number {
25
47
  try {
26
- await bot.addReaction(messageId, cfg.reactionEmojiId);
27
- } catch (err) {
28
- ctx.logger.warn(`admin verify 添加表态失败: ${err}`);
48
+ const buf = Buffer.from(String(messageId), "base64");
49
+ return buf.length >= 12 ? buf.readUInt32BE(8) : 0;
50
+ } catch {
51
+ return 0;
29
52
  }
30
53
  }
31
54
 
32
- export function isReactionPass(p: PendingVerify, event: any): boolean {
55
+ export function isReactionPass(p: PendingVerify, event: unknown): boolean {
33
56
  if (!p.promptMessageId) return false;
34
- if (Number(event?.message_id || 0) !== p.promptMessageId) return false;
57
+ const ev = event as { raw?: unknown };
58
+ const raw = (ev.raw ?? event) as {
59
+ message_id?: unknown;
60
+ likes?: unknown;
61
+ notice_type?: unknown;
62
+ sub_type?: unknown;
63
+ set?: unknown;
64
+ seq?: unknown;
65
+ id?: unknown;
66
+ };
35
67
  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);
68
+
69
+ if (
70
+ raw?.sub_type === "reaction" &&
71
+ String(raw.notice_type ?? "") === "group"
72
+ ) {
73
+ if (raw.set === false) return false;
74
+ const seq = seqOfIcqqMessageId(p.promptMessageId);
75
+ return (
76
+ seq > 0 && Number(raw.seq) === seq && String(raw.id ?? "") === emojiId
77
+ );
78
+ }
79
+
80
+ if (Number(raw?.message_id || 0) !== Number(p.promptMessageId)) return false;
81
+ const likes: unknown[] = Array.isArray(raw?.likes) ? raw.likes : [];
82
+ return likes.some((l) => {
83
+ const item = l as { emoji_id?: unknown };
84
+ return String(item?.emoji_id || "") === emojiId;
85
+ });
38
86
  }
package/verify/state.ts CHANGED
@@ -12,7 +12,7 @@ export function getPendingMap(): Map<string, PendingVerify> {
12
12
  }
13
13
 
14
14
  export function pendingKey(
15
- selfId: number,
15
+ selfId: string | number,
16
16
  groupId: number,
17
17
  userId: number,
18
18
  ): string {
package/verify/types.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { MiokuContext } from "mioku";
2
2
  import type { AIService } from "mioku";
3
3
  import type { AdminConfig } from "../config";
4
4
  import type { VerifyConfig, VerifyMode } from "./config";
5
5
 
6
6
  export interface VerifyControllerOptions {
7
- ctx: MiokiContext;
7
+ ctx: MiokuContext;
8
8
  aiService?: AIService;
9
9
  getConfig: () => AdminConfig;
10
10
  getVerifyConfig: () => VerifyConfig;
@@ -13,20 +13,21 @@ export interface VerifyControllerOptions {
13
13
  }
14
14
 
15
15
  export interface MemberJoinInfo {
16
- selfId: number;
16
+ selfId: string | number;
17
17
  groupId: number;
18
18
  userId: number;
19
19
  groupName: string;
20
20
  }
21
21
 
22
22
  export interface PendingVerify {
23
- selfId: number;
23
+ selfId: string | number;
24
24
  groupId: number;
25
25
  userId: number;
26
26
  memberName: string;
27
27
  groupName: string;
28
28
  mode: VerifyMode;
29
- promptMessageId?: number;
29
+ bot?: import("mioku").Bot;
30
+ promptMessageId?: string | number;
30
31
  reactionEmojiId?: string;
31
32
  numberAnswer?: number;
32
33
  requiredRegions?: string[];
@@ -39,9 +40,18 @@ export interface PendingVerify {
39
40
  }
40
41
 
41
42
  export interface VerifyController {
42
- handleMemberJoin(info: MemberJoinInfo): Promise<boolean>;
43
- restartVerification(info: MemberJoinInfo): Promise<boolean>;
43
+ handleMemberJoin(
44
+ info: MemberJoinInfo,
45
+ bot?: import("mioku").Bot,
46
+ ): Promise<boolean>;
47
+ restartVerification(
48
+ info: MemberJoinInfo,
49
+ bot?: import("mioku").Bot,
50
+ ): Promise<boolean>;
44
51
  bypassVerification(info: MemberJoinInfo): Promise<void>;
45
- trySendCustomWelcome(info: MemberJoinInfo): Promise<boolean>;
52
+ trySendCustomWelcome(
53
+ info: MemberJoinInfo,
54
+ bot?: import("mioku").Bot,
55
+ ): Promise<boolean>;
46
56
  dispose(): void;
47
57
  }