@fraqjs/fraq 0.10.3 → 0.11.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/dist/index.d.mts CHANGED
@@ -405,7 +405,7 @@ declare namespace types_d_exports {
405
405
  };
406
406
  }
407
407
  declare const milkyVersion = '1.3';
408
- declare const milkyPackageVersion = '1.3.0-rc.1';
408
+ declare const milkyPackageVersion = '1.3.0-rc.2';
409
409
  /** 机器人离线事件 */
410
410
  interface BotOfflineEvent {
411
411
  /** 数据类型区分字段,表示自身为机器人离线事件 */
@@ -2846,7 +2846,7 @@ interface GetImplInfoOutput {
2846
2846
  | 'iphone'
2847
2847
  | 'harmony'
2848
2848
  | 'watch';
2849
- /** 协议端实现的 Milky 协议版本,目前为 "1.2" */
2849
+ /** 协议端实现的 Milky 协议版本,目前为 "1.3" */
2850
2850
  milky_version: string;
2851
2851
  }
2852
2852
  /** 获取协议端信息 API 响应数据 */
@@ -2868,7 +2868,7 @@ interface GetImplInfoOutput_ZodInput {
2868
2868
  | 'iphone'
2869
2869
  | 'harmony'
2870
2870
  | 'watch';
2871
- /** 协议端实现的 Milky 协议版本,目前为 "1.2" */
2871
+ /** 协议端实现的 Milky 协议版本,目前为 "1.3" */
2872
2872
  milky_version: string;
2873
2873
  }
2874
2874
  /** 获取用户个人信息 API 请求参数 */
@@ -4151,6 +4151,8 @@ interface GetPrivateFileDownloadUrlInput {
4151
4151
  file_id: string;
4152
4152
  /** 文件的 TriSHA1 哈希值 */
4153
4153
  file_hash: string;
4154
+ /** 是否为自己发送的文件 */
4155
+ is_self_send: boolean;
4154
4156
  }
4155
4157
  /** 获取私聊文件下载链接 API 请求参数 */
4156
4158
  interface GetPrivateFileDownloadUrlInput_ZodInput {
@@ -4160,6 +4162,8 @@ interface GetPrivateFileDownloadUrlInput_ZodInput {
4160
4162
  file_id: string;
4161
4163
  /** 文件的 TriSHA1 哈希值 */
4162
4164
  file_hash: string;
4165
+ /** 是否为自己发送的文件 */
4166
+ is_self_send?: boolean | null | undefined;
4163
4167
  }
4164
4168
  /** 获取私聊文件下载链接 API 响应数据 */
4165
4169
  interface GetPrivateFileDownloadUrlOutput {
@@ -5396,13 +5400,7 @@ type EventMap = {
5396
5400
  };
5397
5401
  //#endregion
5398
5402
  //#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
- }
5403
+ interface MilkyClient extends ApiEndpoints {}
5406
5404
  declare class MilkyClientBase {
5407
5405
  readonly baseUrl: string;
5408
5406
  readonly wsBaseUrl: string;
@@ -5415,10 +5413,26 @@ declare class MilkyClientBase {
5415
5413
  },
5416
5414
  );
5417
5415
  callApi(endpoint: string, params?: unknown): Promise<unknown>;
5418
- startEvents(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
5419
5416
  }
5420
5417
  declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
5421
5418
  //#endregion
5419
+ //#region src/protocol/event.d.ts
5420
+ interface MilkyEventSubscription {
5421
+ closed: Promise<void>;
5422
+ stop(): void | Promise<void>;
5423
+ }
5424
+ interface MilkyEventSource {
5425
+ readonly name?: string;
5426
+ start(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
5427
+ }
5428
+ interface MilkyWebSocketEventSourceOptions {
5429
+ accessToken?: string;
5430
+ }
5431
+ declare function createMilkyWebSocketEventSource(
5432
+ baseUrl: string | URL,
5433
+ options?: MilkyWebSocketEventSourceOptions,
5434
+ ): MilkyEventSource;
5435
+ //#endregion
5422
5436
  //#region src/routing/tokenizer.d.ts
5423
5437
  type Token =
5424
5438
  | string
@@ -5529,6 +5543,7 @@ interface SessionReplyOptions {
5529
5543
  withMention?: boolean;
5530
5544
  }
5531
5545
  interface Session {
5546
+ selfId: number;
5532
5547
  raw: IncomingMessage;
5533
5548
  reply(
5534
5549
  textOrSegments: string | OutgoingSegment_ZodInput[],
@@ -5719,6 +5734,7 @@ interface ContextOptions {
5719
5734
  }
5720
5735
  interface ContextUrlOptions {
5721
5736
  accessToken?: string;
5737
+ installEventSource?: boolean;
5722
5738
  }
5723
5739
  declare class Context {
5724
5740
  readonly client: MilkyClient;
@@ -5731,6 +5747,7 @@ declare class Context {
5731
5747
  private readonly plugins;
5732
5748
  private readonly services;
5733
5749
  private readonly subContexts;
5750
+ private readonly eventSourceRuntimes;
5734
5751
  private readonly parentEventForwarder?;
5735
5752
  private readonly timers;
5736
5753
  private readonly initialReconnectDelayMs;
@@ -5739,16 +5756,13 @@ declare class Context {
5739
5756
  private state;
5740
5757
  private startPromise?;
5741
5758
  private stopPromise?;
5742
- private eventSubscription?;
5743
- private eventStreamTask?;
5744
- private reconnectTimer?;
5745
- private resolveReconnectTimer?;
5746
5759
  private constructor();
5747
5760
  on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void | Promise<void>): () => void;
5748
5761
  install<T extends ParameterList, I extends Injection | undefined, OI extends Injection | undefined>(
5749
5762
  plugin: Plugin<T, I, OI>,
5750
5763
  ...args: T
5751
5764
  ): void;
5765
+ installEventSource(eventSource: MilkyEventSource): void;
5752
5766
  provide<T extends object>(service: ServiceClass<T>, instance: T): void;
5753
5767
  resolve<T extends object>(service: ServiceClass<T>): T;
5754
5768
  tryResolve<T extends object>(service: ServiceClass<T>): T | undefined;
@@ -5756,7 +5770,7 @@ declare class Context {
5756
5770
  fork(name: string, filter?: Filter): Context;
5757
5771
  timeout(delayMs: number, callback: () => void | Promise<void>): NodeJS.Timeout;
5758
5772
  interval(intervalMs: number, callback: () => void | Promise<void>): NodeJS.Timeout;
5759
- createSession(message: IncomingMessage): Session;
5773
+ createSession(selfId: number, message: IncomingMessage): Session;
5760
5774
  start(): Promise<void>;
5761
5775
  stop(): Promise<void>;
5762
5776
  private startInternal;
@@ -5765,7 +5779,7 @@ declare class Context {
5765
5779
  private assertCanScheduleTimer;
5766
5780
  private runTimerCallback;
5767
5781
  private clearTimers;
5768
- private stopEventStream;
5782
+ private stopEventSources;
5769
5783
  private acceptsParentEvent;
5770
5784
  private applyPlugins;
5771
5785
  private recursiveApplyPlugins;
@@ -5779,7 +5793,8 @@ declare class Context {
5779
5793
  private createUnresolvablePluginError;
5780
5794
  private getPluginContext;
5781
5795
  private createProxyContextForPlugin;
5782
- private runEventStream;
5796
+ private startEventSource;
5797
+ private runEventSource;
5783
5798
  private waitForReconnectDelay;
5784
5799
  private resolveReconnectDelay;
5785
5800
  static fromUrl(baseUrl: string | URL, options?: ContextOptions & ContextUrlOptions): Context;
@@ -5844,7 +5859,9 @@ export {
5844
5859
  LogMessage,
5845
5860
  Logger,
5846
5861
  MilkyClient,
5862
+ MilkyEventSource,
5847
5863
  MilkyEventSubscription,
5864
+ MilkyWebSocketEventSourceOptions,
5848
5865
  Parameter,
5849
5866
  ParameterList,
5850
5867
  ParamsOf,
@@ -5862,6 +5879,7 @@ export {
5862
5879
  TypeInstruction,
5863
5880
  combineLogHandlers,
5864
5881
  createMilkyClient,
5882
+ createMilkyWebSocketEventSource,
5865
5883
  definePlugin,
5866
5884
  filter,
5867
5885
  implementsESNextDisposable,
package/dist/index.mjs CHANGED
@@ -24,37 +24,6 @@ var MilkyClientBase = class {
24
24
  if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
25
25
  return json.data;
26
26
  }
27
- async startEvents(onEvent) {
28
- const ws = new WebSocket(`${this.wsBaseUrl}/event${this.accessToken ? `?access_token=${this.accessToken}` : ""}`);
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 });
47
- ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
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
- };
57
- }
58
27
  };
59
28
  function createMilkyClient(...params) {
60
29
  const base = new MilkyClientBase(...params);
@@ -64,6 +33,46 @@ function createMilkyClient(...params) {
64
33
  } });
65
34
  }
66
35
  //#endregion
36
+ //#region src/protocol/event.ts
37
+ function createMilkyWebSocketEventSource(baseUrl, options) {
38
+ const normalizedBaseUrl = baseUrl.toString();
39
+ const wsBaseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1).replace(/^http/, "ws") : normalizedBaseUrl.replace(/^http/, "ws");
40
+ return {
41
+ name: "websocket",
42
+ async start(onEvent) {
43
+ const ws = new WebSocket(`${wsBaseUrl}/event${options?.accessToken ? `?access_token=${options.accessToken}` : ""}`);
44
+ let closeSubscription = () => {};
45
+ const closed = new Promise((resolve, reject) => {
46
+ closeSubscription = (error) => {
47
+ if (error) reject(error);
48
+ else resolve();
49
+ };
50
+ });
51
+ ws.addEventListener("message", async (event) => {
52
+ try {
53
+ if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
54
+ await onEvent(JSON.parse(event.data));
55
+ } catch (error) {
56
+ closeSubscription(error);
57
+ ws.close();
58
+ }
59
+ });
60
+ await new Promise((resolve, reject) => {
61
+ ws.addEventListener("open", () => resolve(), { once: true });
62
+ ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
63
+ });
64
+ ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
65
+ ws.addEventListener("close", () => closeSubscription(), { once: true });
66
+ return {
67
+ closed,
68
+ stop() {
69
+ ws.close();
70
+ }
71
+ };
72
+ }
73
+ };
74
+ }
75
+ //#endregion
67
76
  //#region src/protocol/segment.ts
68
77
  function msg(strings, ...values) {
69
78
  let buffer = "";
@@ -628,6 +637,7 @@ var Context = class Context {
628
637
  plugins = [];
629
638
  services = /* @__PURE__ */ new Map();
630
639
  subContexts = /* @__PURE__ */ new Map();
640
+ eventSourceRuntimes = /* @__PURE__ */ new Map();
631
641
  parentEventForwarder;
632
642
  timers = /* @__PURE__ */ new Set();
633
643
  initialReconnectDelayMs;
@@ -636,10 +646,6 @@ var Context = class Context {
636
646
  state = "idle";
637
647
  startPromise;
638
648
  stopPromise;
639
- eventSubscription;
640
- eventStreamTask;
641
- reconnectTimer;
642
- resolveReconnectTimer;
643
649
  constructor(client, options, name, parent, filter) {
644
650
  this.client = client;
645
651
  this.initialReconnectDelayMs = options?.reconnect?.initialDelayMs ?? DEFAULT_INITIAL_RECONNECT_DELAY_MS;
@@ -656,10 +662,10 @@ var Context = class Context {
656
662
  };
657
663
  parent.eventBus.on("*", this.parentEventForwarder);
658
664
  }
659
- this.eventBus.on("message_receive", async ({ data: message }) => {
665
+ this.eventBus.on("message_receive", async ({ self_id, data: message }) => {
660
666
  try {
661
667
  if (this.state === "stopping" || this.state === "stopped") return;
662
- await this.router.dispatch(this.createSession(message), message);
668
+ await this.router.dispatch(this.createSession(self_id, message), message);
663
669
  } catch (error) {
664
670
  this.logger.error(`Error routing command (scene=${message.message_scene} peer=${message.peer_id} sender=${message.sender_id} seq=${message.message_seq})`, error);
665
671
  }
@@ -685,6 +691,11 @@ var Context = class Context {
685
691
  args
686
692
  });
687
693
  }
694
+ installEventSource(eventSource) {
695
+ if (this.eventSourceRuntimes.has(eventSource)) return;
696
+ this.eventSourceRuntimes.set(eventSource, {});
697
+ if (this.state === "started") this.startEventSource(eventSource);
698
+ }
688
699
  provide(service, instance) {
689
700
  if (this.services.has(service)) throw new Error(`Service ${service.name} has already been provided in this context.`);
690
701
  if (implementsESNextDisposable(instance) && !isDisposable(instance)) throw new Error(`
@@ -735,8 +746,9 @@ and implement the dispose method to clean up resources when the context stops.
735
746
  this.timers.add(interval);
736
747
  return interval;
737
748
  }
738
- createSession(message) {
749
+ createSession(selfId, message) {
739
750
  return {
751
+ selfId,
740
752
  raw: message,
741
753
  reply: async (textOrSegments, options) => {
742
754
  const actualSegments = [];
@@ -819,8 +831,10 @@ and implement the dispose method to clean up resources when the context stops.
819
831
  for (const context of startingContexts) if (context.state === "starting") context.state = "idle";
820
832
  throw error;
821
833
  }
822
- for (const { context } of appliedContextPlugins) context.state = "started";
823
- if (!this.parent) this.eventStreamTask = this.runEventStream();
834
+ for (const { context } of appliedContextPlugins) {
835
+ context.state = "started";
836
+ for (const eventSource of context.eventSourceRuntimes.keys()) context.startEventSource(eventSource);
837
+ }
824
838
  }
825
839
  getState() {
826
840
  return this.state;
@@ -833,7 +847,7 @@ and implement the dispose method to clean up resources when the context stops.
833
847
  } catch (error) {
834
848
  errors.push(error);
835
849
  }
836
- await this.stopEventStream(errors);
850
+ await this.stopEventSources(errors);
837
851
  if (this.parent && this.parentEventForwarder) this.parent.eventBus.off("*", this.parentEventForwarder);
838
852
  for (const service of [...this.services.values()].reverse()) {
839
853
  if (!isDisposable(service)) continue;
@@ -862,20 +876,27 @@ and implement the dispose method to clean up resources when the context stops.
862
876
  for (const timer of this.timers) clearTimeout(timer);
863
877
  this.timers.clear();
864
878
  }
865
- async stopEventStream(errors) {
866
- this.resolveReconnectDelay();
867
- const subscription = this.eventSubscription;
868
- if (subscription) try {
869
- await subscription.stop();
870
- } catch (error) {
871
- errors.push(error);
879
+ async stopEventSources(errors) {
880
+ for (const [_, runtime] of this.eventSourceRuntimes) {
881
+ this.resolveReconnectDelay(runtime);
882
+ const subscription = runtime.subscription;
883
+ if (!subscription) continue;
884
+ try {
885
+ await subscription.stop();
886
+ } catch (error) {
887
+ errors.push(error);
888
+ }
889
+ runtime.subscription = void 0;
872
890
  }
873
- if (this.eventStreamTask) try {
874
- await this.eventStreamTask;
875
- } catch (error) {
876
- errors.push(error);
877
- } finally {
878
- this.eventStreamTask = void 0;
891
+ for (const runtime of this.eventSourceRuntimes.values()) {
892
+ if (!runtime.task) continue;
893
+ try {
894
+ await runtime.task;
895
+ } catch (error) {
896
+ errors.push(error);
897
+ } finally {
898
+ runtime.task = void 0;
899
+ }
879
900
  }
880
901
  }
881
902
  acceptsParentEvent(type, event) {
@@ -1009,13 +1030,18 @@ and implement the dispose method to clean up resources when the context stops.
1009
1030
  else return Reflect.get(target, prop, receiver);
1010
1031
  } });
1011
1032
  }
1012
- async runEventStream() {
1033
+ startEventSource(eventSource) {
1034
+ const runtime = this.eventSourceRuntimes.get(eventSource);
1035
+ if (!runtime) return;
1036
+ runtime.task = this.runEventSource(eventSource, runtime);
1037
+ }
1038
+ async runEventSource(eventSource, runtime) {
1013
1039
  let reconnectDelay = this.initialReconnectDelayMs;
1014
1040
  let reconnectAttempt = 1;
1015
1041
  while (this.state === "started") {
1016
1042
  try {
1017
- this.logger.debug(`Connecting event stream (attempt=${reconnectAttempt})`);
1018
- const subscription = await this.client.startEvents((event) => {
1043
+ this.logger.debug(`Connecting ${eventSource.name ?? "event source"} (attempt=${reconnectAttempt})`);
1044
+ const subscription = await eventSource.start((event) => {
1019
1045
  try {
1020
1046
  if (this.state !== "started") return;
1021
1047
  this.eventBus.emit(event.event_type, event);
@@ -1027,48 +1053,52 @@ and implement the dispose method to clean up resources when the context stops.
1027
1053
  await subscription.stop();
1028
1054
  break;
1029
1055
  }
1030
- this.eventSubscription = subscription;
1031
- this.logger.info("Event stream connected");
1056
+ runtime.subscription = subscription;
1057
+ this.logger.info(`${eventSource.name ?? "Event source"} connected`);
1032
1058
  reconnectDelay = this.initialReconnectDelayMs;
1033
1059
  reconnectAttempt = 1;
1034
1060
  await subscription.closed;
1035
- this.eventSubscription = void 0;
1061
+ if (runtime.subscription === subscription) runtime.subscription = void 0;
1036
1062
  if (this.state !== "started") break;
1037
- this.logger.warn(`Event stream disconnected; reconnecting in ${reconnectDelay}ms`);
1063
+ this.logger.warn(`${eventSource.name ?? "Event source"} disconnected; reconnecting in ${reconnectDelay}ms`);
1038
1064
  } catch (error) {
1039
- this.eventSubscription = void 0;
1040
1065
  if (this.state !== "started") break;
1041
- this.logger.error(`Error connecting event stream; reconnecting in ${reconnectDelay}ms`, error);
1066
+ this.logger.error(`Error connecting ${eventSource.name ?? "event source"}; reconnecting in ${reconnectDelay}ms`, error);
1042
1067
  }
1043
- await this.waitForReconnectDelay(reconnectDelay);
1068
+ await this.waitForReconnectDelay(runtime, reconnectDelay);
1044
1069
  reconnectDelay = Math.min(reconnectDelay * 2, this.maxReconnectDelayMs);
1045
1070
  reconnectAttempt += 1;
1046
1071
  }
1047
1072
  }
1048
- waitForReconnectDelay(delay) {
1073
+ waitForReconnectDelay(runtime, delay) {
1049
1074
  return new Promise((resolve) => {
1050
- this.resolveReconnectTimer = resolve;
1051
- this.reconnectTimer = setTimeout(() => {
1052
- this.reconnectTimer = void 0;
1053
- this.resolveReconnectTimer = void 0;
1075
+ runtime.resolveReconnectTimer = resolve;
1076
+ runtime.reconnectTimer = setTimeout(() => {
1077
+ runtime.reconnectTimer = void 0;
1078
+ runtime.resolveReconnectTimer = void 0;
1054
1079
  resolve();
1055
1080
  }, delay);
1056
1081
  });
1057
1082
  }
1058
- resolveReconnectDelay() {
1059
- if (this.reconnectTimer) {
1060
- clearTimeout(this.reconnectTimer);
1061
- this.reconnectTimer = void 0;
1083
+ resolveReconnectDelay(runtime) {
1084
+ if (runtime.reconnectTimer) {
1085
+ clearTimeout(runtime.reconnectTimer);
1086
+ runtime.reconnectTimer = void 0;
1062
1087
  }
1063
- const resolve = this.resolveReconnectTimer;
1064
- this.resolveReconnectTimer = void 0;
1088
+ const resolve = runtime.resolveReconnectTimer;
1089
+ runtime.resolveReconnectTimer = void 0;
1065
1090
  resolve?.();
1066
1091
  }
1067
1092
  static fromUrl(baseUrl, options) {
1068
- return new Context(createMilkyClient(baseUrl, { accessToken: options?.accessToken }), options);
1093
+ const context = new Context(createMilkyClient(baseUrl, { accessToken: options?.accessToken }), options);
1094
+ if (options?.installEventSource ?? true) context.installEventSource(createMilkyWebSocketEventSource(baseUrl, { accessToken: options?.accessToken }));
1095
+ return context;
1069
1096
  }
1070
1097
  static fromClient(client, options) {
1071
- return new Context(client, options);
1098
+ const context = new Context(client, options);
1099
+ const eventSourceLike = client;
1100
+ if (typeof eventSourceLike.start === "function") context.installEventSource(eventSourceLike);
1101
+ return context;
1072
1102
  }
1073
1103
  };
1074
1104
  //#endregion
@@ -1226,7 +1256,7 @@ function definePlugin(plugin) {
1226
1256
  //#endregion
1227
1257
  //#region src/protocol/types.ts
1228
1258
  const milkyVersion = "1.3";
1229
- const milkyPackageVersion = "1.3.0-rc.1";
1259
+ const milkyPackageVersion = "1.3.0-rc.2";
1230
1260
  //#endregion
1231
1261
  //#region src/routing/parameter.ts
1232
1262
  var Parameter = class Parameter {
@@ -1352,4 +1382,4 @@ let param;
1352
1382
  _param.segment = segment;
1353
1383
  })(param || (param = {}));
1354
1384
  //#endregion
1355
- export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createMilkyClient, definePlugin, filter, implementsESNextDisposable, isDisposable, milkyPackageVersion, milkyVersion, msg, param, seg };
1385
+ export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createMilkyClient, createMilkyWebSocketEventSource, definePlugin, filter, implementsESNextDisposable, isDisposable, 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.10.3",
4
+ "version": "0.11.0",
5
5
  "description": "Milky Bot framework",
6
6
  "files": [
7
7
  "dist"