mioku-plugin-chat 3.0.5 → 3.1.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/handlers/poke.ts CHANGED
@@ -15,12 +15,13 @@ export function createPokeHandler(
15
15
  const { getConfig, runtimeState, pokeCooldowns } = state;
16
16
 
17
17
  return async (e: NoticeEvent) => {
18
- const selfId = Number(e.self_id || 0);
19
- const targetId = Number(
20
- (e.raw as { target_id?: string } | undefined)?.target_id ?? 0,
21
- );
22
- if (targetId !== selfId) return;
23
- const groupId = Number(e.group_id || 0);
18
+ const selfId = String(e.self_id ?? "").trim();
19
+ const raw = e.raw as { target_id?: unknown; user_id?: unknown } | undefined;
20
+ const targetId = String(
21
+ raw?.target_id ?? raw?.user_id ?? e.user_id ?? "",
22
+ ).trim();
23
+ if (!targetId || targetId !== selfId) return;
24
+ const groupId = String(e.group_id ?? "").trim();
24
25
  const cfg = groupId ? await getConfig(groupId) : await getConfig();
25
26
  if (!cfg.model && !cfg.apiKey) return;
26
27
  if (!groupId || !isGroupAllowed(groupId, cfg)) return;
@@ -42,10 +43,9 @@ export function createPokeHandler(
42
43
  "group",
43
44
  groupId,
44
45
  );
45
- const userId = Number(e.user_id || 0);
46
+ const userId = String(e.user_id ?? "").trim();
46
47
  const botRole = await getBotRole(groupId, ctx, selfId);
47
- const botNickname =
48
- cfg.nicknames[0] || e.bot?.nickname || "Bot";
48
+ const botNickname = cfg.nicknames[0] || e.bot?.nickname || "Bot";
49
49
 
50
50
  let senderName = String(userId);
51
51
  try {
@@ -54,9 +54,7 @@ export function createPokeHandler(
54
54
  ? await bot.getMemberInfo(groupId, userId)
55
55
  : undefined;
56
56
  senderName =
57
- memberInfo?.card ||
58
- memberInfo?.nickname ||
59
- String(userId);
57
+ memberInfo?.card || memberInfo?.nickname || String(userId);
60
58
  } catch {}
61
59
 
62
60
  const targetMessage: TargetMessage = {
@@ -76,8 +74,11 @@ export function createPokeHandler(
76
74
  selfId,
77
75
  pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
78
76
  );
79
- const { groupName, memberCount } =
80
- await pluginCtx.getGroupInfoData(ctx, groupId, selfId);
77
+ const { groupName, memberCount } = await pluginCtx.getGroupInfoData(
78
+ ctx,
79
+ groupId,
80
+ selfId,
81
+ );
81
82
 
82
83
  const toolCtx = pluginCtx.buildToolContext({
83
84
  ctx,
@@ -38,7 +38,7 @@ export class EmotionAgent {
38
38
  return state;
39
39
  }
40
40
 
41
- getAvailableEmotions(groupId?: number): string[] {
41
+ getAvailableEmotions(groupId?: string): string[] {
42
42
  const cfg = this.getConfig(groupId);
43
43
  const emotions = Object.keys(cfg.emotion?.emotions || {})
44
44
  .map((name) => name.trim().toLowerCase())
@@ -46,7 +46,7 @@ export class EmotionAgent {
46
46
  return Array.from(new Set(["default", ...emotions]));
47
47
  }
48
48
 
49
- getReferenceExamples(emotion: string, groupId?: number): string[] {
49
+ getReferenceExamples(emotion: string, groupId?: string): string[] {
50
50
  const cfg = this.getConfig(groupId);
51
51
  const normalized = this.normalizeEmotionName(emotion);
52
52
  const emotions = cfg.emotion?.emotions || {};
@@ -55,7 +55,7 @@ export class EmotionAgent {
55
55
  return this.normalizeExamples(emotions[this.getDefaultEmotion(groupId)]?.examples);
56
56
  }
57
57
 
58
- setEmotion(sessionId: string, emotion: string, groupId?: number): EmotionState {
58
+ setEmotion(sessionId: string, emotion: string, groupId?: string): EmotionState {
59
59
  const current = this.resolveEmotion(emotion, groupId);
60
60
  const state = { current, updatedAt: Date.now() };
61
61
  this.states.set(sessionId, state);
@@ -144,14 +144,14 @@ ${input.targetMessage.userName}: ${input.targetMessage.content}`;
144
144
  return this.resolveEmotion(parsed?.emotion, extractGroupIdFromSession(input.sessionId));
145
145
  }
146
146
 
147
- private resolveEmotion(emotion: unknown, groupId?: number): string {
147
+ private resolveEmotion(emotion: unknown, groupId?: string): string {
148
148
  const normalized = this.normalizeEmotionName(emotion);
149
149
  const available = this.getAvailableEmotions(groupId);
150
150
  if (available.includes(normalized)) return normalized;
151
151
  return this.getDefaultEmotion(groupId);
152
152
  }
153
153
 
154
- private getDefaultEmotion(groupId?: number): string {
154
+ private getDefaultEmotion(groupId?: string): string {
155
155
  const cfg = this.getConfig(groupId);
156
156
  const configured = this.normalizeEmotionName(cfg.emotion?.defaultEmotion);
157
157
  const available = this.getAvailableEmotions(groupId);
@@ -9,8 +9,8 @@ export class ExpressionLearner {
9
9
  private ai: AIInstance;
10
10
  private getConfig: ChatConfigProvider;
11
11
  private db: ChatDatabase;
12
- private pendingMessagesByUser: Map<number, ChatMessage[]> = new Map();
13
- private learningUsers: Set<number> = new Set();
12
+ private pendingMessagesByUser: Map<string, ChatMessage[]> = new Map();
13
+ private learningUsers: Set<string> = new Set();
14
14
 
15
15
  constructor(ai: AIInstance, configProvider: ChatConfigProvider, db: ChatDatabase) {
16
16
  this.ai = ai;
@@ -33,9 +33,9 @@ export class ExpressionLearner {
33
33
  }
34
34
 
35
35
  getExpressionContextForUser(
36
- userId: number,
36
+ userId: string,
37
37
  userName: string,
38
- groupId?: number,
38
+ groupId?: string,
39
39
  ): string {
40
40
  const cfg = this.getConfig(groupId);
41
41
  if (!cfg.expression?.enabled) return "";
@@ -62,7 +62,7 @@ export class ExpressionLearner {
62
62
  return context;
63
63
  }
64
64
 
65
- private async tryLearn(userId: number, cfg = this.getConfig()): Promise<void> {
65
+ private async tryLearn(userId: string, cfg = this.getConfig()): Promise<void> {
66
66
  if (this.learningUsers.has(userId)) return;
67
67
 
68
68
  const threshold = Math.max(
@@ -95,7 +95,7 @@ export class ExpressionLearner {
95
95
  }
96
96
 
97
97
  private async learnForUser(
98
- userId: number,
98
+ userId: string,
99
99
  messages: ChatMessage[],
100
100
  cfg = this.getConfig(),
101
101
  ): Promise<void> {
package/humanize/index.ts CHANGED
@@ -16,7 +16,7 @@ import { TopicTracker } from "./topic";
16
16
  import { ActionPlanner } from "./planner";
17
17
  import { ExpressionLearner } from "./expression";
18
18
 
19
- export type ChatConfigProvider = (groupId?: number) => ChatConfig;
19
+ export type ChatConfigProvider = (groupId?: string) => ChatConfig;
20
20
 
21
21
  export class HumanizeEngine {
22
22
  readonly memoryRetrieval: MemoryRetrieval;
@@ -6,7 +6,7 @@ import { extractGroupIdFromSession } from "../utils/group-config";
6
6
  import type { ChatConfigProvider } from "./index";
7
7
 
8
8
  export interface MemoryUserHistoryChunk {
9
- userId: number;
9
+ userId: string;
10
10
  messages: ChatMessage[];
11
11
  }
12
12
 
package/index.ts CHANGED
@@ -261,7 +261,7 @@ export default definePlugin({
261
261
  });
262
262
  }
263
263
 
264
- const configProvider: ChatConfigProvider = (groupId?: number) => {
264
+ const configProvider: ChatConfigProvider = (groupId?: string) => {
265
265
  const base = cachedBaseConfig;
266
266
  if (!base) return PERSONALIZATION_CONFIG as unknown as ChatConfig;
267
267
  if (groupId === undefined) return base;
@@ -269,7 +269,7 @@ export default definePlugin({
269
269
  return mergeGroupOverrides(base, overrides);
270
270
  };
271
271
 
272
- const getConfig = async (groupId?: number): Promise<ChatConfig> => {
272
+ const getConfig = async (groupId?: string): Promise<ChatConfig> => {
273
273
  if (!cachedBaseConfig) await refreshBaseCache();
274
274
  return configProvider(groupId);
275
275
  };
@@ -380,14 +380,14 @@ roleModels = resolved.models;
380
380
  rateLimiter.setConfigProvider(configProvider);
381
381
  const skillManager = new SkillSessionManager();
382
382
 
383
- rateLimiter.setQueueLengthGetter((groupId: number) =>
383
+ rateLimiter.setQueueLengthGetter((groupId: string) =>
384
384
  queueManager.getQueueLength(`group:${groupId}`),
385
385
  );
386
386
 
387
387
  const humanize = new HumanizeEngine(workAIInstance, db, configProvider);
388
388
  await humanize.init();
389
389
 
390
- const pokeCooldowns = new Map<number, number>();
390
+ const pokeCooldowns = new Map<string, number>();
391
391
  const processingSet = new Set<string>();
392
392
  const sessionTurnScheduler = new SessionTurnScheduler();
393
393
  const groupStructuredHistory = new GroupStructuredHistoryManager();
@@ -442,8 +442,8 @@ roleModels = resolved.models;
442
442
  audioService,
443
443
  startCooldownTimer: (
444
444
  groupSessionId: string,
445
- groupId: number,
446
- selfId: number,
445
+ groupId: string,
446
+ selfId: string,
447
447
  ) => cooldownManager.startCooldownTimer(groupSessionId, groupId, selfId),
448
448
  async recordGroupMessageForLearning(
449
449
  userMsg: ChatMessage,
@@ -511,20 +511,25 @@ roleModels = resolved.models;
511
511
  aiService.registerChatRuntime(runtime);
512
512
 
513
513
  ctx.command({
514
- name: "/重置会话",
515
- match: /^\/重置会话$/,
516
- prefixes: false,
514
+ name: "重置会话",
515
+ prefixes: ["/", "."],
516
+ match: /^重置会话$/,
517
517
  description: "重置自己的AI聊天记录",
518
+ usage: ".重置会话",
518
519
  handler: async ({ event }) => {
519
- if (event.user_id === event.self_id) return;
520
+ if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
520
521
  const groupId =
521
- event.message_type === "group" ? Number(event.group_id) : undefined;
522
+ event.message_type === "group"
523
+ ? String(event.group_id ?? "").trim() || undefined
524
+ : undefined;
522
525
  if (groupId) {
523
526
  pluginCtx.sessionManager.resetBotMessages(`group:${groupId}`);
524
527
  pluginCtx.groupStructuredHistory.clear(`group:${groupId}`);
525
528
  await event.reply("已清除本群会话中 AI 发送的消息~");
526
529
  } else {
527
- const userId = Number(event.user_id || event.sender?.user_id || 0);
530
+ const userId = String(
531
+ event.user_id ?? event.sender?.user_id ?? "",
532
+ ).trim();
528
533
  pluginCtx.sessionManager.resetBotMessages(`personal:${userId}`);
529
534
  pluginCtx.groupStructuredHistory.clear(`personal:${userId}`);
530
535
  await event.reply("已清除你的个人会话中 AI 发送的消息~");
@@ -532,15 +537,18 @@ roleModels = resolved.models;
532
537
  },
533
538
  });
534
539
  ctx.command({
535
- name: "/重置群会话",
536
- match: /^\/重置群会话$/,
537
- prefixes: false,
540
+ name: "重置群会话",
541
+ prefixes: ["/", "."],
542
+ match: /^重置群会话$/,
538
543
  permission: "admin",
539
544
  description: "重置当前群的AI聊天记录",
545
+ usage: ".重置群会话",
540
546
  handler: async ({ event }) => {
541
- if (event.user_id === event.self_id) return;
547
+ if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
542
548
  const groupId =
543
- event.message_type === "group" ? Number(event.group_id) : undefined;
549
+ event.message_type === "group"
550
+ ? String(event.group_id ?? "").trim() || undefined
551
+ : undefined;
544
552
  if (!groupId) {
545
553
  await event.reply("在群里使用试试看吧~", true);
546
554
  return;
@@ -551,14 +559,14 @@ roleModels = resolved.models;
551
559
  },
552
560
  });
553
561
  ctx.command({
554
- name: "/tts",
555
- match: /^\/tts\s*(.*)$/,
556
- prefixes: false,
562
+ name: "tts",
563
+ prefixes: ["/", "."],
564
+ match: /^tts\s*(.*)$/,
557
565
  permission: "owner",
558
566
  description: "TTS 推理",
559
- usage: "/tts <文本>",
567
+ usage: ".tts <文本>",
560
568
  handler: async ({ event, match }) => {
561
- if (event.user_id === event.self_id) return;
569
+ if (String(event.user_id ?? "") === String(event.self_id ?? "")) return;
562
570
  await handleTtsCommand(pluginCtx, event, String(match?.[1] ?? "").trim());
563
571
  },
564
572
  });
@@ -26,7 +26,7 @@ interface CooldownMessage {
26
26
  event: any;
27
27
  content: string;
28
28
  userName: string;
29
- userId: number;
29
+ userId: string;
30
30
  messageId: string;
31
31
  timestamp: number;
32
32
  isDirectAt: boolean;
@@ -86,8 +86,8 @@ export class CooldownManager {
86
86
 
87
87
  startCooldownTimer(
88
88
  groupSessionId: string,
89
- groupId: number,
90
- selfId: number,
89
+ groupId: string,
90
+ selfId: string,
91
91
  ): void {
92
92
  const existingTimer = this.cooldownTimeoutIds.get(groupSessionId);
93
93
  if (existingTimer) clearTimeout(existingTimer);
@@ -142,7 +142,7 @@ export class CooldownManager {
142
142
 
143
143
  collectMessage(
144
144
  groupSessionId: string,
145
- groupId: number,
145
+ groupId: string,
146
146
  event: any,
147
147
  content: string,
148
148
  isDirectAt: boolean,
@@ -175,9 +175,9 @@ export class CooldownManager {
175
175
 
176
176
  private async processReviewMessages(
177
177
  groupSessionId: string,
178
- groupId: number,
178
+ groupId: string,
179
179
  collected: CooldownMessage[],
180
- selfId: number,
180
+ selfId: string,
181
181
  ): Promise<void> {
182
182
  const mergedContents = collected.map((m) => m.content);
183
183
  const userNames = collected.map((m) => m.userName);
@@ -297,9 +297,9 @@ export class CooldownManager {
297
297
 
298
298
  private async processCooldownWithPlanner(
299
299
  groupSessionId: string,
300
- groupId: number,
300
+ groupId: string,
301
301
  collected: CooldownMessage[],
302
- selfId: number,
302
+ selfId: string,
303
303
  ): Promise<void> {
304
304
  const mergedContent = collected.map((m) => m.content).join("\n");
305
305
  const firstMsg = collected[0];
@@ -4,7 +4,7 @@ import type { TargetMessage } from "../types";
4
4
 
5
5
  export interface StructuredUserInput {
6
6
  userName: string;
7
- userId: number;
7
+ userId: string;
8
8
  userRole?: string;
9
9
  userTitle?: string;
10
10
  content: string;
@@ -192,7 +192,7 @@ function formatStructuredUserContent(input: StructuredUserInput): string {
192
192
  const lines = [
193
193
  "[Group Member Message]",
194
194
  `Name: ${input.userName || "unknown"}`,
195
- `QQ: ${input.userId || 0}`,
195
+ `user_id: ${input.userId || "unknown"}`,
196
196
  `Time: ${formatTimestamp(input.timestamp)}`,
197
197
  ];
198
198
 
@@ -25,7 +25,7 @@ export class IdleCheckManager {
25
25
  private groupLastBotMessageTime = new Map<string, number>();
26
26
  private groupMessageCountAfterBot = new Map<string, number>();
27
27
  private groupLastIdleCheckTime = new Map<string, number>();
28
- private groupBotsMapping = new Map<string, Set<number>>();
28
+ private groupBotsMapping = new Map<string, Set<string>>();
29
29
  private intervalHandle: NodeJS.Timeout | null = null;
30
30
 
31
31
  private ctx: MiokuContext;
@@ -71,11 +71,11 @@ export class IdleCheckManager {
71
71
  this.groupMessageCount.set(groupSessionId, count + 1);
72
72
  }
73
73
 
74
- recordBotMessages(groupSessionId: string, count: number, selfId: number): void {
74
+ recordBotMessages(groupSessionId: string, count: number, selfId: string): void {
75
75
  const current = this.groupMessageCountAfterBot.get(groupSessionId) ?? 0;
76
76
  this.groupMessageCountAfterBot.set(groupSessionId, current + count);
77
77
  let bots = this.groupBotsMapping.get(groupSessionId);
78
- if (!bots) { bots = new Set<number>(); this.groupBotsMapping.set(groupSessionId, bots); }
78
+ if (!bots) { bots = new Set<string>(); this.groupBotsMapping.set(groupSessionId, bots); }
79
79
  bots.add(selfId);
80
80
  this.groupLastBotMessageTime.set(groupSessionId, Date.now());
81
81
  }
@@ -106,14 +106,15 @@ export class IdleCheckManager {
106
106
 
107
107
  const now = Date.now();
108
108
  const checkInterval = 60_000;
109
- const allBotIds = Array.from(this.ctx.bots).map((bot) => Number(bot.bot_id));
109
+ const allBotIds = Array.from(this.ctx.bots).map((bot) => String(bot.bot_id));
110
110
 
111
111
  for (const [groupSessionId, lastTime] of this.groupLastActivityTime) {
112
112
  const lastCheckTime =
113
113
  this.groupLastIdleCheckTime.get(groupSessionId) ?? 0;
114
114
  if (now - lastCheckTime < checkInterval) continue;
115
115
 
116
- const groupId = parseInt(groupSessionId.split(":")[1], 10);
116
+ const groupId = String(groupSessionId.split(":")[1] ?? "").trim();
117
+ if (!groupId) continue;
117
118
  const cfg = this.configProvider(groupId);
118
119
  if (!cfg.planner?.enabled) continue;
119
120
  if (!isGroupAllowed(groupId, cfg)) continue;
@@ -130,7 +131,7 @@ export class IdleCheckManager {
130
131
  cfg.planner.idleCheckBotIds ??
131
132
  baseCfg.planner?.idleCheckBotIds ??
132
133
  allBotIds;
133
- const enabledBotIds = idleCheckBotIds.filter((id: number) =>
134
+ const enabledBotIds = idleCheckBotIds.filter((id: string) =>
134
135
  allBotIds.includes(id),
135
136
  );
136
137
 
@@ -193,8 +194,8 @@ export class IdleCheckManager {
193
194
 
194
195
  private async processIdleCheckTurn(
195
196
  groupSessionId: string,
196
- groupId: number,
197
- selfId: number,
197
+ groupId: string,
198
+ selfId: string,
198
199
  cfg: ChatConfig,
199
200
  scheduledLastActivity: number,
200
201
  scheduledAt: number,
@@ -234,7 +235,7 @@ export class IdleCheckManager {
234
235
 
235
236
  const targetMessage: TargetMessage = {
236
237
  userName: "system",
237
- userId: 0,
238
+ userId: "",
238
239
  userRole: "member",
239
240
  content: "[No one in the group is talking? I'll answer!]",
240
241
  messageId: "",
@@ -246,7 +247,7 @@ export class IdleCheckManager {
246
247
  event: null,
247
248
  groupSessionId,
248
249
  groupId,
249
- userId: 0,
250
+ userId: "",
250
251
  config: cfg,
251
252
  aiService: this.aiService,
252
253
  db: this.db,
@@ -292,7 +293,7 @@ export class IdleCheckManager {
292
293
  result,
293
294
  groupId,
294
295
  groupSessionId,
295
- userId: 0,
296
+ userId: "",
296
297
  selfId,
297
298
  toolCtx,
298
299
  send: true,
@@ -25,7 +25,7 @@ import { finalizeChatTurn } from "../core/chat-turn";
25
25
  import { extractContent, getBotRole } from "../utils";
26
26
 
27
27
  interface DynamicDelayQueueData {
28
- messages: Array<{ event: any; content: string; userName: string; userId: number; messageId: string; timestamp: number }>;
28
+ messages: Array<{ event: any; content: string; userName: string; userId: string; messageId: string; timestamp: number }>;
29
29
  timer: NodeJS.Timeout | null;
30
30
  delayUntil: number;
31
31
  }
@@ -91,7 +91,7 @@ export class QueueProcessor {
91
91
  queueData.messages.push({ event, content, userName, userId: event.user_id, messageId: event.message_id, timestamp: Date.now() });
92
92
  }
93
93
 
94
- startDynamicDelayTimer(groupSessionId: string, groupId: number, delayMs: number, selfId: number): void {
94
+ startDynamicDelayTimer(groupSessionId: string, groupId: string, delayMs: number, selfId: string): void {
95
95
  let queueData = this.dynamicDelayQueues.get(groupSessionId);
96
96
  if (!queueData) {
97
97
  queueData = { messages: [], timer: null, delayUntil: Date.now() + delayMs };
@@ -132,7 +132,7 @@ export class QueueProcessor {
132
132
  return queue != null && Date.now() < queue.delayUntil;
133
133
  }
134
134
 
135
- scheduleQueuedMessages(groupSessionId: string, selfId: number): void {
135
+ scheduleQueuedMessages(groupSessionId: string, selfId: string): void {
136
136
  void this.sessionTurnScheduler
137
137
  .run(
138
138
  groupSessionId,
@@ -156,8 +156,8 @@ export class QueueProcessor {
156
156
 
157
157
  private async processDynamicDelayMessages(
158
158
  groupSessionId: string,
159
- groupId: number,
160
- selfId: number,
159
+ groupId: string,
160
+ selfId: string,
161
161
  messages: DynamicDelayQueueData["messages"],
162
162
  ): Promise<void> {
163
163
  this.ctx.logger.info(`[DynamicDelay] group ${groupId} processes ${messages.length} delayed messages`);
@@ -217,7 +217,7 @@ export class QueueProcessor {
217
217
  });
218
218
  }
219
219
 
220
- async processQueuedMessages(groupSessionId: string, selfId: number): Promise<void> {
220
+ async processQueuedMessages(groupSessionId: string, selfId: string): Promise<void> {
221
221
  try {
222
222
  const queue = this.queueManager.getQueue(groupSessionId);
223
223
  if (!queue || queue.length === 0) {
@@ -225,7 +225,8 @@ export class QueueProcessor {
225
225
  return;
226
226
  }
227
227
 
228
- const groupId = parseInt(groupSessionId.split(":")[1], 10);
228
+ const groupId = String(groupSessionId.split(":")[1] ?? "").trim();
229
+ if (!groupId) return;
229
230
  this.ctx.logger.info(`[Queue] group ${groupSessionId} batch ${queue.length} messages`);
230
231
 
231
232
  const queuedContents: string[] = [];
@@ -255,7 +256,7 @@ export class QueueProcessor {
255
256
  const mergedContent = queuedContents.join("\n");
256
257
 
257
258
  const targetMessage: TargetMessage = {
258
- userName, userId: firstItem.event.user_id || firstItem.event.sender?.user_id,
259
+ userName, userId: String(firstItem.event.user_id ?? firstItem.event.sender?.user_id ?? "").trim(),
259
260
  userRole: firstItem.event.sender?.role || "member", content: mergedContent,
260
261
  messageId: firstItem.event.message_id, timestamp: Date.now(),
261
262
  };
@@ -52,8 +52,8 @@ export class RateLimitGuard {
52
52
  async run<T>(
53
53
  request: () => Promise<T>,
54
54
  opts?: {
55
- userId?: number;
56
- groupId?: number;
55
+ userId?: string;
56
+ groupId?: string;
57
57
  label?: string;
58
58
  skipRetryOnRateLimit?: boolean;
59
59
  },
@@ -13,16 +13,16 @@ const DEFAULT_AI_REQUEST_LIMITS: AIRequestLimitConfig = {
13
13
  windowMs: 60_000,
14
14
  };
15
15
 
16
- export type ChatConfigProvider = (groupId?: number) => ChatConfig;
16
+ export type ChatConfigProvider = (groupId?: string) => ChatConfig;
17
17
 
18
18
  export class RateLimiter {
19
- private userTriggers: Map<number, number[]> = new Map();
20
- private userMessages: Map<number, { content: string; timestamp: number }[]> =
19
+ private userTriggers: Map<string, number[]> = new Map();
20
+ private userMessages: Map<string, { content: string; timestamp: number }[]> =
21
21
  new Map();
22
- private groupLastResponse: Map<number, number> = new Map();
23
- private groupInteractions: Map<number, Map<number, number[]>> = new Map();
24
- private userAiRequests: Map<number, number[]> = new Map();
25
- private groupAiRequests: Map<number, number[]> = new Map();
22
+ private groupLastResponse: Map<string, number> = new Map();
23
+ private groupInteractions: Map<string, Map<string, number[]>> = new Map();
24
+ private userAiRequests: Map<string, number[]> = new Map();
25
+ private groupAiRequests: Map<string, number[]> = new Map();
26
26
 
27
27
  private readonly maxTriggersPerWindow: number;
28
28
  private readonly windowMs: number;
@@ -30,13 +30,13 @@ export class RateLimiter {
30
30
  private readonly groupCooldownMs: number;
31
31
  private readonly cleanupTimer: ReturnType<typeof setInterval>;
32
32
  private getConfig: ChatConfigProvider;
33
- private getQueueLengthFn: ((groupId: number) => number) | null = null;
33
+ private getQueueLengthFn: ((groupId: string) => number) | null = null;
34
34
 
35
35
  setConfigProvider(provider: ChatConfigProvider): void {
36
36
  this.getConfig = provider;
37
37
  }
38
38
 
39
- setQueueLengthGetter(fn: (groupId: number) => number): void {
39
+ setQueueLengthGetter(fn: (groupId: string) => number): void {
40
40
  this.getQueueLengthFn = fn;
41
41
  }
42
42
 
@@ -58,17 +58,17 @@ export class RateLimiter {
58
58
  this.cleanupTimer = setInterval(() => this.cleanup(), 300_000);
59
59
  }
60
60
 
61
- private getDynamicDelay(groupId?: number): DynamicDelayConfig {
61
+ private getDynamicDelay(groupId?: string): DynamicDelayConfig {
62
62
  return this.getConfig(groupId)?.dynamicDelay ?? DEFAULT_DYNAMIC_DELAY;
63
63
  }
64
64
 
65
- private getAiRequestLimits(groupId?: number): AIRequestLimitConfig {
65
+ private getAiRequestLimits(groupId?: string): AIRequestLimitConfig {
66
66
  return this.getConfig(groupId)?.aiRequestLimits ?? DEFAULT_AI_REQUEST_LIMITS;
67
67
  }
68
68
 
69
69
  canProcess(
70
- userId: number,
71
- groupId: number | undefined,
70
+ userId: string,
71
+ groupId: string | undefined,
72
72
  content: string,
73
73
  ): boolean {
74
74
  const now = Date.now();
@@ -93,7 +93,7 @@ export class RateLimiter {
93
93
  return !recentSame;
94
94
  }
95
95
 
96
- record(userId: number, groupId: number | undefined, content: string): void {
96
+ record(userId: string, groupId: string | undefined, content: string): void {
97
97
  const now = Date.now();
98
98
 
99
99
  const triggers = this.userTriggers.get(userId) ?? [];
@@ -110,7 +110,7 @@ export class RateLimiter {
110
110
  }
111
111
  }
112
112
 
113
- recordInteraction(groupId: number, userId: number): void {
113
+ recordInteraction(groupId: string, userId: string): void {
114
114
  const dynamicDelay = this.getDynamicDelay(groupId);
115
115
  if (!dynamicDelay.enabled) return;
116
116
 
@@ -129,7 +129,7 @@ export class RateLimiter {
129
129
  groupUsers.set(userId, timestamps);
130
130
  }
131
131
 
132
- canRunAIRequest(userId?: number, groupId?: number): boolean {
132
+ canRunAIRequest(userId?: string, groupId?: string): boolean {
133
133
  const now = Date.now();
134
134
  const { userRpm, groupRpm, windowMs } = this.getAiRequestLimits(groupId);
135
135
 
@@ -154,7 +154,7 @@ export class RateLimiter {
154
154
  return true;
155
155
  }
156
156
 
157
- recordAIRequest(userId?: number, groupId?: number): void {
157
+ recordAIRequest(userId?: string, groupId?: string): void {
158
158
  const now = Date.now();
159
159
  const { windowMs } = this.getAiRequestLimits(groupId);
160
160
 
@@ -175,7 +175,7 @@ export class RateLimiter {
175
175
  }
176
176
  }
177
177
 
178
- getInteractionCount(groupId: number): number {
178
+ getInteractionCount(groupId: string): number {
179
179
  const now = Date.now();
180
180
  const { interactionWindowMs: windowMs } = this.getDynamicDelay(groupId);
181
181
 
@@ -199,7 +199,7 @@ export class RateLimiter {
199
199
  return count;
200
200
  }
201
201
 
202
- calculateDelay(groupId: number): number {
202
+ calculateDelay(groupId: string): number {
203
203
  const dynamicDelay = this.getDynamicDelay(groupId);
204
204
  if (!dynamicDelay.enabled) return 0;
205
205
 
@@ -211,7 +211,7 @@ export class RateLimiter {
211
211
  return Math.min(delay, maxDelayMs);
212
212
  }
213
213
 
214
- getDelayInfo(groupId: number): {
214
+ getDelayInfo(groupId: string): {
215
215
  delayMs: number;
216
216
  interactionCount: number;
217
217
  shouldDelay: boolean;
@@ -225,7 +225,7 @@ export class RateLimiter {
225
225
  };
226
226
  }
227
227
 
228
- clearGroupInteractions(groupId: number): void {
228
+ clearGroupInteractions(groupId: string): void {
229
229
  this.groupInteractions.delete(groupId);
230
230
  }
231
231
 
package/manage/session.ts CHANGED
@@ -17,7 +17,7 @@ export class SessionManager {
17
17
  /**
18
18
  * 获取或创建会话
19
19
  */
20
- getOrCreate(id: string, type: SessionType, targetId: number): SessionMeta {
20
+ getOrCreate(id: string, type: SessionType, targetId: string): SessionMeta {
21
21
  // 先查缓存
22
22
  if (this.cache.has(id)) {
23
23
  return this.touch(id);