mioku-plugin-admin 3.0.2 → 3.0.3

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/notify/index.ts CHANGED
@@ -16,8 +16,8 @@ interface NotifyPayload {
16
16
  }
17
17
 
18
18
  interface PendingFriendRequest {
19
- selfId: number;
20
- userId: number;
19
+ selfId: string;
20
+ userId: string;
21
21
  flag: string;
22
22
  createdAt: number;
23
23
  approve: () => Promise<void>;
@@ -25,9 +25,9 @@ interface PendingFriendRequest {
25
25
  }
26
26
 
27
27
  interface PendingGroupInvite {
28
- selfId: number;
29
- groupId: number;
30
- userId: number;
28
+ selfId: string;
29
+ groupId: string;
30
+ userId: string;
31
31
  flag: string;
32
32
  subType: string;
33
33
  createdAt: number;
@@ -50,10 +50,12 @@ function normalizeErrorMessage(error: unknown): string {
50
50
  }
51
51
 
52
52
  /** 获取应该通知的主人列表 */
53
- function getNotifyOwners(config: AdminConfig): number[] {
53
+ function getNotifyOwners(config: AdminConfig): string[] {
54
54
  if (config.notifyTarget.length > 0) return config.notifyTarget;
55
55
  const owners = Array.isArray(botConfig?.owners) ? botConfig.owners : [];
56
- return owners.map((v: any) => Number(v)).filter((n: number) => n > 0);
56
+ return owners
57
+ .map((v: unknown) => String(v ?? "").trim())
58
+ .filter((id: string) => id.length > 0);
57
59
  }
58
60
 
59
61
  /** 注册所有事件通知处理器 */
@@ -65,7 +67,7 @@ export function registerNotificationHandlers(
65
67
  const pendingGroupInvites: PendingGroupInvite[] = [];
66
68
  const recentEventKeys = new Map<string, number>();
67
69
 
68
- function owners(): number[] {
70
+ function owners(): string[] {
69
71
  return getNotifyOwners(getConfig());
70
72
  }
71
73
 
@@ -187,8 +189,8 @@ export function registerNotificationHandlers(
187
189
  if (ctx.isMaster?.(event)) {
188
190
  return true;
189
191
  }
190
- const userId = Number(event.user_id || 0);
191
- if (userId <= 0) {
192
+ const userId = String(event.user_id ?? "").trim();
193
+ if (!userId) {
192
194
  return false;
193
195
  }
194
196
  return owners().includes(userId);
@@ -206,8 +208,8 @@ export function registerNotificationHandlers(
206
208
  }
207
209
 
208
210
  function pushPendingFriendRequest(event: RequestEvent) {
209
- const selfId = Number(event.self_id || 0);
210
- const userId = Number(event.user_id || 0);
211
+ const selfId = String(event.self_id ?? "").trim();
212
+ const userId = String(event.user_id ?? "").trim();
211
213
  const flag = String(event.flag || "").trim();
212
214
  if (!selfId || !userId || !flag) return;
213
215
  pendingFriendRequests.push({
@@ -222,9 +224,9 @@ export function registerNotificationHandlers(
222
224
  }
223
225
 
224
226
  function pushPendingGroupInvite(event: RequestEvent) {
225
- const selfId = Number(event.self_id || 0);
226
- const groupId = Number(event.group_id || 0);
227
- const userId = Number(event.user_id || 0);
227
+ const selfId = String(event.self_id ?? "").trim();
228
+ const groupId = String(event.group_id ?? "").trim();
229
+ const userId = String(event.user_id ?? "").trim();
228
230
  const flag = String(event.flag || "").trim();
229
231
  const subType = String(event.sub_type || "invite").trim() || "invite";
230
232
  if (!selfId || !groupId || !userId || !flag) return;
@@ -242,8 +244,8 @@ export function registerNotificationHandlers(
242
244
  }
243
245
 
244
246
  function shiftLatestFriendRequest(
245
- selfId: number,
246
- userId: number,
247
+ selfId: string,
248
+ userId: string,
247
249
  ): PendingFriendRequest | undefined {
248
250
  prunePendingRequests();
249
251
  for (let i = pendingFriendRequests.length - 1; i >= 0; i--) {
@@ -257,9 +259,9 @@ export function registerNotificationHandlers(
257
259
  }
258
260
 
259
261
  function shiftLatestGroupInvite(
260
- selfId: number,
261
- groupId: number,
262
- userId?: number,
262
+ selfId: string,
263
+ groupId: string,
264
+ userId?: string,
263
265
  ): PendingGroupInvite | undefined {
264
266
  prunePendingRequests();
265
267
  for (let i = pendingGroupInvites.length - 1; i >= 0; i--) {
@@ -296,47 +298,53 @@ export function registerNotificationHandlers(
296
298
  function parseQuotedApprovalTarget(
297
299
  quotedText: string,
298
300
  ):
299
- | { type: "friend_message"; userId: number }
300
- | { type: "friend_request"; userId: number }
301
- | { type: "group_invite"; groupId: number; userId?: number }
302
- | { type: "group_ban"; groupId: number }
301
+ | { type: "friend_message"; userId: string }
302
+ | { type: "friend_request"; userId: string }
303
+ | { type: "group_invite"; groupId: string; userId?: string }
304
+ | { type: "group_ban"; groupId: string }
303
305
  | null {
304
306
  const text = String(quotedText || "");
305
307
  if (!text) return null;
306
308
 
309
+ // 文本里的 id 可能是 QQ 号,也可能是 openid
310
+ const ID_PATTERN = "([A-Za-z0-9_-]{4,64})";
311
+
307
312
  if (text.includes("[好友消息]")) {
308
313
  const userIdMatch =
309
- text.match(/好友QQ[::]\s*(\d+)/) ||
310
- text.match(/QQ[::]\s*(\d+)/);
311
- const userId = Number(userIdMatch?.[1] || 0);
312
- return userId > 0 ? { type: "friend_message", userId } : null;
314
+ text.match(new RegExp(`好友(?:QQ|ID)[::]\\s*${ID_PATTERN}`)) ||
315
+ text.match(new RegExp(`(?:QQ|ID)[::]\\s*${ID_PATTERN}`));
316
+ const userId = String(userIdMatch?.[1] ?? "").trim();
317
+ return userId ? { type: "friend_message", userId } : null;
313
318
  }
314
319
 
315
320
  if (text.includes("[好友申请]")) {
316
321
  const userIdMatch =
317
- text.match(/好友QQ[::]\s*(\d+)/) ||
318
- text.match(/QQ[::]\s*(\d+)/) ||
319
- text.match(/\[好友申请\]\s*(\d+)/);
320
- const userId = Number(userIdMatch?.[1] || 0);
321
- return userId > 0 ? { type: "friend_request", userId } : null;
322
+ text.match(new RegExp(`好友(?:QQ|ID)[::]\\s*${ID_PATTERN}`)) ||
323
+ text.match(new RegExp(`(?:QQ|ID)[::]\\s*${ID_PATTERN}`)) ||
324
+ text.match(new RegExp(`\\[好友申请\\]\\s*${ID_PATTERN}`));
325
+ const userId = String(userIdMatch?.[1] ?? "").trim();
326
+ return userId ? { type: "friend_request", userId } : null;
322
327
  }
323
328
 
324
329
  if (text.includes("[群邀请]")) {
325
330
  const groupIdMatch =
326
- text.match(/群号[::]\s*(\d+)/) || text.match(/\[群邀请\][^\d]*(\d+)/);
327
- const inviterIdMatch = text.match(/邀请人QQ[::]\s*(\d+)/);
328
- const groupId = Number(groupIdMatch?.[1] || 0);
329
- const userId = Number(inviterIdMatch?.[1] || 0);
330
- if (groupId <= 0) return null;
331
- return userId > 0
331
+ text.match(new RegExp(`群号[::]\\s*${ID_PATTERN}`)) ||
332
+ text.match(new RegExp(`\\[群邀请\\][^A-Za-z0-9_-]*${ID_PATTERN}`));
333
+ const inviterIdMatch = text.match(
334
+ new RegExp(`邀请人(?:QQ|ID)[::]\\s*${ID_PATTERN}`),
335
+ );
336
+ const groupId = String(groupIdMatch?.[1] ?? "").trim();
337
+ const userId = String(inviterIdMatch?.[1] ?? "").trim();
338
+ if (!groupId) return null;
339
+ return userId
332
340
  ? { type: "group_invite", groupId, userId }
333
341
  : { type: "group_invite", groupId };
334
342
  }
335
343
 
336
344
  if (text.includes("[Bot被禁言]")) {
337
- const groupIdMatch = text.match(/群号[::]\s*(\d+)/);
338
- const groupId = Number(groupIdMatch?.[1] || 0);
339
- return groupId > 0 ? { type: "group_ban", groupId } : null;
345
+ const groupIdMatch = text.match(new RegExp(`群号[::]\\s*${ID_PATTERN}`));
346
+ const groupId = String(groupIdMatch?.[1] ?? "").trim();
347
+ return groupId ? { type: "group_ban", groupId } : null;
340
348
  }
341
349
 
342
350
  return null;
@@ -363,9 +371,9 @@ export function registerNotificationHandlers(
363
371
  ): Promise<void> {
364
372
  if (!getConfig().notifyGroupInvite) return;
365
373
 
366
- const selfId = Number(event.self_id || 0);
367
- const groupId = Number(event.group_id || 0);
368
- const userId = Number(event.user_id || 0);
374
+ const selfId = String(event.self_id ?? "").trim();
375
+ const groupId = String(event.group_id ?? "").trim();
376
+ const userId = String(event.user_id ?? "").trim();
369
377
  const flag = String(event.flag || "").trim();
370
378
  if (!selfId || !groupId || !userId || !flag) return;
371
379
 
@@ -380,7 +388,7 @@ export function registerNotificationHandlers(
380
388
  lines: [
381
389
  "[群邀请]",
382
390
  `群号:${groupId}`,
383
- `邀请人QQ:${userId}`,
391
+ `邀请人QQ:${userId}`, // 可能是 openid,保留原值便于引用回复
384
392
  `验证消息:${comment}`,
385
393
  "备注:引用该消息回复「同意」或「拒绝」",
386
394
  ],
@@ -388,15 +396,15 @@ export function registerNotificationHandlers(
388
396
  }
389
397
 
390
398
  async function notifyGroupBan(event: NoticeEvent): Promise<void> {
391
- const selfId = Number(event.self_id || 0);
392
- const groupId = Number(event.group_id || 0);
393
- const userId = Number(event.user_id || 0);
399
+ const selfId = String(event.self_id ?? "").trim();
400
+ const groupId = String(event.group_id ?? "").trim();
401
+ const userId = String(event.user_id ?? "").trim();
394
402
  const rawBan = event.raw as { action_type?: string; duration?: number } | undefined;
395
403
  const duration = Number(rawBan?.duration || 0);
396
404
  if (!selfId || !groupId) return;
397
405
  if (userId !== selfId) return;
398
406
 
399
- const operatorId = Number(event.operator_id || 0);
407
+ const operatorId = String(event.operator_id ?? "").trim();
400
408
  const isUnban = rawBan?.action_type === "lift_ban";
401
409
  if (isUnban) {
402
410
  if (!getConfig().notifyGroupUnban) return;
@@ -424,22 +432,21 @@ export function registerNotificationHandlers(
424
432
  ): Promise<void> {
425
433
  if (!getConfig().notifyGroupKick) return;
426
434
 
427
- const selfId = Number(event.self_id || 0);
428
- const groupId = Number(event.group_id || 0);
429
- const userId = Number(event.user_id || 0);
435
+ const selfId = String(event.self_id ?? "").trim();
436
+ const groupId = String(event.group_id ?? "").trim();
437
+ const userId = String(event.user_id ?? "").trim();
430
438
  const rawKick = event.raw as { action_type?: string } | undefined;
431
439
  const leaveType = String(rawKick?.action_type || "").trim();
432
440
  if (!selfId || !groupId) return;
433
441
  if (userId !== selfId) return;
434
442
  if (leaveType !== "kick" && leaveType !== "kick_me") return;
435
443
 
436
- const operatorId = Number(event.operator_id || 0);
444
+ const operatorId = String(event.operator_id ?? "").trim();
437
445
  const eventKey = `group-kick:${selfId}:${groupId}:${operatorId}:${leaveType}:${Number(event.time || 0)}`;
438
446
  if (!markEventOnce(eventKey)) return;
439
447
 
440
448
  await sendNotify(event.bot, {
441
- avatarUrl:
442
- operatorId > 0 ? getAvatarUrl(operatorId) : getGroupAvatarUrl(groupId),
449
+ avatarUrl: operatorId ? getAvatarUrl(operatorId) : getGroupAvatarUrl(groupId),
443
450
  lines: ["[Bot被踢]", `群号:${groupId}`, `操作者QQ:${operatorId}`],
444
451
  });
445
452
  }
@@ -520,7 +527,7 @@ export function registerNotificationHandlers(
520
527
  }
521
528
  const text = (ctx.text(event) || "").trim();
522
529
 
523
- const selfId = Number(event.self_id || 0);
530
+ const selfId = String(event.self_id ?? "").trim();
524
531
  const bot = event.bot;
525
532
  if (!bot) {
526
533
  await event.reply("Bot不可用", true);
package/notify/welcome.ts CHANGED
@@ -5,8 +5,8 @@ import type { AdminConfig } from "../config";
5
5
  export async function resolveMemberName(
6
6
  ctx: MiokuContext,
7
7
  bot: import("mioku").Bot | undefined,
8
- groupId: number,
9
- userId: number,
8
+ groupId: string,
9
+ userId: string,
10
10
  ): Promise<string> {
11
11
  try {
12
12
  if (!bot) return String(userId);
@@ -22,7 +22,7 @@ export async function resolveMemberName(
22
22
  }
23
23
 
24
24
  interface PendingMember {
25
- userId: number;
25
+ userId: string;
26
26
  memberName: string;
27
27
  }
28
28
 
@@ -42,7 +42,7 @@ function getBatchMap(): Map<string, BatchState> {
42
42
  return state[RUNTIME_KEY] as Map<string, BatchState>;
43
43
  }
44
44
 
45
- function batchKey(selfId: string | number, groupId: number): string {
45
+ function batchKey(selfId: string, groupId: string): string {
46
46
  return `${selfId}:${groupId}`;
47
47
  }
48
48
 
@@ -68,16 +68,16 @@ async function flushBatch(options: {
68
68
  ctx: MiokuContext;
69
69
  aiService?: AIService;
70
70
  config: AdminConfig;
71
- selfId: string | number;
72
- groupId: number;
71
+ selfId: string;
72
+ groupId: string;
73
73
  groupName: string;
74
74
  members: PendingMember[];
75
75
  promptInjections?: { content: string; title?: string }[];
76
76
  bot?: import("mioku").Bot;
77
77
  tryCustomPrompt?: (info: {
78
- selfId: string | number;
79
- groupId: number;
80
- userId: number;
78
+ selfId: string;
79
+ groupId: string;
80
+ userId: string;
81
81
  groupName: string;
82
82
  }, bot?: import("mioku").Bot) => Promise<boolean>;
83
83
  }): Promise<string> {
@@ -135,13 +135,13 @@ async function flushBatch(options: {
135
135
 
136
136
  try {
137
137
  await chatRuntime.generateNotice({
138
- selfId: Number(selfId),
138
+ selfId,
139
139
  groupId,
140
140
  send: true,
141
141
  instruction: [
142
142
  `当前有 ${members.length} 位新成员同时入群,请一次性发送一段统一的欢迎语(不要逐个 @ 欢迎、不要重复点名)不要长篇大论,精简即可。`,
143
143
  `新成员昵称:${userList}`,
144
- `新成员 QQ:${userIdList}`,
144
+ `新成员 ID:${userIdList}`,
145
145
  `所在群:${groupName}`,
146
146
  `${config.welcome.aiPrompt || ""}`,
147
147
  ].join("\n"),
@@ -159,17 +159,17 @@ async function sendSingleWelcome(options: {
159
159
  ctx: MiokuContext;
160
160
  aiService?: AIService;
161
161
  config: AdminConfig;
162
- selfId: string | number;
163
- groupId: number;
162
+ selfId: string;
163
+ groupId: string;
164
164
  groupName: string;
165
- userId: number;
165
+ userId: string;
166
166
  memberName: string;
167
167
  promptInjections?: { content: string; title?: string }[];
168
168
  bot?: import("mioku").Bot;
169
169
  tryCustomPrompt?: (info: {
170
- selfId: string | number;
171
- groupId: number;
172
- userId: number;
170
+ selfId: string;
171
+ groupId: string;
172
+ userId: string;
173
173
  groupName: string;
174
174
  }, bot?: import("mioku").Bot) => Promise<boolean>;
175
175
  }): Promise<void> {
@@ -211,16 +211,16 @@ export async function triggerSingleWelcome(options: {
211
211
  ctx: MiokuContext;
212
212
  aiService?: AIService;
213
213
  getConfig: () => AdminConfig;
214
- selfId: string | number;
215
- groupId: number;
214
+ selfId: string;
215
+ groupId: string;
216
216
  groupName: string;
217
- userId: number;
217
+ userId: string;
218
218
  memberName?: string;
219
219
  promptInjections?: { content: string; title?: string }[];
220
220
  tryCustomPrompt?: (info: {
221
- selfId: string | number;
222
- groupId: number;
223
- userId: number;
221
+ selfId: string;
222
+ groupId: string;
223
+ userId: string;
224
224
  groupName: string;
225
225
  }, bot?: import("mioku").Bot) => Promise<boolean>;
226
226
  }, bot?: import("mioku").Bot): Promise<void> {
@@ -252,15 +252,15 @@ export function registerWelcomeHandler(
252
252
  aiService: AIService | undefined,
253
253
  getConfig: () => AdminConfig,
254
254
  shouldSuppress?: (info: {
255
- selfId: string | number;
256
- groupId: number;
257
- userId: number;
255
+ selfId: string;
256
+ groupId: string;
257
+ userId: string;
258
258
  groupName: string;
259
259
  }, bot?: import("mioku").Bot) => Promise<boolean> | boolean,
260
260
  tryCustomPrompt?: (info: {
261
- selfId: string | number;
262
- groupId: number;
263
- userId: number;
261
+ selfId: string;
262
+ groupId: string;
263
+ userId: string;
264
264
  groupName: string;
265
265
  }, bot?: import("mioku").Bot) => Promise<boolean>,
266
266
  ): () => void {
@@ -272,13 +272,13 @@ export function registerWelcomeHandler(
272
272
  const cfg = getConfig();
273
273
  const bot = event.bot;
274
274
  const selfId = event?.self_id || ctx.self_id || "";
275
- const groupId = Number(event?.group_id || 0);
276
- const userId = Number(event?.user_id || 0);
275
+ const groupId = String(event?.group_id ?? "").trim();
276
+ const userId = String(event?.user_id ?? "").trim();
277
277
  if (!groupId || !userId) return;
278
278
  if (selfId != null && String(userId) === String(selfId)) return;
279
279
 
280
280
  const groupName =
281
- String((event.raw as { group_name?: string } | undefined)?.group_name || "").trim() || String(groupId);
281
+ String((event.raw as { group_name?: string } | undefined)?.group_name || "").trim() || groupId;
282
282
 
283
283
  if (
284
284
  shouldSuppress &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-admin",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "管理插件,提供事件通知与群管/个人管理指令",
5
5
  "main": "index.ts",
6
6
  "type": "module",
package/skills/group.ts CHANGED
@@ -7,8 +7,8 @@ interface GroupRuntime {
7
7
  ctx: { logger?: { error?: (...args: unknown[]) => void } };
8
8
  event: MessageEvent;
9
9
  bot: Bot;
10
- groupId: number;
11
- selfId: number;
10
+ groupId: string;
11
+ selfId: string;
12
12
  }
13
13
 
14
14
  interface SkillRuntimeContext {
@@ -24,10 +24,10 @@ function resolveGroupRuntime(runtimeCtx: SkillRuntimeContext | undefined):
24
24
  if (!ctx) return { error: "无法获取上下文" };
25
25
 
26
26
  const event = runtimeCtx?.event ?? runtimeCtx?.rawEvent;
27
- const groupId = Number(event?.group_id ?? 0);
27
+ const groupId = String(event?.group_id ?? "").trim();
28
28
  if (!groupId) return { error: "这个工具只能在群聊中使用" };
29
29
 
30
- const selfId = Number(event?.self_id ?? 0);
30
+ const selfId = String(event?.self_id ?? "").trim();
31
31
  if (!selfId) return { error: "无法获取Bot ID" };
32
32
 
33
33
  const bot = event?.bot;
@@ -161,7 +161,7 @@ const groupAdminSkill: AISkill = {
161
161
  return { success: true, message: `已将 ${userId} 的头衔设为 "${title}"` };
162
162
  }
163
163
  case "set_self_title": {
164
- const userId = Number(event.user_id);
164
+ const userId = String(event.user_id ?? "").trim();
165
165
  if (!userId) return { error: "无法获取当前用户ID" };
166
166
  const title = String((args as { title?: unknown })?.title ?? "").trim();
167
167
  if (!title) return { error: "set_self_title 需要提供 title" };
@@ -244,8 +244,10 @@ const groupAdminSkill: AISkill = {
244
244
  return { success: true, message: `Bot在当前群的群名片已修改为 ${card}` };
245
245
  }
246
246
  case "set_group_avatar": {
247
- const messageId = Number((args as { message_id?: unknown })?.message_id);
248
- if (!Number.isFinite(messageId) || messageId <= 0) {
247
+ const messageId = String(
248
+ (args as { message_id?: unknown })?.message_id ?? "",
249
+ ).trim();
250
+ if (!messageId) {
249
251
  return { error: "set_group_avatar 需要提供有效的 message_id" };
250
252
  }
251
253
  const imageUrl = await getImageUrlByMessageId(bot, messageId);
@@ -1,18 +1,28 @@
1
1
  import type { Bot } from "mioku";
2
2
 
3
+ /** 通过框架能力取消息里的第一张图片(适配器各自实现,不依赖 OneBot 原生 action) */
3
4
  export async function getImageUrlByMessageId(
4
5
  bot: Bot,
5
- messageId: number,
6
+ messageId: string,
6
7
  ): Promise<string | null> {
7
8
  try {
8
- const msg = await bot.sendApi<{
9
- message?: Array<{ type?: string; url?: string; file?: string }>;
10
- }>("get_msg", { message_id: messageId });
9
+ const msg = await bot.getMessage(messageId);
11
10
  const segments = Array.isArray(msg?.message) ? msg.message : [];
12
- const imageSeg = segments.find((seg) => seg?.type === "image");
13
- if (!imageSeg) return null;
14
-
15
- return imageSeg.url || imageSeg.file || null;
11
+ for (const segment of segments) {
12
+ if (segment?.type !== "image") continue;
13
+ const data = (segment.data ?? {}) as Record<string, unknown>;
14
+ const attachment = segment.attachment as
15
+ | { url?: string; file?: string }
16
+ | undefined;
17
+ const url =
18
+ (typeof data.url === "string" && data.url) ||
19
+ (typeof data.file === "string" && data.file) ||
20
+ attachment?.url ||
21
+ attachment?.file ||
22
+ "";
23
+ if (url) return url;
24
+ }
25
+ return null;
16
26
  } catch {
17
27
  return null;
18
28
  }
@@ -101,8 +101,8 @@ const personalSkill: AISkill = {
101
101
  try {
102
102
  switch (action) {
103
103
  case "set_avatar": {
104
- const messageId = Number(a?.message_id);
105
- if (!Number.isFinite(messageId) || messageId <= 0) {
104
+ const messageId = String(a?.message_id ?? "").trim();
105
+ if (!messageId) {
106
106
  return { error: "set_avatar 需要提供有效的 message_id" };
107
107
  }
108
108
  const imageUrl = await getImageUrlByMessageId(bot, messageId);
@@ -20,7 +20,7 @@ export interface SavedPromptImage {
20
20
  size: number;
21
21
  }
22
22
 
23
- export function getGroupPromptImageDir(groupId: number): string {
23
+ export function getGroupPromptImageDir(groupId: string): string {
24
24
  return path.join(
25
25
  getPluginDataDir("admin"),
26
26
  String(groupId),
@@ -28,13 +28,13 @@ export function getGroupPromptImageDir(groupId: number): string {
28
28
  );
29
29
  }
30
30
 
31
- export function ensureGroupPromptImageDir(groupId: number): string {
31
+ export function ensureGroupPromptImageDir(groupId: string): string {
32
32
  const dir = getGroupPromptImageDir(groupId);
33
33
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
34
34
  return dir;
35
35
  }
36
36
 
37
- export function getGroupPromptImagePath(groupId: number, filename: string): string {
37
+ export function getGroupPromptImagePath(groupId: string, filename: string): string {
38
38
  return path.join(getGroupPromptImageDir(groupId), filename);
39
39
  }
40
40
 
@@ -57,7 +57,7 @@ function generateFilename(ext: string): string {
57
57
  }
58
58
 
59
59
  export async function saveRemoteImageAsPrompt(
60
- groupId: number,
60
+ groupId: string,
61
61
  sourceUrl: string,
62
62
  ): Promise<SavedPromptImage | null> {
63
63
  const trimmed = String(sourceUrl || "").trim();
@@ -95,7 +95,7 @@ export async function saveRemoteImageAsPrompt(
95
95
  }
96
96
 
97
97
  async function copyLocalFileAsPrompt(
98
- groupId: number,
98
+ groupId: string,
99
99
  filePath: string,
100
100
  ): Promise<SavedPromptImage | null> {
101
101
  try {
@@ -171,7 +171,7 @@ function downloadToFile(url: string, savePath: string): Promise<number> {
171
171
  }
172
172
 
173
173
  export async function deletePromptImage(
174
- groupId: number,
174
+ groupId: string,
175
175
  filename: string,
176
176
  ): Promise<void> {
177
177
  if (!filename) return;
@@ -182,7 +182,7 @@ export async function deletePromptImage(
182
182
  }
183
183
 
184
184
  export async function pruneGroupPromptImages(
185
- groupId: number,
185
+ groupId: string,
186
186
  keep: readonly string[],
187
187
  ): Promise<void> {
188
188
  const dir = getGroupPromptImageDir(groupId);
package/verify/config.ts CHANGED
@@ -3,7 +3,7 @@ export type VerifyMode = "reaction" | "number" | "chiral";
3
3
  export const MAX_CUSTOM_PROMPT_LENGTH = 50;
4
4
 
5
5
  export interface VerifyGroupConfig {
6
- groupId: number;
6
+ groupId: string;
7
7
  enabled: boolean;
8
8
  mode: VerifyMode;
9
9
  customPrompt: string;
@@ -71,14 +71,14 @@ export function normalizeVerifyMode(value: unknown): VerifyMode {
71
71
  }
72
72
 
73
73
  function normalizeVerifyGroup(raw: any): VerifyGroupConfig {
74
- const groupId = Number(raw?.groupId || raw?.group_id || 0);
74
+ const groupId = String(raw?.groupId ?? raw?.group_id ?? "").trim();
75
75
  const promptImageRaw =
76
76
  raw?.promptImage ??
77
77
  raw?.prompt_image ??
78
78
  (Array.isArray(raw?.promptImages) ? raw.promptImages[0] : undefined) ??
79
79
  (Array.isArray(raw?.images) ? raw.images[0] : undefined);
80
80
  return {
81
- groupId: groupId > 0 ? groupId : 0,
81
+ groupId,
82
82
  enabled: raw?.enabled === true,
83
83
  mode: normalizeVerifyMode(raw?.mode),
84
84
  customPrompt: normalizeCustomPrompt(
@@ -92,7 +92,7 @@ export function normalizeVerifyConfig(raw: any): VerifyConfig {
92
92
  const groups: VerifyGroupConfig[] = Array.isArray(raw?.groups)
93
93
  ? raw.groups
94
94
  .map((g: any) => normalizeVerifyGroup(g))
95
- .filter((g: VerifyGroupConfig) => g.groupId > 0)
95
+ .filter((g: VerifyGroupConfig) => g.groupId.length > 0)
96
96
  : [];
97
97
 
98
98
  const numOr = (value: unknown, fallback: number): number => {
@@ -142,7 +142,7 @@ export function normalizeVerifyConfig(raw: any): VerifyConfig {
142
142
 
143
143
  export function getGroupVerifyConfig(
144
144
  config: VerifyConfig,
145
- groupId: number,
145
+ groupId: string,
146
146
  ): VerifyGroupConfig {
147
147
  const found = config.groups.find((g) => g.groupId === groupId);
148
148
  if (found) return found;
@@ -157,7 +157,7 @@ export function getGroupVerifyConfig(
157
157
 
158
158
  export function upsertGroupVerifyConfig(
159
159
  config: VerifyConfig,
160
- groupId: number,
160
+ groupId: string,
161
161
  patch: Partial<Omit<VerifyGroupConfig, "groupId">>,
162
162
  ): VerifyConfig {
163
163
  const idx = config.groups.findIndex((g) => g.groupId === groupId);