@fraqjs/fraq 0.13.3 → 0.14.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
@@ -5377,6 +5377,86 @@ interface ApiEndpoints$1 {
5377
5377
  };
5378
5378
  }
5379
5379
  //#endregion
5380
+ //#region src/protocol/endpoint.d.ts
5381
+ type ApiEndpointName = keyof ApiEndpoints$1;
5382
+ type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
5383
+ type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
5384
+ type RawApiEndpoint<E extends ApiEndpointName> = {
5385
+ request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
5386
+ response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
5387
+ };
5388
+ type ApiRequest<E extends ApiEndpointName> = RawApiEndpoint<E>['request'];
5389
+ type ApiParams<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
5390
+ ? undefined
5391
+ : AllOptional<RawApiEndpoint<E>['request']> extends true
5392
+ ? RawApiEndpoint<E>['request'] | undefined
5393
+ : RawApiEndpoint<E>['request'];
5394
+ type ApiResponse<E extends ApiEndpointName> = RawApiEndpoint<E>['response'];
5395
+ interface ApiCall<E extends ApiEndpointName = ApiEndpointName> {
5396
+ endpoint: E;
5397
+ params: ApiParams<E>;
5398
+ }
5399
+ type ApiNext<E extends ApiEndpointName> =
5400
+ undefined extends ApiParams<E>
5401
+ ? (params?: ApiParams<E>) => Promise<ApiResponse<E>>
5402
+ : (params: ApiParams<E>) => Promise<ApiResponse<E>>;
5403
+ type ApiHook<E extends ApiEndpointName> = (
5404
+ params: ApiParams<E>,
5405
+ next: ApiNext<E>,
5406
+ call: ApiCall<E>,
5407
+ ) => ApiResponse<E> | Promise<ApiResponse<E>>;
5408
+ type AnyApiCall = { [E in ApiEndpointName]: ApiCall<E> }[ApiEndpointName];
5409
+ type AnyApiNext = (params?: unknown) => Promise<unknown>;
5410
+ type AnyApiHook = (call: AnyApiCall, next: AnyApiNext) => unknown | Promise<unknown>;
5411
+ type ApiEndpointFunction<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
5412
+ ? () => Promise<ApiResponse<E>>
5413
+ : AllOptional<RawApiEndpoint<E>['request']> extends true
5414
+ ? (params?: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>
5415
+ : (params: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>;
5416
+ type ApiEndpoints = { [E in ApiEndpointName]: ApiEndpointFunction<E> };
5417
+ type EventMap = {
5418
+ [K in Event['event_type']]: Extract<
5419
+ Event,
5420
+ {
5421
+ event_type: K;
5422
+ }
5423
+ >;
5424
+ };
5425
+ //#endregion
5426
+ //#region src/protocol/client.d.ts
5427
+ interface MilkyClient extends ApiEndpoints {}
5428
+ declare class MilkyClientBase {
5429
+ readonly baseUrl: string;
5430
+ readonly wsBaseUrl: string;
5431
+ private readonly accessToken?;
5432
+ private readonly baseHeaders;
5433
+ constructor(
5434
+ baseUrl: string | URL,
5435
+ options?: {
5436
+ accessToken?: string;
5437
+ },
5438
+ );
5439
+ callApi(endpoint: string, params?: unknown): Promise<unknown>;
5440
+ }
5441
+ declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
5442
+ //#endregion
5443
+ //#region src/protocol/event.d.ts
5444
+ interface MilkyEventSubscription {
5445
+ closed: Promise<void>;
5446
+ stop(): void | Promise<void>;
5447
+ }
5448
+ interface MilkyEventSource {
5449
+ readonly name?: string;
5450
+ start(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
5451
+ }
5452
+ interface MilkyWebSocketEventSourceOptions {
5453
+ accessToken?: string;
5454
+ }
5455
+ declare function createMilkyWebSocketEventSource(
5456
+ baseUrl: string | URL,
5457
+ options?: MilkyWebSocketEventSourceOptions,
5458
+ ): MilkyEventSource;
5459
+ //#endregion
5380
5460
  //#region src/routing/tokenizer.d.ts
5381
5461
  type Token =
5382
5462
  | string
@@ -5548,21 +5628,20 @@ type RouteActivation =
5548
5628
  type: 'prefix';
5549
5629
  prefix: string;
5550
5630
  };
5551
- type RouteActivationInput = RouteActivation | readonly RouteActivation[];
5552
5631
  type RouteDescriptor =
5553
5632
  | {
5554
5633
  type: 'command';
5555
- path: readonly string[];
5634
+ path: string[];
5556
5635
  name: string;
5557
- aliases?: readonly string[];
5636
+ aliases?: string[];
5558
5637
  meta?: RouteMeta;
5559
5638
  }
5560
5639
  | {
5561
5640
  type: 'rawPattern';
5562
- path: readonly string[];
5641
+ path: string[];
5563
5642
  meta?: RouteMeta;
5564
5643
  };
5565
- type RouteActivationResolver = (route: RouteDescriptor, session: Session) => RouteActivationInput | undefined;
5644
+ type RouteActivationResolver = (route: RouteDescriptor, session: Session) => RouteActivation[];
5566
5645
  declare const defaultRouteActivationResolver: RouteActivationResolver;
5567
5646
  type RouteEntry =
5568
5647
  | {
@@ -5642,118 +5721,6 @@ declare class Router {
5642
5721
  private capturePattern;
5643
5722
  }
5644
5723
  //#endregion
5645
- //#region src/core/activation.d.ts
5646
- type ContextRouteActivationByScene = Partial<
5647
- Record<IncomingMessage['message_scene'] | 'default', RouteActivationInput>
5648
- >;
5649
- type ContextRouteActivation = RouteActivationInput | ContextRouteActivationByScene;
5650
- interface ContextRouteActivationMatcher {
5651
- type?: RouteDescriptor['type'] | readonly RouteDescriptor['type'][];
5652
- plugin?: string | readonly string[];
5653
- context?: string | readonly string[];
5654
- tag?: string | readonly string[];
5655
- command?: string | readonly string[];
5656
- path?: readonly string[];
5657
- route?: readonly string[];
5658
- predicate?: (route: RouteDescriptor, session: Session) => boolean;
5659
- }
5660
- interface ContextRouteActivationRule {
5661
- match?: ContextRouteActivationMatcher;
5662
- activation: ContextRouteActivation;
5663
- }
5664
- interface ContextRouteActivationConfig {
5665
- default?: ContextRouteActivation;
5666
- rules?: ContextRouteActivationRule | readonly ContextRouteActivationRule[];
5667
- }
5668
- interface ContextRoutingOptions {
5669
- activation?: ContextRouteActivationConfig;
5670
- activationResolver?: RouteActivationResolver;
5671
- }
5672
- declare function createContextRouteActivationResolver(
5673
- routing: ContextRoutingOptions | undefined,
5674
- fallback?: RouteActivationResolver,
5675
- ): RouteActivationResolver;
5676
- //#endregion
5677
- //#region src/protocol/endpoint.d.ts
5678
- type ApiEndpointName = keyof ApiEndpoints$1;
5679
- type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
5680
- type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
5681
- type RawApiEndpoint<E extends ApiEndpointName> = {
5682
- request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
5683
- response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
5684
- };
5685
- type ApiRequest<E extends ApiEndpointName> = RawApiEndpoint<E>['request'];
5686
- type ApiParams<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
5687
- ? undefined
5688
- : AllOptional<RawApiEndpoint<E>['request']> extends true
5689
- ? RawApiEndpoint<E>['request'] | undefined
5690
- : RawApiEndpoint<E>['request'];
5691
- type ApiResponse<E extends ApiEndpointName> = RawApiEndpoint<E>['response'];
5692
- interface ApiCall<E extends ApiEndpointName = ApiEndpointName> {
5693
- endpoint: E;
5694
- params: ApiParams<E>;
5695
- }
5696
- type ApiNext<E extends ApiEndpointName> =
5697
- undefined extends ApiParams<E>
5698
- ? (params?: ApiParams<E>) => Promise<ApiResponse<E>>
5699
- : (params: ApiParams<E>) => Promise<ApiResponse<E>>;
5700
- type ApiHook<E extends ApiEndpointName> = (
5701
- params: ApiParams<E>,
5702
- next: ApiNext<E>,
5703
- call: ApiCall<E>,
5704
- ) => ApiResponse<E> | Promise<ApiResponse<E>>;
5705
- type AnyApiCall = { [E in ApiEndpointName]: ApiCall<E> }[ApiEndpointName];
5706
- type AnyApiNext = (params?: unknown) => Promise<unknown>;
5707
- type AnyApiHook = (call: AnyApiCall, next: AnyApiNext) => unknown | Promise<unknown>;
5708
- type ApiEndpointFunction<E extends ApiEndpointName> = RawApiEndpoint<E>['request'] extends null
5709
- ? () => Promise<ApiResponse<E>>
5710
- : AllOptional<RawApiEndpoint<E>['request']> extends true
5711
- ? (params?: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>
5712
- : (params: RawApiEndpoint<E>['request']) => Promise<ApiResponse<E>>;
5713
- type ApiEndpoints = { [E in ApiEndpointName]: ApiEndpointFunction<E> };
5714
- type EventMap = {
5715
- [K in Event['event_type']]: Extract<
5716
- Event,
5717
- {
5718
- event_type: K;
5719
- }
5720
- >;
5721
- };
5722
- //#endregion
5723
- //#region src/protocol/client.d.ts
5724
- interface MilkyClient extends ApiEndpoints {}
5725
- declare class MilkyClientBase {
5726
- readonly baseUrl: string;
5727
- readonly wsBaseUrl: string;
5728
- private readonly accessToken?;
5729
- private readonly baseHeaders;
5730
- constructor(
5731
- baseUrl: string | URL,
5732
- options?: {
5733
- accessToken?: string;
5734
- },
5735
- );
5736
- callApi(endpoint: string, params?: unknown): Promise<unknown>;
5737
- }
5738
- declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
5739
- //#endregion
5740
- //#region src/protocol/event.d.ts
5741
- interface MilkyEventSubscription {
5742
- closed: Promise<void>;
5743
- stop(): void | Promise<void>;
5744
- }
5745
- interface MilkyEventSource {
5746
- readonly name?: string;
5747
- start(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
5748
- }
5749
- interface MilkyWebSocketEventSourceOptions {
5750
- accessToken?: string;
5751
- }
5752
- declare function createMilkyWebSocketEventSource(
5753
- baseUrl: string | URL,
5754
- options?: MilkyWebSocketEventSourceOptions,
5755
- ): MilkyEventSource;
5756
- //#endregion
5757
5724
  //#region src/core/filter.d.ts
5758
5725
  type Filter = { [K in keyof EventMap]?: (event: EventMap[K]) => boolean };
5759
5726
  declare namespace filter {
@@ -5836,7 +5803,9 @@ interface ContextOptions {
5836
5803
  maxDelayMs?: number;
5837
5804
  };
5838
5805
  logHandler?: LogHandler;
5839
- routing?: ContextRoutingOptions;
5806
+ routing?: {
5807
+ activationResolver?: RouteActivationResolver;
5808
+ };
5840
5809
  }
5841
5810
  interface ContextUrlOptions {
5842
5811
  accessToken?: string;
@@ -5940,12 +5909,6 @@ export {
5940
5909
  CommandBuilder,
5941
5910
  Context,
5942
5911
  ContextOptions,
5943
- ContextRouteActivation,
5944
- ContextRouteActivationByScene,
5945
- ContextRouteActivationConfig,
5946
- ContextRouteActivationMatcher,
5947
- ContextRouteActivationRule,
5948
- ContextRoutingOptions,
5949
5912
  ContextUrlOptions,
5950
5913
  Disposable,
5951
5914
  EventMap,
@@ -5967,7 +5930,6 @@ export {
5967
5930
  Plugin,
5968
5931
  RawPattern,
5969
5932
  RouteActivation,
5970
- RouteActivationInput,
5971
5933
  RouteActivationResolver,
5972
5934
  RouteBranch,
5973
5935
  RouteDescriptor,
@@ -5981,7 +5943,6 @@ export {
5981
5943
  SessionReplyOptions,
5982
5944
  TypeInstruction,
5983
5945
  combineLogHandlers,
5984
- createContextRouteActivationResolver,
5985
5946
  createMilkyClient,
5986
5947
  createMilkyWebSocketEventSource,
5987
5948
  defaultRouteActivationResolver,
package/dist/index.mjs CHANGED
@@ -1,4 +1,210 @@
1
1
  import mitt from "mitt";
2
+ //#region src/protocol/client.ts
3
+ var MilkyClientBase = class {
4
+ baseUrl;
5
+ wsBaseUrl;
6
+ accessToken;
7
+ baseHeaders;
8
+ constructor(baseUrl, options) {
9
+ const normalizedBaseUrl = baseUrl.toString();
10
+ this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
11
+ this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
12
+ this.accessToken = options?.accessToken;
13
+ this.baseHeaders = { "Content-Type": "application/json" };
14
+ if (options?.accessToken) this.baseHeaders.Authorization = `Bearer ${options.accessToken}`;
15
+ }
16
+ async callApi(endpoint, params) {
17
+ const response = await fetch(`${this.baseUrl}/api/${endpoint}`, {
18
+ method: "POST",
19
+ body: JSON.stringify(params ?? {}),
20
+ headers: this.baseHeaders
21
+ });
22
+ if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
23
+ const json = await response.json();
24
+ if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
25
+ return json.data;
26
+ }
27
+ };
28
+ function createMilkyClient(...params) {
29
+ const base = new MilkyClientBase(...params);
30
+ return new Proxy(base, { get(target, endpoint) {
31
+ if (typeof endpoint === "string" && endpoint.includes("_")) return (params) => target.callApi(endpoint, params);
32
+ else return target[endpoint];
33
+ } });
34
+ }
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
76
+ //#region src/protocol/segment.ts
77
+ function msg(strings, ...values) {
78
+ let buffer = "";
79
+ const segments = [];
80
+ for (let i = 0; i < strings.length; i++) {
81
+ buffer += strings[i];
82
+ if (i < values.length) {
83
+ const value = values[i];
84
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") buffer += value.toString();
85
+ else {
86
+ if (buffer) {
87
+ segments.push({
88
+ type: "text",
89
+ data: { text: buffer }
90
+ });
91
+ buffer = "";
92
+ }
93
+ segments.push(value);
94
+ }
95
+ }
96
+ }
97
+ if (buffer) segments.push({
98
+ type: "text",
99
+ data: { text: buffer }
100
+ });
101
+ return trimBoundaryTextSegments(segments);
102
+ }
103
+ function isTextSegment(segment) {
104
+ return segment.type === "text";
105
+ }
106
+ function withText(segment, text) {
107
+ return {
108
+ ...segment,
109
+ data: {
110
+ ...segment.data,
111
+ text
112
+ }
113
+ };
114
+ }
115
+ function trimBoundaryTextSegments(segments) {
116
+ const result = [...segments];
117
+ const first = result[0];
118
+ if (first && isTextSegment(first)) {
119
+ const text = first.data.text.trimStart();
120
+ if (text) result[0] = withText(first, text);
121
+ else result.shift();
122
+ }
123
+ const lastIndex = result.length - 1;
124
+ const last = result[lastIndex];
125
+ if (last && isTextSegment(last)) {
126
+ const text = last.data.text.trimEnd();
127
+ if (text) result[lastIndex] = withText(last, text);
128
+ else result.pop();
129
+ }
130
+ return result;
131
+ }
132
+ let seg;
133
+ (function(_seg) {
134
+ function mention(userId) {
135
+ return {
136
+ type: "mention",
137
+ data: { user_id: userId }
138
+ };
139
+ }
140
+ _seg.mention = mention;
141
+ function mentionAll() {
142
+ return {
143
+ type: "mention_all",
144
+ data: {}
145
+ };
146
+ }
147
+ _seg.mentionAll = mentionAll;
148
+ function face(faceId, options) {
149
+ return {
150
+ type: "face",
151
+ data: {
152
+ face_id: faceId.toString(),
153
+ is_large: options?.isLarge ?? false
154
+ }
155
+ };
156
+ }
157
+ _seg.face = face;
158
+ function reply(messageSeq) {
159
+ return {
160
+ type: "reply",
161
+ data: { message_seq: messageSeq }
162
+ };
163
+ }
164
+ _seg.reply = reply;
165
+ function image(uri, options) {
166
+ return {
167
+ type: "image",
168
+ data: {
169
+ uri,
170
+ sub_type: options?.subType,
171
+ summary: options?.summary
172
+ }
173
+ };
174
+ }
175
+ _seg.image = image;
176
+ function record(uri) {
177
+ return {
178
+ type: "record",
179
+ data: { uri }
180
+ };
181
+ }
182
+ _seg.record = record;
183
+ function video(uri, options) {
184
+ return {
185
+ type: "video",
186
+ data: {
187
+ uri,
188
+ thumb_uri: options?.thumbUri
189
+ }
190
+ };
191
+ }
192
+ _seg.video = video;
193
+ function forward(messages, options) {
194
+ return {
195
+ type: "forward",
196
+ data: {
197
+ messages,
198
+ title: options?.title,
199
+ preview: options?.preview,
200
+ summary: options?.summary,
201
+ prompt: options?.prompt
202
+ }
203
+ };
204
+ }
205
+ _seg.forward = forward;
206
+ })(seg || (seg = {}));
207
+ //#endregion
2
208
  //#region src/routing/command.ts
3
209
  var CommandBuilder = class {
4
210
  name;
@@ -235,8 +441,8 @@ var Tokenizer = class {
235
441
  };
236
442
  //#endregion
237
443
  //#region src/routing/router.ts
238
- const DEFAULT_ACTIVATIONS = [{ type: "direct" }];
239
- const defaultRouteActivationResolver = () => DEFAULT_ACTIVATIONS;
444
+ const defaultActivations = [{ type: "direct" }];
445
+ const defaultRouteActivationResolver = () => defaultActivations;
240
446
  var Router = class Router {
241
447
  entries = [];
242
448
  groups = /* @__PURE__ */ new Map();
@@ -315,11 +521,10 @@ var Router = class Router {
315
521
  match(session, message) {
316
522
  for (const branch of this.branchesFrom(session, [], { includeHidden: true })) {
317
523
  const literalActivationIndex = branch.type === "rawPattern" ? Object.values(branch.rawPattern.pattern).findIndex((parameter) => parameter.capturer.typeInstruction.type === "literal") : -1;
318
- let activationInputs = DEFAULT_ACTIVATIONS;
524
+ let activationInputs = defaultActivations;
319
525
  if (branch.type === "command" || literalActivationIndex !== -1 || branch.path.length > 0) {
320
526
  const descriptor = this.describeBranch(branch);
321
- const activations = this.activationResolver(descriptor, session) ?? DEFAULT_ACTIVATIONS;
322
- activationInputs = Array.isArray(activations) ? activations : [activations];
527
+ activationInputs = this.activationResolver(descriptor, session) ?? defaultActivations;
323
528
  }
324
529
  for (const activation of activationInputs) {
325
530
  const tokenizer = new Tokenizer(message.segments);
@@ -498,275 +703,6 @@ var Router = class Router {
498
703
  }
499
704
  };
500
705
  //#endregion
501
- //#region src/core/activation.ts
502
- function createContextRouteActivationResolver(routing, fallback = defaultRouteActivationResolver) {
503
- if (!routing) return fallback;
504
- if (routing.activation && routing.activationResolver) throw new Error("Context routing cannot specify both activation and activationResolver.");
505
- if (routing.activationResolver) return routing.activationResolver;
506
- if (routing.activation) return compileContextRouteActivationConfig(routing.activation, fallback);
507
- return fallback;
508
- }
509
- function compileContextRouteActivationConfig(config, fallback) {
510
- return (route, session) => {
511
- const rules = config.rules === void 0 ? [] : Array.isArray(config.rules) ? config.rules : [config.rules];
512
- for (const rule of rules) {
513
- if (!matchesContextRouteActivationRule(route, session, rule)) continue;
514
- const activation = resolveContextRouteActivation(rule.activation, session.raw.message_scene);
515
- if (activation !== void 0) return activation;
516
- }
517
- if (config.default !== void 0) {
518
- const activation = resolveContextRouteActivation(config.default, session.raw.message_scene);
519
- if (activation !== void 0) return activation;
520
- }
521
- return fallback(route, session);
522
- };
523
- }
524
- function resolveContextRouteActivation(activation, scene) {
525
- if (Array.isArray(activation) || "type" in activation) return activation;
526
- const activationByScene = activation;
527
- return activationByScene[scene] ?? activationByScene.default;
528
- }
529
- function matchesContextRouteActivationRule(route, session, rule) {
530
- const match = rule.match;
531
- if (!match) return true;
532
- if (!matchesStringSelector(match.type, route.type)) return false;
533
- if (!matchesStringSelector(match.plugin, routeMetaString(route, "plugin"))) return false;
534
- if (!matchesStringSelector(match.context, routeMetaString(route, "context"))) return false;
535
- if (!matchesTagSelector(match.tag, route.meta?.tags)) return false;
536
- if (match.command !== void 0 && (route.type !== "command" || !matchesStringSelector(match.command, route.name))) return false;
537
- if (match.path !== void 0 && !sameStringArray(match.path, route.path)) return false;
538
- if (match.route !== void 0 && !sameStringArray(match.route, routeSegments(route))) return false;
539
- if (match.predicate && match.predicate(route, session) !== true) return false;
540
- return true;
541
- }
542
- function routeMetaString(route, key) {
543
- const value = route.meta?.[key];
544
- return typeof value === "string" ? value : void 0;
545
- }
546
- function matchesStringSelector(selector, value) {
547
- if (selector === void 0) return true;
548
- if (value === void 0) return false;
549
- return typeof selector === "string" ? selector === value : selector.includes(value);
550
- }
551
- function matchesTagSelector(selector, tags) {
552
- if (selector === void 0) return true;
553
- if (!tags) return false;
554
- return typeof selector === "string" ? tags.includes(selector) : selector.some((tag) => tags.includes(tag));
555
- }
556
- function routeSegments(route) {
557
- if (route.type === "command") return [...route.path, route.name];
558
- return route.path;
559
- }
560
- function sameStringArray(left, right) {
561
- return left.length === right.length && left.every((value, index) => value === right[index]);
562
- }
563
- //#endregion
564
- //#region src/protocol/client.ts
565
- var MilkyClientBase = class {
566
- baseUrl;
567
- wsBaseUrl;
568
- accessToken;
569
- baseHeaders;
570
- constructor(baseUrl, options) {
571
- const normalizedBaseUrl = baseUrl.toString();
572
- this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
573
- this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
574
- this.accessToken = options?.accessToken;
575
- this.baseHeaders = { "Content-Type": "application/json" };
576
- if (options?.accessToken) this.baseHeaders.Authorization = `Bearer ${options.accessToken}`;
577
- }
578
- async callApi(endpoint, params) {
579
- const response = await fetch(`${this.baseUrl}/api/${endpoint}`, {
580
- method: "POST",
581
- body: JSON.stringify(params ?? {}),
582
- headers: this.baseHeaders
583
- });
584
- if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
585
- const json = await response.json();
586
- if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
587
- return json.data;
588
- }
589
- };
590
- function createMilkyClient(...params) {
591
- const base = new MilkyClientBase(...params);
592
- return new Proxy(base, { get(target, endpoint) {
593
- if (typeof endpoint === "string" && endpoint.includes("_")) return (params) => target.callApi(endpoint, params);
594
- else return target[endpoint];
595
- } });
596
- }
597
- //#endregion
598
- //#region src/protocol/event.ts
599
- function createMilkyWebSocketEventSource(baseUrl, options) {
600
- const normalizedBaseUrl = baseUrl.toString();
601
- const wsBaseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1).replace(/^http/, "ws") : normalizedBaseUrl.replace(/^http/, "ws");
602
- return {
603
- name: "websocket",
604
- async start(onEvent) {
605
- const ws = new WebSocket(`${wsBaseUrl}/event${options?.accessToken ? `?access_token=${options.accessToken}` : ""}`);
606
- let closeSubscription = () => {};
607
- const closed = new Promise((resolve, reject) => {
608
- closeSubscription = (error) => {
609
- if (error) reject(error);
610
- else resolve();
611
- };
612
- });
613
- ws.addEventListener("message", async (event) => {
614
- try {
615
- if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
616
- await onEvent(JSON.parse(event.data));
617
- } catch (error) {
618
- closeSubscription(error);
619
- ws.close();
620
- }
621
- });
622
- await new Promise((resolve, reject) => {
623
- ws.addEventListener("open", () => resolve(), { once: true });
624
- ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
625
- });
626
- ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
627
- ws.addEventListener("close", () => closeSubscription(), { once: true });
628
- return {
629
- closed,
630
- stop() {
631
- ws.close();
632
- }
633
- };
634
- }
635
- };
636
- }
637
- //#endregion
638
- //#region src/protocol/segment.ts
639
- function msg(strings, ...values) {
640
- let buffer = "";
641
- const segments = [];
642
- for (let i = 0; i < strings.length; i++) {
643
- buffer += strings[i];
644
- if (i < values.length) {
645
- const value = values[i];
646
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") buffer += value.toString();
647
- else {
648
- if (buffer) {
649
- segments.push({
650
- type: "text",
651
- data: { text: buffer }
652
- });
653
- buffer = "";
654
- }
655
- segments.push(value);
656
- }
657
- }
658
- }
659
- if (buffer) segments.push({
660
- type: "text",
661
- data: { text: buffer }
662
- });
663
- return trimBoundaryTextSegments(segments);
664
- }
665
- function isTextSegment(segment) {
666
- return segment.type === "text";
667
- }
668
- function withText(segment, text) {
669
- return {
670
- ...segment,
671
- data: {
672
- ...segment.data,
673
- text
674
- }
675
- };
676
- }
677
- function trimBoundaryTextSegments(segments) {
678
- const result = [...segments];
679
- const first = result[0];
680
- if (first && isTextSegment(first)) {
681
- const text = first.data.text.trimStart();
682
- if (text) result[0] = withText(first, text);
683
- else result.shift();
684
- }
685
- const lastIndex = result.length - 1;
686
- const last = result[lastIndex];
687
- if (last && isTextSegment(last)) {
688
- const text = last.data.text.trimEnd();
689
- if (text) result[lastIndex] = withText(last, text);
690
- else result.pop();
691
- }
692
- return result;
693
- }
694
- let seg;
695
- (function(_seg) {
696
- function mention(userId) {
697
- return {
698
- type: "mention",
699
- data: { user_id: userId }
700
- };
701
- }
702
- _seg.mention = mention;
703
- function mentionAll() {
704
- return {
705
- type: "mention_all",
706
- data: {}
707
- };
708
- }
709
- _seg.mentionAll = mentionAll;
710
- function face(faceId, options) {
711
- return {
712
- type: "face",
713
- data: {
714
- face_id: faceId.toString(),
715
- is_large: options?.isLarge ?? false
716
- }
717
- };
718
- }
719
- _seg.face = face;
720
- function reply(messageSeq) {
721
- return {
722
- type: "reply",
723
- data: { message_seq: messageSeq }
724
- };
725
- }
726
- _seg.reply = reply;
727
- function image(uri, options) {
728
- return {
729
- type: "image",
730
- data: {
731
- uri,
732
- sub_type: options?.subType,
733
- summary: options?.summary
734
- }
735
- };
736
- }
737
- _seg.image = image;
738
- function record(uri) {
739
- return {
740
- type: "record",
741
- data: { uri }
742
- };
743
- }
744
- _seg.record = record;
745
- function video(uri, options) {
746
- return {
747
- type: "video",
748
- data: {
749
- uri,
750
- thumb_uri: options?.thumbUri
751
- }
752
- };
753
- }
754
- _seg.video = video;
755
- function forward(messages, options) {
756
- return {
757
- type: "forward",
758
- data: {
759
- messages,
760
- title: options?.title,
761
- preview: options?.preview,
762
- summary: options?.summary,
763
- prompt: options?.prompt
764
- }
765
- };
766
- }
767
- _seg.forward = forward;
768
- })(seg || (seg = {}));
769
- //#endregion
770
706
  //#region src/core/logging.ts
771
707
  var Logger = class {
772
708
  logHandler;
@@ -1355,7 +1291,7 @@ var Context = class Context {
1355
1291
  this.logHandler = options?.logHandler ?? parent?.logHandler;
1356
1292
  this.name = name ?? "root";
1357
1293
  this.logger = new Logger((message) => this.logHandler?.(message), `context:${this.name}`);
1358
- this.routeActivationResolver = createContextRouteActivationResolver(options?.routing, parent?.routeActivationResolver);
1294
+ this.routeActivationResolver = options?.routing?.activationResolver ?? parent?.routeActivationResolver ?? defaultRouteActivationResolver;
1359
1295
  this.router.setActivationResolver(this.routeActivationResolver);
1360
1296
  this.parent = parent;
1361
1297
  this.filter = filter;
@@ -1781,4 +1717,4 @@ let param;
1781
1717
  _param.segment = segment;
1782
1718
  })(param || (param = {}));
1783
1719
  //#endregion
1784
- export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createContextRouteActivationResolver, createMilkyClient, createMilkyWebSocketEventSource, defaultRouteActivationResolver, definePlugin, filter, implementsESNextDisposable, isDisposable, mergeRouteMeta, milkyPackageVersion, milkyVersion, msg, param, seg };
1720
+ export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createMilkyClient, createMilkyWebSocketEventSource, defaultRouteActivationResolver, definePlugin, filter, implementsESNextDisposable, isDisposable, mergeRouteMeta, 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.13.3",
4
+ "version": "0.14.0",
5
5
  "description": "Milky Bot framework",
6
6
  "files": [
7
7
  "dist"