@fraqjs/fraq 0.1.0 → 0.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 CHANGED
@@ -1,3 +1,56 @@
1
1
  # Fraq
2
2
 
3
- - [官方网站](https://fraq.ntqqrev.org/)
3
+ Fraq 是一个 [Milky](https://milky.ntqqrev.org/) 协议的聊天机器人框架,在协议之上提供了更方便的上下文、路由、消息构造、插件和服务依赖注入能力,让你可以用 TypeScript 更轻松地组织一个机器人应用。
4
+
5
+ ## 安装 Fraq
6
+
7
+ 可以通过任意的包管理器安装 Fraq:
8
+
9
+ ```bash
10
+ npm install @fraqjs/fraq
11
+ # or
12
+ yarn add @fraqjs/fraq
13
+ # or
14
+ pnpm add @fraqjs/fraq
15
+ ```
16
+
17
+ Fraq 需要在 Node.js 22+ 环境(或其他的支持 WebSocket API 的服务端 JS 环境)中运行。强烈建议使用 TypeScript 来编写 Fraq 代码,并使用 [tsx](https://www.npmjs.com/package/tsx) 来运行你的项目。
18
+
19
+ ## 第一个 Fraq 机器人
20
+
21
+ 我们从一个 Echo 机器人开始。首先,启动你的 Milky 协议端,并确认它监听的地址与端口。然后,在你的项目中创建一个 `index.ts` 文件,并添加以下代码:
22
+
23
+ ```typescript
24
+ import { Context, msg, param } from '@fraqjs/fraq';
25
+
26
+ const ctx = Context.fromUrl('<替换成你的 Milky 协议端地址>', {
27
+ // accessToken: '<如果你的 Milky 协议端配置了 token,请在这里添加>',
28
+ });
29
+
30
+ ctx.router.command(
31
+ 'echo',
32
+ {
33
+ content: param.greedy(),
34
+ },
35
+ (session, { content }) => {
36
+ session.reply(msg`You said: ${content}`);
37
+ },
38
+ );
39
+
40
+ ctx.start();
41
+ ```
42
+
43
+ 在一切完成之后,用 `tsx` 执行你的 `index.ts` 文件:
44
+
45
+ ```bash
46
+ npx tsx index.ts
47
+ # 或 tsx index.ts, 如果你全局安装了 tsx
48
+ ```
49
+
50
+ 你可以在任何一个包含它的会话中发送 `echo Hello` 来测试它是否正常工作。
51
+
52
+ ## 下一步
53
+
54
+ 上面的例子只是 Fraq 的最小使用方式,你还可以继续了解如何构造更丰富的消息、定义更复杂的指令参数、编写可复用的插件等。
55
+
56
+ 完整文档请见 [fraq.ntqqrev.org](https://fraq.ntqqrev.org/)。
package/dist/index.d.mts CHANGED
@@ -5396,21 +5396,27 @@ type EventMap = {
5396
5396
  };
5397
5397
  //#endregion
5398
5398
  //#region src/protocol/client.d.ts
5399
+ interface MilkyEventSubscription {
5400
+ closed: Promise<void>;
5401
+ stop(): void | Promise<void>;
5402
+ }
5403
+ interface MilkyClient extends ApiEndpoints {
5404
+ startEvents(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
5405
+ }
5399
5406
  declare class MilkyClientBase {
5400
5407
  readonly baseUrl: string;
5401
5408
  readonly wsBaseUrl: string;
5402
5409
  private readonly accessToken?;
5403
5410
  private readonly baseHeaders;
5404
5411
  constructor(
5405
- baseUrl: string,
5412
+ baseUrl: string | URL,
5406
5413
  options?: {
5407
5414
  accessToken?: string;
5408
5415
  },
5409
5416
  );
5410
5417
  callApi(endpoint: string, params?: unknown): Promise<unknown>;
5411
- openEventWebSocket(onEvent: (event: MessageEvent) => void): Promise<WebSocket>;
5418
+ startEvents(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
5412
5419
  }
5413
- type MilkyClient = MilkyClientBase & ApiEndpoints;
5414
5420
  declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
5415
5421
  //#endregion
5416
5422
  //#region src/routing/tokenizer.d.ts
@@ -5526,13 +5532,32 @@ type Filter = { [K in keyof EventMap]?: (event: EventMap[K]) => boolean };
5526
5532
  declare namespace filter {
5527
5533
  function define(filter: Filter): Filter;
5528
5534
  function allPass(): Filter;
5529
- function friend(uinList: number[]): Filter;
5530
- function group(uinList: number[]): Filter;
5535
+ function friend(...uinList: number[]): Filter;
5536
+ function group(...uinList: number[]): Filter;
5531
5537
  function admin(): Filter;
5532
5538
  function or(...filters: Filter[]): Filter;
5533
5539
  function and(...filters: Filter[]): Filter;
5534
5540
  }
5535
5541
  //#endregion
5542
+ //#region src/core/logging.d.ts
5543
+ interface LogMessage {
5544
+ level: 'debug' | 'info' | 'warn' | 'error';
5545
+ module: string;
5546
+ message: string;
5547
+ error?: unknown;
5548
+ time: number;
5549
+ }
5550
+ declare class Logger {
5551
+ private readonly logHandler;
5552
+ private readonly module;
5553
+ constructor(logHandler: (message: LogMessage) => void, module: string);
5554
+ private log;
5555
+ debug(message: string): void;
5556
+ info(message: string): void;
5557
+ warn(message: string, error?: unknown): void;
5558
+ error(message: string, error?: unknown): void;
5559
+ }
5560
+ //#endregion
5536
5561
  //#region src/core/service.d.ts
5537
5562
  /** biome-ignore-all lint/suspicious/noExplicitAny: Service constructors may accept any arguments. */
5538
5563
  type ServiceClass<T extends object = object> = abstract new (...args: any[]) => T;
@@ -5540,21 +5565,38 @@ type ServiceClass<T extends object = object> = abstract new (...args: any[]) =>
5540
5565
  //#region src/core/plugin.d.ts
5541
5566
  type ParameterList = Array<any>;
5542
5567
  interface Plugin<T extends ParameterList> {
5568
+ name: string;
5543
5569
  requires?: readonly ServiceClass[];
5544
5570
  provides?: readonly ServiceClass[];
5545
- apply(context: Context, ...args: T): void | Promise<void>;
5571
+ apply(ctx: Context, ...args: T): void | Promise<void>;
5546
5572
  }
5547
5573
  declare function definePlugin<T extends ParameterList>(plugin: Plugin<T>): Plugin<T>;
5548
5574
  //#endregion
5549
5575
  //#region src/core/context.d.ts
5576
+ interface ContextOptions {
5577
+ reconnect?: {
5578
+ initialDelayMs?: number;
5579
+ maxDelayMs?: number;
5580
+ };
5581
+ logHandler?: (message: LogMessage) => void;
5582
+ }
5583
+ interface ContextUrlOptions {
5584
+ accessToken?: string;
5585
+ }
5550
5586
  declare class Context {
5551
5587
  readonly client: MilkyClient;
5552
- private readonly parent?;
5553
5588
  readonly router: Router;
5589
+ readonly logger: Logger;
5590
+ readonly name: string;
5591
+ private readonly parent?;
5592
+ private readonly filter?;
5554
5593
  private readonly eventBus;
5555
5594
  private readonly plugins;
5556
5595
  private readonly services;
5557
5596
  private readonly subContexts;
5597
+ private readonly initialReconnectDelayMs;
5598
+ private readonly maxReconnectDelayMs;
5599
+ private readonly logHandler?;
5558
5600
  private isStarted;
5559
5601
  private constructor();
5560
5602
  on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void | Promise<void>): void;
@@ -5563,25 +5605,18 @@ declare class Context {
5563
5605
  resolve<T extends object>(service: ServiceClass<T>): T;
5564
5606
  tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
5565
5607
  isProvided<T extends object>(service: ServiceClass<T>): boolean;
5566
- fork(filter?: Filter): Context;
5608
+ fork(name: string, filter?: Filter): Context;
5567
5609
  start(): Promise<void>;
5610
+ private acceptsParentEvent;
5568
5611
  private applyPlugins;
5569
5612
  private sortPlugins;
5570
- private validateUniqueProvidedServices;
5571
5613
  private collectAvailableServices;
5572
- private areRequirementsAvailable;
5573
5614
  private createUnresolvablePluginError;
5574
- private describePlugin;
5575
- private connectEventWebSocket;
5576
- private handleWebSocketMessage;
5577
- private delay;
5615
+ private createProxyContextForPlugin;
5616
+ private runEventStream;
5578
5617
  private createSession;
5579
- static create(options: {
5580
- connect: {
5581
- baseUrl: string;
5582
- accessToken?: string;
5583
- };
5584
- }): Context;
5618
+ static fromUrl(baseUrl: string | URL, options?: ContextOptions & ContextUrlOptions): Context;
5619
+ static fromClient(client: MilkyClient, options?: ContextOptions): Context;
5585
5620
  }
5586
5621
  //#endregion
5587
5622
  //#region src/protocol/segment.d.ts
@@ -5625,24 +5660,29 @@ declare namespace seg {
5625
5660
  }
5626
5661
  //#endregion
5627
5662
  export {
5628
- type ApiEndpoints,
5663
+ ApiEndpoints,
5629
5664
  Context,
5630
- type EventMap,
5631
- type Filter,
5632
- type MilkyClient,
5665
+ ContextOptions,
5666
+ ContextUrlOptions,
5667
+ EventMap,
5668
+ Filter,
5669
+ LogMessage,
5670
+ Logger,
5671
+ MilkyClient,
5672
+ MilkyEventSubscription,
5633
5673
  Parameter,
5634
- type ParameterList,
5635
- type Plugin,
5674
+ ParameterList,
5675
+ Plugin,
5636
5676
  Router,
5637
5677
  type ServiceClass,
5638
- type Session,
5678
+ Session,
5639
5679
  createMilkyClient,
5640
5680
  definePlugin,
5641
5681
  filter,
5682
+ type types_d_exports as milky,
5642
5683
  milkyPackageVersion,
5643
5684
  milkyVersion,
5644
5685
  msg,
5645
5686
  param,
5646
5687
  seg,
5647
- type types_d_exports as types,
5648
5688
  };
package/dist/index.mjs CHANGED
@@ -6,7 +6,8 @@ var MilkyClientBase = class {
6
6
  accessToken;
7
7
  baseHeaders;
8
8
  constructor(baseUrl, options) {
9
- this.baseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
9
+ const normalizedBaseUrl = baseUrl.toString();
10
+ this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
10
11
  this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
11
12
  this.accessToken = options?.accessToken;
12
13
  this.baseHeaders = { "Content-Type": "application/json" };
@@ -18,18 +19,41 @@ var MilkyClientBase = class {
18
19
  body: JSON.stringify(params ?? {}),
19
20
  headers: this.baseHeaders
20
21
  });
21
- if (!response.ok) throw new Error(`API call failed with status ${response.status}`);
22
+ if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
22
23
  const json = await response.json();
23
- if (json.status === "failed") throw new Error(`API call failed with retcode ${json.retcode}: ${json.message}`);
24
+ if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
24
25
  return json.data;
25
26
  }
26
- async openEventWebSocket(onEvent) {
27
+ async startEvents(onEvent) {
27
28
  const ws = new WebSocket(`${this.wsBaseUrl}/event${this.accessToken ? `?access_token=${this.accessToken}` : ""}`);
28
- ws.addEventListener("message", onEvent);
29
- return new Promise((resolve, reject) => {
30
- ws.addEventListener("open", () => resolve(ws));
29
+ let closeSubscription = () => {};
30
+ const closed = new Promise((resolve, reject) => {
31
+ closeSubscription = (error) => {
32
+ if (error) reject(error);
33
+ else resolve();
34
+ };
35
+ });
36
+ ws.addEventListener("message", async (event) => {
37
+ try {
38
+ if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
39
+ await onEvent(JSON.parse(event.data));
40
+ } catch (error) {
41
+ closeSubscription(error);
42
+ ws.close();
43
+ }
44
+ });
45
+ await new Promise((resolve, reject) => {
46
+ ws.addEventListener("open", () => resolve(), { once: true });
31
47
  ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
32
48
  });
49
+ ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
50
+ ws.addEventListener("close", () => closeSubscription(), { once: true });
51
+ return {
52
+ closed,
53
+ stop() {
54
+ ws.close();
55
+ }
56
+ };
33
57
  }
34
58
  };
35
59
  function createMilkyClient(...params) {
@@ -246,38 +270,78 @@ var Router = class Router {
246
270
  }
247
271
  };
248
272
  //#endregion
273
+ //#region src/core/logging.ts
274
+ var Logger = class {
275
+ logHandler;
276
+ module;
277
+ constructor(logHandler, module) {
278
+ this.logHandler = logHandler;
279
+ this.module = module;
280
+ }
281
+ log(level, message, error) {
282
+ this.logHandler({
283
+ level,
284
+ module: this.module,
285
+ message,
286
+ error,
287
+ time: Date.now()
288
+ });
289
+ }
290
+ debug(message) {
291
+ this.log("debug", message);
292
+ }
293
+ info(message) {
294
+ this.log("info", message);
295
+ }
296
+ warn(message, error) {
297
+ this.log("warn", message, error);
298
+ }
299
+ error(message, error) {
300
+ this.log("error", message, error);
301
+ }
302
+ };
303
+ //#endregion
249
304
  //#region src/core/service.ts
250
305
  function getServiceName(service) {
251
306
  return service.name || "<anonymous service>";
252
307
  }
253
308
  //#endregion
254
309
  //#region src/core/context.ts
255
- const INITIAL_RECONNECT_DELAY_MS = 1e3;
256
- const MAX_RECONNECT_DELAY_MS = 3e4;
310
+ const DEFAULT_INITIAL_RECONNECT_DELAY_MS = 1e3;
311
+ const DEFAULT_MAX_RECONNECT_DELAY_MS = 3e4;
257
312
  var Context = class Context {
258
313
  client;
259
- parent;
260
314
  router = new Router();
315
+ logger;
316
+ name;
317
+ parent;
318
+ filter;
261
319
  eventBus = mitt();
262
320
  plugins = [];
263
321
  services = /* @__PURE__ */ new Map();
264
322
  subContexts = [];
323
+ initialReconnectDelayMs;
324
+ maxReconnectDelayMs;
325
+ logHandler;
265
326
  isStarted = false;
266
- constructor(client, parent, filter) {
327
+ constructor(client, options, name, parent, filter) {
267
328
  this.client = client;
329
+ this.initialReconnectDelayMs = options?.reconnect?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
330
+ this.maxReconnectDelayMs = options?.reconnect?.maxDelayMs ?? DEFAULT_MAX_RECONNECT_DELAY_MS;
331
+ this.logHandler = options?.logHandler ?? parent?.logHandler;
332
+ this.name = name ?? "root";
333
+ this.logger = new Logger((message) => this.logHandler?.(message), this.name);
268
334
  this.parent = parent;
335
+ this.filter = filter;
269
336
  parent?.eventBus.on("*", (type, event) => {
270
- if (filter) {
271
- const predicate = filter[type];
272
- if (predicate && !predicate(event)) return;
273
- }
337
+ if (!this.acceptsParentEvent(type, event)) return;
274
338
  this.eventBus.emit(type, event);
275
339
  });
276
340
  this.eventBus.on("message_receive", async ({ data: message }) => {
277
341
  try {
278
- this.router.dispatch(this.createSession(message), message);
342
+ await this.router.dispatch(this.createSession(message), message);
279
343
  } catch (error) {
280
- console.error("Error routing command:", error);
344
+ this.logger.error(`Error routing command (scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
281
345
  }
282
346
  });
283
347
  }
@@ -286,7 +350,7 @@ var Context = class Context {
286
350
  try {
287
351
  await handler(event);
288
352
  } catch (error) {
289
- console.error(`Error handling event ${type}:`, error);
353
+ this.logger.error(`Error handling event ${type}`, error);
290
354
  }
291
355
  });
292
356
  }
@@ -312,8 +376,8 @@ var Context = class Context {
312
376
  isProvided(service) {
313
377
  return this.tryResolve(service) !== void 0;
314
378
  }
315
- fork(filter) {
316
- const subContext = new Context(this.client, this, filter);
379
+ fork(name, filter) {
380
+ const subContext = new Context(this.client, void 0, name, this, filter);
317
381
  this.subContexts.push(subContext);
318
382
  return subContext;
319
383
  }
@@ -322,23 +386,35 @@ var Context = class Context {
322
386
  await this.applyPlugins();
323
387
  for (const subContext of this.subContexts) await subContext.start();
324
388
  this.isStarted = true;
325
- if (!this.parent) this.connectEventWebSocket();
389
+ if (!this.parent) this.runEventStream();
390
+ }
391
+ acceptsParentEvent(type, event) {
392
+ if (!this.filter) return true;
393
+ const predicate = this.filter[type];
394
+ return predicate?.(event) === true;
326
395
  }
327
396
  async applyPlugins() {
328
397
  for (const { plugin, args } of this.sortPlugins()) {
329
398
  const providedBeforeApply = new Set(this.services.keys());
330
- await plugin.apply(this, ...args);
331
- for (const service of plugin.provides ?? []) if (!this.services.has(service) || providedBeforeApply.has(service)) throw new Error(`${this.describePlugin(plugin)} declares service ${getServiceName(service)} but did not provide it.`);
399
+ this.logger.info(`Applying plugin ${plugin.name}`);
400
+ await plugin.apply(this.createProxyContextForPlugin(plugin), ...args);
401
+ for (const service of plugin.provides ?? []) if (!this.services.has(service) || providedBeforeApply.has(service)) throw new Error(`${plugin.name} declares service ${getServiceName(service)} but did not provide it.`);
402
+ this.logger.debug(`Applied plugin ${plugin.name}`);
332
403
  }
333
404
  }
334
405
  sortPlugins() {
335
- this.validateUniqueProvidedServices();
406
+ const providers = /* @__PURE__ */ new Map();
407
+ for (const { plugin } of this.plugins) for (const service of plugin.provides ?? []) {
408
+ const existingProvider = providers.get(service);
409
+ if (existingProvider) throw new Error(`Service ${getServiceName(service)} is declared by multiple plugins: ${existingProvider.name} and ${plugin.name}.`);
410
+ providers.set(service, plugin);
411
+ }
336
412
  const pending = [...this.plugins];
337
413
  const sorted = [];
338
414
  const available = /* @__PURE__ */ new Set();
339
415
  for (const service of this.collectAvailableServices()) available.add(service);
340
416
  while (pending.length > 0) {
341
- const nextIndex = pending.findIndex(({ plugin }) => this.areRequirementsAvailable(plugin, available));
417
+ const nextIndex = pending.findIndex(({ plugin }) => (plugin.requires ?? []).every((service) => available.has(service)));
342
418
  if (nextIndex === -1) throw this.createUnresolvablePluginError(pending, available);
343
419
  const [next] = pending.splice(nextIndex, 1);
344
420
  sorted.push(next);
@@ -346,22 +422,11 @@ var Context = class Context {
346
422
  }
347
423
  return sorted;
348
424
  }
349
- validateUniqueProvidedServices() {
350
- const providers = /* @__PURE__ */ new Map();
351
- for (const { plugin } of this.plugins) for (const service of plugin.provides ?? []) {
352
- const existingProvider = providers.get(service);
353
- if (existingProvider) throw new Error(`Service ${getServiceName(service)} is declared by multiple plugins: ${this.describePlugin(existingProvider)} and ${this.describePlugin(plugin)}.`);
354
- providers.set(service, plugin);
355
- }
356
- }
357
425
  collectAvailableServices() {
358
426
  const services = [...this.services.keys()];
359
427
  if (this.parent) services.push(...this.parent.collectAvailableServices());
360
428
  return services;
361
429
  }
362
- areRequirementsAvailable(plugin, available) {
363
- return (plugin.requires ?? []).every((service) => available.has(service));
364
- }
365
430
  createUnresolvablePluginError(pending, available) {
366
431
  const missingRequirements = /* @__PURE__ */ new Map();
367
432
  const pendingProviders = /* @__PURE__ */ new Set();
@@ -372,44 +437,39 @@ var Context = class Context {
372
437
  missingRequirements.set(service, plugins);
373
438
  }
374
439
  const lines = [...missingRequirements].map(([service, plugins]) => {
375
- const dependents = plugins.map((plugin) => this.describePlugin(plugin)).join(", ");
440
+ const dependents = plugins.map((plugin) => plugin.name).join(", ");
376
441
  const reason = pendingProviders.has(service) ? "blocked by a dependency cycle" : "no installed plugin provides it";
377
442
  return `${getServiceName(service)} required by ${dependents} (${reason})`;
378
443
  });
379
444
  return /* @__PURE__ */ new Error(`Unable to resolve plugin service dependencies: ${lines.join("; ")}.`);
380
445
  }
381
- describePlugin(plugin) {
382
- const pluginIndex = this.plugins.findIndex((installed) => installed.plugin === plugin);
383
- return plugin.constructor.name && plugin.constructor.name !== "Object" ? plugin.constructor.name : `plugin #${pluginIndex + 1}`;
446
+ createProxyContextForPlugin(plugin) {
447
+ const proxyLogger = new Logger((message) => this.logHandler?.(message), plugin.name);
448
+ return new Proxy(this, { get(target, prop) {
449
+ if (prop === "logger") return proxyLogger;
450
+ else return target[prop];
451
+ } });
384
452
  }
385
- async connectEventWebSocket() {
386
- let reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
453
+ async runEventStream() {
454
+ let reconnectDelay = this.initialReconnectDelayMs;
387
455
  while (this.isStarted) {
388
456
  try {
389
- const ws = await this.client.openEventWebSocket((event) => this.handleWebSocketMessage(event));
390
- reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
391
- await new Promise((resolve) => {
392
- ws.addEventListener("close", () => resolve(), { once: true });
457
+ const subscription = await this.client.startEvents((event) => {
458
+ try {
459
+ this.eventBus.emit(event.event_type, event);
460
+ } catch (error) {
461
+ this.logger.error("Error handling event stream event", error);
462
+ }
393
463
  });
464
+ reconnectDelay = this.initialReconnectDelayMs;
465
+ await subscription.closed;
394
466
  } catch (error) {
395
- console.error("Error connecting event WebSocket:", error);
467
+ this.logger.error("Error connecting event stream", error);
396
468
  }
397
- await this.delay(reconnectDelay);
398
- reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
469
+ await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
470
+ reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
399
471
  }
400
472
  }
401
- handleWebSocketMessage(event) {
402
- try {
403
- if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
404
- const payload = JSON.parse(event.data);
405
- this.eventBus.emit(payload.event_type, payload);
406
- } catch (error) {
407
- console.error("Error handling WebSocket event:", error);
408
- }
409
- }
410
- delay(ms) {
411
- return new Promise((resolve) => setTimeout(resolve, ms));
412
- }
413
473
  createSession(message) {
414
474
  return {
415
475
  raw: message,
@@ -430,13 +490,16 @@ var Context = class Context {
430
490
  break;
431
491
  }
432
492
  } catch (error) {
433
- console.error("Error sending reply:", error);
493
+ this.logger.error(`Error sending reply (source msg: scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
434
494
  }
435
495
  }
436
496
  };
437
497
  }
438
- static create(options) {
439
- return new Context(createMilkyClient(options.connect.baseUrl, { accessToken: options.connect.accessToken }));
498
+ static fromUrl(baseUrl, options) {
499
+ return new Context(createMilkyClient(baseUrl, { accessToken: options?.accessToken }), options);
500
+ }
501
+ static fromClient(client, options) {
502
+ return new Context(client, options);
440
503
  }
441
504
  };
442
505
  //#endregion
@@ -476,7 +539,7 @@ let filter;
476
539
  return result;
477
540
  }
478
541
  _filter.allPass = allPass;
479
- function friend(uinList) {
542
+ function friend(...uinList) {
480
543
  const uinSet = new Set(uinList);
481
544
  return {
482
545
  message_receive: ({ data }) => data.message_scene === "friend" && uinSet.has(data.peer_id),
@@ -488,7 +551,7 @@ let filter;
488
551
  };
489
552
  }
490
553
  _filter.friend = friend;
491
- function group(uinList) {
554
+ function group(...uinList) {
492
555
  const uinSet = new Set(uinList);
493
556
  return {
494
557
  message_receive: ({ data }) => data.message_scene === "group" && uinSet.has(data.peer_id),
@@ -774,4 +837,4 @@ let param;
774
837
  _param.segment = segment;
775
838
  })(param || (param = {}));
776
839
  //#endregion
777
- export { Context, Parameter, Router, createMilkyClient, definePlugin, filter, milkyPackageVersion, milkyVersion, msg, param, seg };
840
+ export { Context, Logger, Parameter, Router, createMilkyClient, definePlugin, filter, milkyPackageVersion, milkyVersion, msg, param, seg };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fraqjs/fraq",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "description": "Milky Bot framework",
6
6
  "files": [
7
7
  "dist"
@@ -14,6 +14,7 @@
14
14
  "type": "git",
15
15
  "url": "git+https://github.com/Wesley-Young/fraq.git"
16
16
  },
17
+ "homepage": "https://fraq.ntqqrev.org/",
17
18
  "license": "MIT",
18
19
  "dependencies": {
19
20
  "mitt": "^3.0.1"
@@ -22,7 +23,7 @@
22
23
  "node": ">=22"
23
24
  },
24
25
  "scripts": {
25
- "build": "tsdown && node scripts/format-dts-comments.mjs && biome format --vcs-enabled=false --write dist/index.d.mts",
26
+ "build": "tsdown && tsx scripts/format-dts-comments.ts && biome format --vcs-enabled=false --write dist/index.d.mts",
26
27
  "test": "tsx --test test/*.test.ts"
27
28
  }
28
29
  }