@fraqjs/fraq 0.11.0 → 0.12.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 +1 -1
- package/dist/index.d.mts +155 -61
- package/dist/index.mjs +445 -253
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -5377,62 +5377,6 @@ interface ApiEndpoints$1 {
|
|
|
5377
5377
|
};
|
|
5378
5378
|
}
|
|
5379
5379
|
//#endregion
|
|
5380
|
-
//#region src/protocol/endpoint.d.ts
|
|
5381
|
-
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
|
|
5382
|
-
type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
|
|
5383
|
-
type RawApiEndpoint<E extends keyof ApiEndpoints$1> = {
|
|
5384
|
-
request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
|
|
5385
|
-
response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
|
|
5386
|
-
};
|
|
5387
|
-
type ApiEndpointFunction<E extends keyof ApiEndpoints$1> = RawApiEndpoint<E>['request'] extends null
|
|
5388
|
-
? () => Promise<RawApiEndpoint<E>['response']>
|
|
5389
|
-
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5390
|
-
? (params?: RawApiEndpoint<E>['request']) => Promise<RawApiEndpoint<E>['response']>
|
|
5391
|
-
: (params: RawApiEndpoint<E>['request']) => Promise<RawApiEndpoint<E>['response']>;
|
|
5392
|
-
type ApiEndpoints = { [E in keyof ApiEndpoints$1]: ApiEndpointFunction<E> };
|
|
5393
|
-
type EventMap = {
|
|
5394
|
-
[K in Event['event_type']]: Extract<
|
|
5395
|
-
Event,
|
|
5396
|
-
{
|
|
5397
|
-
event_type: K;
|
|
5398
|
-
}
|
|
5399
|
-
>;
|
|
5400
|
-
};
|
|
5401
|
-
//#endregion
|
|
5402
|
-
//#region src/protocol/client.d.ts
|
|
5403
|
-
interface MilkyClient extends ApiEndpoints {}
|
|
5404
|
-
declare class MilkyClientBase {
|
|
5405
|
-
readonly baseUrl: string;
|
|
5406
|
-
readonly wsBaseUrl: string;
|
|
5407
|
-
private readonly accessToken?;
|
|
5408
|
-
private readonly baseHeaders;
|
|
5409
|
-
constructor(
|
|
5410
|
-
baseUrl: string | URL,
|
|
5411
|
-
options?: {
|
|
5412
|
-
accessToken?: string;
|
|
5413
|
-
},
|
|
5414
|
-
);
|
|
5415
|
-
callApi(endpoint: string, params?: unknown): Promise<unknown>;
|
|
5416
|
-
}
|
|
5417
|
-
declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
|
|
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
|
|
5436
5380
|
//#region src/routing/tokenizer.d.ts
|
|
5437
5381
|
type Token =
|
|
5438
5382
|
| string
|
|
@@ -5460,6 +5404,8 @@ declare class Tokenizer {
|
|
|
5460
5404
|
greedy(): string;
|
|
5461
5405
|
isCatchAllAvailable(): boolean;
|
|
5462
5406
|
catchAll(): IncomingSegment[];
|
|
5407
|
+
consumeMention(userId: number): boolean;
|
|
5408
|
+
consumeTextPrefix(prefix: string): boolean;
|
|
5463
5409
|
private findNextPosition;
|
|
5464
5410
|
private findTextTokenStart;
|
|
5465
5411
|
private readTextToken;
|
|
@@ -5526,6 +5472,9 @@ declare namespace param {
|
|
|
5526
5472
|
type Pattern = Record<string, Parameter<any>>;
|
|
5527
5473
|
type ParamsOf<P extends Pattern> = { [K in keyof P]: P[K] extends Parameter<infer T> ? T : never };
|
|
5528
5474
|
type Executor<P extends Pattern> = (session: Session, params: ParamsOf<P>) => void | Promise<void>;
|
|
5475
|
+
type RouteMeta = Record<string, unknown> & {
|
|
5476
|
+
tags?: readonly string[];
|
|
5477
|
+
};
|
|
5529
5478
|
interface Command<P extends Pattern> {
|
|
5530
5479
|
name: string;
|
|
5531
5480
|
pattern: P;
|
|
@@ -5533,10 +5482,12 @@ interface Command<P extends Pattern> {
|
|
|
5533
5482
|
description?: string;
|
|
5534
5483
|
aliases?: string[];
|
|
5535
5484
|
hidden?: boolean;
|
|
5485
|
+
meta?: RouteMeta;
|
|
5536
5486
|
}
|
|
5537
5487
|
interface RawPattern<P extends Pattern> {
|
|
5538
5488
|
pattern: P;
|
|
5539
5489
|
execute: Executor<P>;
|
|
5490
|
+
meta?: RouteMeta;
|
|
5540
5491
|
}
|
|
5541
5492
|
interface SessionReplyOptions {
|
|
5542
5493
|
withQuote?: boolean;
|
|
@@ -5561,6 +5512,7 @@ declare class CommandBuilder<P extends Pattern = {}, S = Command<P>> {
|
|
|
5561
5512
|
private description?;
|
|
5562
5513
|
private aliases?;
|
|
5563
5514
|
private hidden?;
|
|
5515
|
+
private routeMeta?;
|
|
5564
5516
|
constructor(name: string, sink?: (command: Command<P>) => S);
|
|
5565
5517
|
arg<K extends string, T>(
|
|
5566
5518
|
key: K,
|
|
@@ -5576,11 +5528,40 @@ declare class CommandBuilder<P extends Pattern = {}, S = Command<P>> {
|
|
|
5576
5528
|
describe(description: string): CommandBuilder<P, S>;
|
|
5577
5529
|
alias(...aliases: string[]): CommandBuilder<P, S>;
|
|
5578
5530
|
hide(): CommandBuilder<P, S>;
|
|
5531
|
+
meta(meta: RouteMeta): CommandBuilder<P, S>;
|
|
5532
|
+
tag(...tags: string[]): CommandBuilder<P, S>;
|
|
5579
5533
|
execute(executor: Executor<P>): S;
|
|
5580
5534
|
}
|
|
5535
|
+
declare function mergeRouteMeta(left?: RouteMeta, right?: RouteMeta): RouteMeta | undefined;
|
|
5581
5536
|
//#endregion
|
|
5582
5537
|
//#region src/routing/router.d.ts
|
|
5583
5538
|
type SessionPredicate = (session: Session) => boolean;
|
|
5539
|
+
type RouteActivation =
|
|
5540
|
+
| {
|
|
5541
|
+
type: 'direct';
|
|
5542
|
+
}
|
|
5543
|
+
| {
|
|
5544
|
+
type: 'mention';
|
|
5545
|
+
}
|
|
5546
|
+
| {
|
|
5547
|
+
type: 'prefix';
|
|
5548
|
+
prefix: string;
|
|
5549
|
+
};
|
|
5550
|
+
type RouteDescriptor =
|
|
5551
|
+
| {
|
|
5552
|
+
type: 'command';
|
|
5553
|
+
path: readonly string[];
|
|
5554
|
+
name: string;
|
|
5555
|
+
aliases?: readonly string[];
|
|
5556
|
+
meta?: RouteMeta;
|
|
5557
|
+
}
|
|
5558
|
+
| {
|
|
5559
|
+
type: 'rawPattern';
|
|
5560
|
+
path: readonly string[];
|
|
5561
|
+
meta?: RouteMeta;
|
|
5562
|
+
};
|
|
5563
|
+
type RouteActivationResolver = (route: RouteDescriptor, session: Session) => readonly RouteActivation[] | undefined;
|
|
5564
|
+
declare const defaultRouteActivationResolver: RouteActivationResolver;
|
|
5584
5565
|
type RouteEntry =
|
|
5585
5566
|
| {
|
|
5586
5567
|
type: 'command';
|
|
@@ -5605,11 +5586,13 @@ type RouteBranch =
|
|
|
5605
5586
|
type: 'command';
|
|
5606
5587
|
path: string[];
|
|
5607
5588
|
command: Command<Pattern>;
|
|
5589
|
+
meta?: RouteMeta;
|
|
5608
5590
|
}
|
|
5609
5591
|
| {
|
|
5610
5592
|
type: 'rawPattern';
|
|
5611
5593
|
path: string[];
|
|
5612
5594
|
rawPattern: RawPattern<Pattern>;
|
|
5595
|
+
meta?: RouteMeta;
|
|
5613
5596
|
};
|
|
5614
5597
|
type RouteMatchResult =
|
|
5615
5598
|
| {
|
|
@@ -5617,16 +5600,22 @@ type RouteMatchResult =
|
|
|
5617
5600
|
path: string[];
|
|
5618
5601
|
command: Command<Pattern>;
|
|
5619
5602
|
params: any;
|
|
5603
|
+
activation?: RouteActivation;
|
|
5620
5604
|
}
|
|
5621
5605
|
| {
|
|
5622
5606
|
type: 'rawPattern';
|
|
5623
5607
|
path: string[];
|
|
5624
5608
|
rawPattern: RawPattern<Pattern>;
|
|
5625
5609
|
params: any;
|
|
5610
|
+
activation?: RouteActivation;
|
|
5626
5611
|
};
|
|
5627
5612
|
declare class Router {
|
|
5628
|
-
private
|
|
5629
|
-
private
|
|
5613
|
+
private entries;
|
|
5614
|
+
private groups;
|
|
5615
|
+
private activationResolver;
|
|
5616
|
+
private scopeMeta?;
|
|
5617
|
+
setActivationResolver(resolver: RouteActivationResolver): this;
|
|
5618
|
+
withMeta(meta: RouteMeta): Router;
|
|
5630
5619
|
command(name: string): CommandBuilder;
|
|
5631
5620
|
command<P extends Pattern>(command: Command<P>): this;
|
|
5632
5621
|
rawPattern(): CommandBuilder<{}, RawPattern<{}>>;
|
|
@@ -5638,17 +5627,107 @@ declare class Router {
|
|
|
5638
5627
|
branches(session: Session): RouteBranch[];
|
|
5639
5628
|
match(session: Session, message: IncomingMessage): RouteMatchResult | undefined;
|
|
5640
5629
|
dispatch(session: Session, message: IncomingMessage): Promise<boolean>;
|
|
5641
|
-
private
|
|
5642
|
-
private
|
|
5630
|
+
private describeBranch;
|
|
5631
|
+
private consumeActivation;
|
|
5632
|
+
private matchBranch;
|
|
5633
|
+
private matchPath;
|
|
5643
5634
|
private matchCommand;
|
|
5644
|
-
private matchGroup;
|
|
5645
5635
|
private matchRawPattern;
|
|
5646
5636
|
private branchesFrom;
|
|
5637
|
+
private applyScopeMeta;
|
|
5647
5638
|
private resolveAliasConflicts;
|
|
5648
5639
|
private validatePattern;
|
|
5649
5640
|
private capturePattern;
|
|
5650
5641
|
}
|
|
5651
5642
|
//#endregion
|
|
5643
|
+
//#region src/core/activation.d.ts
|
|
5644
|
+
type ContextRouteActivationByScene = Partial<
|
|
5645
|
+
Record<IncomingMessage['message_scene'] | 'default', readonly RouteActivation[]>
|
|
5646
|
+
>;
|
|
5647
|
+
type ContextRouteActivation = readonly RouteActivation[] | ContextRouteActivationByScene;
|
|
5648
|
+
interface ContextRouteActivationMatcher {
|
|
5649
|
+
type?: RouteDescriptor['type'] | readonly RouteDescriptor['type'][];
|
|
5650
|
+
plugin?: string | readonly string[];
|
|
5651
|
+
context?: string | readonly string[];
|
|
5652
|
+
tag?: string | readonly string[];
|
|
5653
|
+
command?: string | readonly string[];
|
|
5654
|
+
path?: readonly string[];
|
|
5655
|
+
route?: readonly string[];
|
|
5656
|
+
predicate?: (route: RouteDescriptor, session: Session) => boolean;
|
|
5657
|
+
}
|
|
5658
|
+
interface ContextRouteActivationRule {
|
|
5659
|
+
match?: ContextRouteActivationMatcher;
|
|
5660
|
+
activation: ContextRouteActivation;
|
|
5661
|
+
}
|
|
5662
|
+
interface ContextRouteActivationConfig {
|
|
5663
|
+
default?: ContextRouteActivation;
|
|
5664
|
+
rules?: ContextRouteActivationRule | readonly ContextRouteActivationRule[];
|
|
5665
|
+
}
|
|
5666
|
+
interface ContextRoutingOptions {
|
|
5667
|
+
activation?: ContextRouteActivationConfig;
|
|
5668
|
+
activationResolver?: RouteActivationResolver;
|
|
5669
|
+
}
|
|
5670
|
+
declare function createContextRouteActivationResolver(
|
|
5671
|
+
routing: ContextRoutingOptions | undefined,
|
|
5672
|
+
fallback?: RouteActivationResolver,
|
|
5673
|
+
): RouteActivationResolver;
|
|
5674
|
+
//#endregion
|
|
5675
|
+
//#region src/protocol/endpoint.d.ts
|
|
5676
|
+
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
|
|
5677
|
+
type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
|
|
5678
|
+
type RawApiEndpoint<E extends keyof ApiEndpoints$1> = {
|
|
5679
|
+
request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
|
|
5680
|
+
response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
|
|
5681
|
+
};
|
|
5682
|
+
type ApiEndpointFunction<E extends keyof ApiEndpoints$1> = RawApiEndpoint<E>['request'] extends null
|
|
5683
|
+
? () => Promise<RawApiEndpoint<E>['response']>
|
|
5684
|
+
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5685
|
+
? (params?: RawApiEndpoint<E>['request']) => Promise<RawApiEndpoint<E>['response']>
|
|
5686
|
+
: (params: RawApiEndpoint<E>['request']) => Promise<RawApiEndpoint<E>['response']>;
|
|
5687
|
+
type ApiEndpoints = { [E in keyof ApiEndpoints$1]: ApiEndpointFunction<E> };
|
|
5688
|
+
type EventMap = {
|
|
5689
|
+
[K in Event['event_type']]: Extract<
|
|
5690
|
+
Event,
|
|
5691
|
+
{
|
|
5692
|
+
event_type: K;
|
|
5693
|
+
}
|
|
5694
|
+
>;
|
|
5695
|
+
};
|
|
5696
|
+
//#endregion
|
|
5697
|
+
//#region src/protocol/client.d.ts
|
|
5698
|
+
interface MilkyClient extends ApiEndpoints {}
|
|
5699
|
+
declare class MilkyClientBase {
|
|
5700
|
+
readonly baseUrl: string;
|
|
5701
|
+
readonly wsBaseUrl: string;
|
|
5702
|
+
private readonly accessToken?;
|
|
5703
|
+
private readonly baseHeaders;
|
|
5704
|
+
constructor(
|
|
5705
|
+
baseUrl: string | URL,
|
|
5706
|
+
options?: {
|
|
5707
|
+
accessToken?: string;
|
|
5708
|
+
},
|
|
5709
|
+
);
|
|
5710
|
+
callApi(endpoint: string, params?: unknown): Promise<unknown>;
|
|
5711
|
+
}
|
|
5712
|
+
declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
|
|
5713
|
+
//#endregion
|
|
5714
|
+
//#region src/protocol/event.d.ts
|
|
5715
|
+
interface MilkyEventSubscription {
|
|
5716
|
+
closed: Promise<void>;
|
|
5717
|
+
stop(): void | Promise<void>;
|
|
5718
|
+
}
|
|
5719
|
+
interface MilkyEventSource {
|
|
5720
|
+
readonly name?: string;
|
|
5721
|
+
start(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
|
|
5722
|
+
}
|
|
5723
|
+
interface MilkyWebSocketEventSourceOptions {
|
|
5724
|
+
accessToken?: string;
|
|
5725
|
+
}
|
|
5726
|
+
declare function createMilkyWebSocketEventSource(
|
|
5727
|
+
baseUrl: string | URL,
|
|
5728
|
+
options?: MilkyWebSocketEventSourceOptions,
|
|
5729
|
+
): MilkyEventSource;
|
|
5730
|
+
//#endregion
|
|
5652
5731
|
//#region src/core/filter.d.ts
|
|
5653
5732
|
type Filter = { [K in keyof EventMap]?: (event: EventMap[K]) => boolean };
|
|
5654
5733
|
declare namespace filter {
|
|
@@ -5731,6 +5810,7 @@ interface ContextOptions {
|
|
|
5731
5810
|
maxDelayMs?: number;
|
|
5732
5811
|
};
|
|
5733
5812
|
logHandler?: LogHandler;
|
|
5813
|
+
routing?: ContextRoutingOptions;
|
|
5734
5814
|
}
|
|
5735
5815
|
interface ContextUrlOptions {
|
|
5736
5816
|
accessToken?: string;
|
|
@@ -5741,6 +5821,7 @@ declare class Context {
|
|
|
5741
5821
|
readonly router: Router;
|
|
5742
5822
|
readonly logger: Logger;
|
|
5743
5823
|
readonly name: string;
|
|
5824
|
+
readonly routeActivationResolver: RouteActivationResolver;
|
|
5744
5825
|
private readonly parent?;
|
|
5745
5826
|
private readonly filter?;
|
|
5746
5827
|
private readonly eventBus;
|
|
@@ -5848,6 +5929,12 @@ export {
|
|
|
5848
5929
|
CommandBuilder,
|
|
5849
5930
|
Context,
|
|
5850
5931
|
ContextOptions,
|
|
5932
|
+
ContextRouteActivation,
|
|
5933
|
+
ContextRouteActivationByScene,
|
|
5934
|
+
ContextRouteActivationConfig,
|
|
5935
|
+
ContextRouteActivationMatcher,
|
|
5936
|
+
ContextRouteActivationRule,
|
|
5937
|
+
ContextRoutingOptions,
|
|
5851
5938
|
ContextUrlOptions,
|
|
5852
5939
|
Disposable,
|
|
5853
5940
|
EventMap,
|
|
@@ -5868,9 +5955,13 @@ export {
|
|
|
5868
5955
|
Pattern,
|
|
5869
5956
|
Plugin,
|
|
5870
5957
|
RawPattern,
|
|
5958
|
+
RouteActivation,
|
|
5959
|
+
RouteActivationResolver,
|
|
5871
5960
|
RouteBranch,
|
|
5961
|
+
RouteDescriptor,
|
|
5872
5962
|
RouteEntry,
|
|
5873
5963
|
RouteMatchResult,
|
|
5964
|
+
RouteMeta,
|
|
5874
5965
|
Router,
|
|
5875
5966
|
ServiceClass,
|
|
5876
5967
|
Session,
|
|
@@ -5878,12 +5969,15 @@ export {
|
|
|
5878
5969
|
SessionReplyOptions,
|
|
5879
5970
|
TypeInstruction,
|
|
5880
5971
|
combineLogHandlers,
|
|
5972
|
+
createContextRouteActivationResolver,
|
|
5881
5973
|
createMilkyClient,
|
|
5882
5974
|
createMilkyWebSocketEventSource,
|
|
5975
|
+
defaultRouteActivationResolver,
|
|
5883
5976
|
definePlugin,
|
|
5884
5977
|
filter,
|
|
5885
5978
|
implementsESNextDisposable,
|
|
5886
5979
|
isDisposable,
|
|
5980
|
+
mergeRouteMeta,
|
|
5887
5981
|
type types_d_exports as milky,
|
|
5888
5982
|
milkyPackageVersion,
|
|
5889
5983
|
milkyVersion,
|
package/dist/index.mjs
CHANGED
|
@@ -1,210 +1,4 @@
|
|
|
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
|
|
208
2
|
//#region src/routing/command.ts
|
|
209
3
|
var CommandBuilder = class {
|
|
210
4
|
name;
|
|
@@ -214,6 +8,7 @@ var CommandBuilder = class {
|
|
|
214
8
|
description;
|
|
215
9
|
aliases;
|
|
216
10
|
hidden;
|
|
11
|
+
routeMeta;
|
|
217
12
|
constructor(name, sink = (command) => command) {
|
|
218
13
|
this.name = name;
|
|
219
14
|
this.sink = sink;
|
|
@@ -235,6 +30,14 @@ var CommandBuilder = class {
|
|
|
235
30
|
this.hidden = true;
|
|
236
31
|
return this;
|
|
237
32
|
}
|
|
33
|
+
meta(meta) {
|
|
34
|
+
this.routeMeta = mergeRouteMeta(this.routeMeta, meta);
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
tag(...tags) {
|
|
38
|
+
this.routeMeta = mergeRouteMeta(this.routeMeta, { tags });
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
238
41
|
execute(executor) {
|
|
239
42
|
this.executor = executor;
|
|
240
43
|
const command = {
|
|
@@ -245,9 +48,31 @@ var CommandBuilder = class {
|
|
|
245
48
|
if (this.description !== void 0) command.description = this.description;
|
|
246
49
|
if (this.aliases !== void 0) command.aliases = this.aliases;
|
|
247
50
|
if (this.hidden !== void 0) command.hidden = this.hidden;
|
|
51
|
+
if (this.routeMeta !== void 0) command.meta = this.routeMeta;
|
|
248
52
|
return this.sink(command);
|
|
249
53
|
}
|
|
250
54
|
};
|
|
55
|
+
function mergeRouteMeta(left, right) {
|
|
56
|
+
if (left === void 0 && right === void 0) return;
|
|
57
|
+
const merged = {
|
|
58
|
+
...left ?? {},
|
|
59
|
+
...right ?? {}
|
|
60
|
+
};
|
|
61
|
+
const tags = uniqueTags(left?.tags, right?.tags);
|
|
62
|
+
if (tags.length > 0) merged.tags = tags;
|
|
63
|
+
else delete merged.tags;
|
|
64
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
65
|
+
}
|
|
66
|
+
function uniqueTags(...tagLists) {
|
|
67
|
+
const tags = [];
|
|
68
|
+
const seen = /* @__PURE__ */ new Set();
|
|
69
|
+
for (const tagList of tagLists) for (const tag of tagList ?? []) {
|
|
70
|
+
if (seen.has(tag)) continue;
|
|
71
|
+
seen.add(tag);
|
|
72
|
+
tags.push(tag);
|
|
73
|
+
}
|
|
74
|
+
return tags;
|
|
75
|
+
}
|
|
251
76
|
//#endregion
|
|
252
77
|
//#region src/routing/tokenizer.ts
|
|
253
78
|
const WHITESPACE_CHARS = new Set([
|
|
@@ -352,6 +177,33 @@ var Tokenizer = class {
|
|
|
352
177
|
this.subOffset = void 0;
|
|
353
178
|
return segments;
|
|
354
179
|
}
|
|
180
|
+
consumeMention(userId) {
|
|
181
|
+
const token = this.peek();
|
|
182
|
+
if (typeof token === "object" && token !== null && token.type === "mention" && token.data.user_id === userId) {
|
|
183
|
+
this.next();
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
consumeTextPrefix(prefix) {
|
|
189
|
+
if (prefix.length === 0) return false;
|
|
190
|
+
const position = this.findNextPosition();
|
|
191
|
+
if (position === void 0) return false;
|
|
192
|
+
const segment = this.segments[position.offset];
|
|
193
|
+
if (segment.type !== "text") return false;
|
|
194
|
+
const prefixStart = this.findTextTokenStart(segment.data.text, position.subOffset ?? 0);
|
|
195
|
+
if (!segment.data.text.startsWith(prefix, prefixStart)) return false;
|
|
196
|
+
const prefixEnd = prefixStart + prefix.length;
|
|
197
|
+
const nextSubOffset = this.findTextTokenStart(segment.data.text, prefixEnd);
|
|
198
|
+
if (nextSubOffset < segment.data.text.length) {
|
|
199
|
+
this.offset = position.offset;
|
|
200
|
+
this.subOffset = nextSubOffset;
|
|
201
|
+
} else {
|
|
202
|
+
this.offset = position.offset + 1;
|
|
203
|
+
this.subOffset = void 0;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
355
207
|
findNextPosition() {
|
|
356
208
|
let offset = this.offset;
|
|
357
209
|
let subOffset = this.subOffset;
|
|
@@ -383,19 +235,35 @@ var Tokenizer = class {
|
|
|
383
235
|
};
|
|
384
236
|
//#endregion
|
|
385
237
|
//#region src/routing/router.ts
|
|
238
|
+
const DEFAULT_ACTIVATIONS = [{ type: "direct" }];
|
|
239
|
+
const defaultRouteActivationResolver = () => DEFAULT_ACTIVATIONS;
|
|
386
240
|
var Router = class Router {
|
|
387
241
|
entries = [];
|
|
388
242
|
groups = /* @__PURE__ */ new Map();
|
|
243
|
+
activationResolver = defaultRouteActivationResolver;
|
|
244
|
+
scopeMeta;
|
|
245
|
+
setActivationResolver(resolver) {
|
|
246
|
+
this.activationResolver = resolver;
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
withMeta(meta) {
|
|
250
|
+
const router = new Router();
|
|
251
|
+
router.entries = this.entries;
|
|
252
|
+
router.groups = this.groups;
|
|
253
|
+
router.scopeMeta = mergeRouteMeta(this.scopeMeta, meta);
|
|
254
|
+
return router;
|
|
255
|
+
}
|
|
389
256
|
command(command) {
|
|
390
257
|
if (typeof command === "string") return new CommandBuilder(command, (builtCommand) => {
|
|
391
258
|
this.command(builtCommand);
|
|
392
259
|
return builtCommand;
|
|
393
260
|
});
|
|
394
|
-
this.
|
|
395
|
-
this.
|
|
261
|
+
const scopedCommand = this.applyScopeMeta(command);
|
|
262
|
+
this.validatePattern(scopedCommand.pattern);
|
|
263
|
+
this.resolveAliasConflicts(scopedCommand);
|
|
396
264
|
this.entries.push({
|
|
397
265
|
type: "command",
|
|
398
|
-
command
|
|
266
|
+
command: scopedCommand
|
|
399
267
|
});
|
|
400
268
|
return this;
|
|
401
269
|
}
|
|
@@ -404,10 +272,11 @@ var Router = class Router {
|
|
|
404
272
|
this.rawPattern(builtCommand);
|
|
405
273
|
return builtCommand;
|
|
406
274
|
});
|
|
407
|
-
this.
|
|
275
|
+
const scopedRawPattern = this.applyScopeMeta(rawPattern);
|
|
276
|
+
this.validatePattern(scopedRawPattern.pattern, { rawPattern: true });
|
|
408
277
|
this.entries.push({
|
|
409
278
|
type: "rawPattern",
|
|
410
|
-
rawPattern
|
|
279
|
+
rawPattern: scopedRawPattern
|
|
411
280
|
});
|
|
412
281
|
return this;
|
|
413
282
|
}
|
|
@@ -422,7 +291,7 @@ var Router = class Router {
|
|
|
422
291
|
router
|
|
423
292
|
});
|
|
424
293
|
}
|
|
425
|
-
return router;
|
|
294
|
+
return this.scopeMeta ? router.withMeta(this.scopeMeta) : router;
|
|
426
295
|
}
|
|
427
296
|
filter(predicate) {
|
|
428
297
|
const router = new Router();
|
|
@@ -431,7 +300,7 @@ var Router = class Router {
|
|
|
431
300
|
predicate,
|
|
432
301
|
router
|
|
433
302
|
});
|
|
434
|
-
return router;
|
|
303
|
+
return this.scopeMeta ? router.withMeta(this.scopeMeta) : router;
|
|
435
304
|
}
|
|
436
305
|
routes() {
|
|
437
306
|
return this.entries;
|
|
@@ -441,11 +310,19 @@ var Router = class Router {
|
|
|
441
310
|
return [];
|
|
442
311
|
}
|
|
443
312
|
branches(session) {
|
|
444
|
-
return this.branchesFrom(session, []);
|
|
313
|
+
return [...this.branchesFrom(session, [], { includeHidden: false })];
|
|
445
314
|
}
|
|
446
315
|
match(session, message) {
|
|
447
|
-
const
|
|
448
|
-
|
|
316
|
+
for (const branch of this.branchesFrom(session, [], { includeHidden: true })) {
|
|
317
|
+
const descriptor = this.describeBranch(branch);
|
|
318
|
+
const activations = this.activationResolver(descriptor, session) ?? DEFAULT_ACTIVATIONS;
|
|
319
|
+
for (const activation of activations) {
|
|
320
|
+
const tokenizer = new Tokenizer(message.segments);
|
|
321
|
+
if (!this.consumeActivation(tokenizer, activation, session)) continue;
|
|
322
|
+
const match = this.matchBranch(branch, tokenizer, activation);
|
|
323
|
+
if (match !== void 0) return match;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
449
326
|
}
|
|
450
327
|
async dispatch(session, message) {
|
|
451
328
|
const match = this.match(session, message);
|
|
@@ -460,26 +337,51 @@ var Router = class Router {
|
|
|
460
337
|
}
|
|
461
338
|
return true;
|
|
462
339
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
340
|
+
describeBranch(branch) {
|
|
341
|
+
switch (branch.type) {
|
|
342
|
+
case "command": {
|
|
343
|
+
const descriptor = {
|
|
344
|
+
type: "command",
|
|
345
|
+
path: [...branch.path],
|
|
346
|
+
name: branch.command.name
|
|
347
|
+
};
|
|
348
|
+
if (branch.command.aliases !== void 0) descriptor.aliases = [...branch.command.aliases];
|
|
349
|
+
if (branch.command.meta !== void 0) descriptor.meta = branch.command.meta;
|
|
350
|
+
return descriptor;
|
|
351
|
+
}
|
|
352
|
+
case "rawPattern": {
|
|
353
|
+
const descriptor = {
|
|
354
|
+
type: "rawPattern",
|
|
355
|
+
path: [...branch.path]
|
|
356
|
+
};
|
|
357
|
+
if (branch.rawPattern.meta !== void 0) descriptor.meta = branch.rawPattern.meta;
|
|
358
|
+
return descriptor;
|
|
359
|
+
}
|
|
469
360
|
}
|
|
470
|
-
tokenizer.setState(initialState);
|
|
471
361
|
}
|
|
472
|
-
|
|
473
|
-
switch (
|
|
474
|
-
case "
|
|
475
|
-
case "
|
|
476
|
-
case "
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
362
|
+
consumeActivation(tokenizer, activation, session) {
|
|
363
|
+
switch (activation.type) {
|
|
364
|
+
case "direct": return true;
|
|
365
|
+
case "mention": return tokenizer.consumeMention(session.selfId);
|
|
366
|
+
case "prefix": return tokenizer.consumeTextPrefix(activation.prefix);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
matchBranch(branch, tokenizer, activation) {
|
|
370
|
+
if (!this.matchPath(branch.path, tokenizer)) return;
|
|
371
|
+
switch (branch.type) {
|
|
372
|
+
case "command": return this.matchCommand(branch.command, tokenizer, branch.path, activation);
|
|
373
|
+
case "rawPattern": return this.matchRawPattern(branch.rawPattern, tokenizer, branch.path, activation);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
matchPath(path, tokenizer) {
|
|
377
|
+
for (const name of path) {
|
|
378
|
+
const token = tokenizer.peek();
|
|
379
|
+
if (typeof token !== "string" || token !== name) return false;
|
|
380
|
+
tokenizer.next();
|
|
480
381
|
}
|
|
382
|
+
return true;
|
|
481
383
|
}
|
|
482
|
-
matchCommand(command, tokenizer, path) {
|
|
384
|
+
matchCommand(command, tokenizer, path, activation) {
|
|
483
385
|
const token = tokenizer.peek();
|
|
484
386
|
if (typeof token !== "string" || token !== command.name && !command.aliases?.includes(token)) return;
|
|
485
387
|
tokenizer.next();
|
|
@@ -489,50 +391,55 @@ var Router = class Router {
|
|
|
489
391
|
type: "command",
|
|
490
392
|
path: [...path],
|
|
491
393
|
command,
|
|
492
|
-
params
|
|
394
|
+
params,
|
|
395
|
+
activation
|
|
493
396
|
};
|
|
494
397
|
}
|
|
495
|
-
|
|
496
|
-
const token = tokenizer.peek();
|
|
497
|
-
if (typeof token !== "string" || token !== name) return;
|
|
498
|
-
tokenizer.next();
|
|
499
|
-
return router.matchFrom(session, tokenizer, [...path, name]);
|
|
500
|
-
}
|
|
501
|
-
matchRawPattern(rawPattern, tokenizer, path) {
|
|
398
|
+
matchRawPattern(rawPattern, tokenizer, path, activation) {
|
|
502
399
|
const params = this.capturePattern(rawPattern.pattern, tokenizer);
|
|
503
400
|
if (params === void 0 || tokenizer.hasNext()) return;
|
|
504
401
|
return {
|
|
505
402
|
type: "rawPattern",
|
|
506
403
|
path: [...path],
|
|
507
404
|
rawPattern,
|
|
508
|
-
params
|
|
405
|
+
params,
|
|
406
|
+
activation
|
|
509
407
|
};
|
|
510
408
|
}
|
|
511
|
-
branchesFrom(session, path) {
|
|
512
|
-
const branches = [];
|
|
409
|
+
*branchesFrom(session, path, options) {
|
|
513
410
|
for (const entry of this.entries) switch (entry.type) {
|
|
514
411
|
case "command":
|
|
515
|
-
if (!entry.command.hidden)
|
|
412
|
+
if (options.includeHidden || !entry.command.hidden) yield {
|
|
516
413
|
type: "command",
|
|
517
414
|
path: [...path],
|
|
518
|
-
command: entry.command
|
|
519
|
-
|
|
415
|
+
command: entry.command,
|
|
416
|
+
meta: entry.command.meta
|
|
417
|
+
};
|
|
520
418
|
break;
|
|
521
419
|
case "group":
|
|
522
|
-
|
|
420
|
+
yield* entry.router.branchesFrom(session, [...path, entry.name], options);
|
|
523
421
|
break;
|
|
524
422
|
case "filter":
|
|
525
|
-
if (entry.predicate(session) === true)
|
|
423
|
+
if (entry.predicate(session) === true) yield* entry.router.branchesFrom(session, path, options);
|
|
526
424
|
break;
|
|
527
425
|
case "rawPattern":
|
|
528
|
-
|
|
426
|
+
yield {
|
|
529
427
|
type: "rawPattern",
|
|
530
428
|
path: [...path],
|
|
531
|
-
rawPattern: entry.rawPattern
|
|
532
|
-
|
|
429
|
+
rawPattern: entry.rawPattern,
|
|
430
|
+
meta: entry.rawPattern.meta
|
|
431
|
+
};
|
|
533
432
|
break;
|
|
534
433
|
}
|
|
535
|
-
|
|
434
|
+
}
|
|
435
|
+
applyScopeMeta(route) {
|
|
436
|
+
if (this.scopeMeta === void 0) return route;
|
|
437
|
+
const meta = mergeRouteMeta(route.meta, this.scopeMeta);
|
|
438
|
+
if (meta === void 0) return route;
|
|
439
|
+
return {
|
|
440
|
+
...route,
|
|
441
|
+
meta
|
|
442
|
+
};
|
|
536
443
|
}
|
|
537
444
|
resolveAliasConflicts(command) {
|
|
538
445
|
for (const entry of this.entries) {
|
|
@@ -579,6 +486,283 @@ var Router = class Router {
|
|
|
579
486
|
}
|
|
580
487
|
};
|
|
581
488
|
//#endregion
|
|
489
|
+
//#region src/core/activation.ts
|
|
490
|
+
function createContextRouteActivationResolver(routing, fallback = defaultRouteActivationResolver) {
|
|
491
|
+
if (!routing) return fallback;
|
|
492
|
+
if (routing.activation && routing.activationResolver) throw new Error("Context routing cannot specify both activation and activationResolver.");
|
|
493
|
+
if (routing.activationResolver) return routing.activationResolver;
|
|
494
|
+
if (routing.activation) return compileContextRouteActivationConfig(routing.activation, fallback);
|
|
495
|
+
return fallback;
|
|
496
|
+
}
|
|
497
|
+
function compileContextRouteActivationConfig(config, fallback) {
|
|
498
|
+
return (route, session) => {
|
|
499
|
+
for (const rule of contextRouteActivationRules(config.rules)) {
|
|
500
|
+
if (!matchesContextRouteActivationRule(route, session, rule)) continue;
|
|
501
|
+
const activation = resolveContextRouteActivation(rule.activation, session.raw.message_scene);
|
|
502
|
+
if (activation !== void 0) return activation;
|
|
503
|
+
}
|
|
504
|
+
if (config.default !== void 0) {
|
|
505
|
+
const activation = resolveContextRouteActivation(config.default, session.raw.message_scene);
|
|
506
|
+
if (activation !== void 0) return activation;
|
|
507
|
+
}
|
|
508
|
+
return fallback(route, session);
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function contextRouteActivationRules(rules) {
|
|
512
|
+
if (rules === void 0) return [];
|
|
513
|
+
return isContextRouteActivationRuleArray(rules) ? rules : [rules];
|
|
514
|
+
}
|
|
515
|
+
function isContextRouteActivationRuleArray(rules) {
|
|
516
|
+
return Array.isArray(rules);
|
|
517
|
+
}
|
|
518
|
+
function resolveContextRouteActivation(activation, scene) {
|
|
519
|
+
if (isRouteActivationArray(activation)) return activation;
|
|
520
|
+
return activation[scene] ?? activation.default;
|
|
521
|
+
}
|
|
522
|
+
function isRouteActivationArray(activation) {
|
|
523
|
+
return Array.isArray(activation);
|
|
524
|
+
}
|
|
525
|
+
function matchesContextRouteActivationRule(route, session, rule) {
|
|
526
|
+
const match = rule.match;
|
|
527
|
+
if (!match) return true;
|
|
528
|
+
if (!matchesStringSelector(match.type, route.type)) return false;
|
|
529
|
+
if (!matchesStringSelector(match.plugin, routeMetaString(route, "plugin"))) return false;
|
|
530
|
+
if (!matchesStringSelector(match.context, routeMetaString(route, "context"))) return false;
|
|
531
|
+
if (!matchesTagSelector(match.tag, route.meta?.tags)) return false;
|
|
532
|
+
if (match.command !== void 0 && (route.type !== "command" || !matchesStringSelector(match.command, route.name))) return false;
|
|
533
|
+
if (match.path !== void 0 && !sameStringArray(match.path, route.path)) return false;
|
|
534
|
+
if (match.route !== void 0 && !sameStringArray(match.route, routeSegments(route))) return false;
|
|
535
|
+
if (match.predicate && match.predicate(route, session) !== true) return false;
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
function routeMetaString(route, key) {
|
|
539
|
+
const value = route.meta?.[key];
|
|
540
|
+
return typeof value === "string" ? value : void 0;
|
|
541
|
+
}
|
|
542
|
+
function matchesStringSelector(selector, value) {
|
|
543
|
+
if (selector === void 0) return true;
|
|
544
|
+
if (value === void 0) return false;
|
|
545
|
+
return typeof selector === "string" ? selector === value : selector.includes(value);
|
|
546
|
+
}
|
|
547
|
+
function matchesTagSelector(selector, tags) {
|
|
548
|
+
if (selector === void 0) return true;
|
|
549
|
+
if (!tags) return false;
|
|
550
|
+
return typeof selector === "string" ? tags.includes(selector) : selector.some((tag) => tags.includes(tag));
|
|
551
|
+
}
|
|
552
|
+
function routeSegments(route) {
|
|
553
|
+
if (route.type === "command") return [...route.path, route.name];
|
|
554
|
+
return route.path;
|
|
555
|
+
}
|
|
556
|
+
function sameStringArray(left, right) {
|
|
557
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/protocol/client.ts
|
|
561
|
+
var MilkyClientBase = class {
|
|
562
|
+
baseUrl;
|
|
563
|
+
wsBaseUrl;
|
|
564
|
+
accessToken;
|
|
565
|
+
baseHeaders;
|
|
566
|
+
constructor(baseUrl, options) {
|
|
567
|
+
const normalizedBaseUrl = baseUrl.toString();
|
|
568
|
+
this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
|
|
569
|
+
this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
|
|
570
|
+
this.accessToken = options?.accessToken;
|
|
571
|
+
this.baseHeaders = { "Content-Type": "application/json" };
|
|
572
|
+
if (options?.accessToken) this.baseHeaders.Authorization = `Bearer ${options.accessToken}`;
|
|
573
|
+
}
|
|
574
|
+
async callApi(endpoint, params) {
|
|
575
|
+
const response = await fetch(`${this.baseUrl}/api/${endpoint}`, {
|
|
576
|
+
method: "POST",
|
|
577
|
+
body: JSON.stringify(params ?? {}),
|
|
578
|
+
headers: this.baseHeaders
|
|
579
|
+
});
|
|
580
|
+
if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
|
|
581
|
+
const json = await response.json();
|
|
582
|
+
if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
|
|
583
|
+
return json.data;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
function createMilkyClient(...params) {
|
|
587
|
+
const base = new MilkyClientBase(...params);
|
|
588
|
+
return new Proxy(base, { get(target, endpoint) {
|
|
589
|
+
if (typeof endpoint === "string" && endpoint.includes("_")) return (params) => target.callApi(endpoint, params);
|
|
590
|
+
else return target[endpoint];
|
|
591
|
+
} });
|
|
592
|
+
}
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/protocol/event.ts
|
|
595
|
+
function createMilkyWebSocketEventSource(baseUrl, options) {
|
|
596
|
+
const normalizedBaseUrl = baseUrl.toString();
|
|
597
|
+
const wsBaseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1).replace(/^http/, "ws") : normalizedBaseUrl.replace(/^http/, "ws");
|
|
598
|
+
return {
|
|
599
|
+
name: "websocket",
|
|
600
|
+
async start(onEvent) {
|
|
601
|
+
const ws = new WebSocket(`${wsBaseUrl}/event${options?.accessToken ? `?access_token=${options.accessToken}` : ""}`);
|
|
602
|
+
let closeSubscription = () => {};
|
|
603
|
+
const closed = new Promise((resolve, reject) => {
|
|
604
|
+
closeSubscription = (error) => {
|
|
605
|
+
if (error) reject(error);
|
|
606
|
+
else resolve();
|
|
607
|
+
};
|
|
608
|
+
});
|
|
609
|
+
ws.addEventListener("message", async (event) => {
|
|
610
|
+
try {
|
|
611
|
+
if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
|
|
612
|
+
await onEvent(JSON.parse(event.data));
|
|
613
|
+
} catch (error) {
|
|
614
|
+
closeSubscription(error);
|
|
615
|
+
ws.close();
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
await new Promise((resolve, reject) => {
|
|
619
|
+
ws.addEventListener("open", () => resolve(), { once: true });
|
|
620
|
+
ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
621
|
+
});
|
|
622
|
+
ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
623
|
+
ws.addEventListener("close", () => closeSubscription(), { once: true });
|
|
624
|
+
return {
|
|
625
|
+
closed,
|
|
626
|
+
stop() {
|
|
627
|
+
ws.close();
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/protocol/segment.ts
|
|
635
|
+
function msg(strings, ...values) {
|
|
636
|
+
let buffer = "";
|
|
637
|
+
const segments = [];
|
|
638
|
+
for (let i = 0; i < strings.length; i++) {
|
|
639
|
+
buffer += strings[i];
|
|
640
|
+
if (i < values.length) {
|
|
641
|
+
const value = values[i];
|
|
642
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") buffer += value.toString();
|
|
643
|
+
else {
|
|
644
|
+
if (buffer) {
|
|
645
|
+
segments.push({
|
|
646
|
+
type: "text",
|
|
647
|
+
data: { text: buffer }
|
|
648
|
+
});
|
|
649
|
+
buffer = "";
|
|
650
|
+
}
|
|
651
|
+
segments.push(value);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (buffer) segments.push({
|
|
656
|
+
type: "text",
|
|
657
|
+
data: { text: buffer }
|
|
658
|
+
});
|
|
659
|
+
return trimBoundaryTextSegments(segments);
|
|
660
|
+
}
|
|
661
|
+
function isTextSegment(segment) {
|
|
662
|
+
return segment.type === "text";
|
|
663
|
+
}
|
|
664
|
+
function withText(segment, text) {
|
|
665
|
+
return {
|
|
666
|
+
...segment,
|
|
667
|
+
data: {
|
|
668
|
+
...segment.data,
|
|
669
|
+
text
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function trimBoundaryTextSegments(segments) {
|
|
674
|
+
const result = [...segments];
|
|
675
|
+
const first = result[0];
|
|
676
|
+
if (first && isTextSegment(first)) {
|
|
677
|
+
const text = first.data.text.trimStart();
|
|
678
|
+
if (text) result[0] = withText(first, text);
|
|
679
|
+
else result.shift();
|
|
680
|
+
}
|
|
681
|
+
const lastIndex = result.length - 1;
|
|
682
|
+
const last = result[lastIndex];
|
|
683
|
+
if (last && isTextSegment(last)) {
|
|
684
|
+
const text = last.data.text.trimEnd();
|
|
685
|
+
if (text) result[lastIndex] = withText(last, text);
|
|
686
|
+
else result.pop();
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
let seg;
|
|
691
|
+
(function(_seg) {
|
|
692
|
+
function mention(userId) {
|
|
693
|
+
return {
|
|
694
|
+
type: "mention",
|
|
695
|
+
data: { user_id: userId }
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
_seg.mention = mention;
|
|
699
|
+
function mentionAll() {
|
|
700
|
+
return {
|
|
701
|
+
type: "mention_all",
|
|
702
|
+
data: {}
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
_seg.mentionAll = mentionAll;
|
|
706
|
+
function face(faceId, options) {
|
|
707
|
+
return {
|
|
708
|
+
type: "face",
|
|
709
|
+
data: {
|
|
710
|
+
face_id: faceId.toString(),
|
|
711
|
+
is_large: options?.isLarge ?? false
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
_seg.face = face;
|
|
716
|
+
function reply(messageSeq) {
|
|
717
|
+
return {
|
|
718
|
+
type: "reply",
|
|
719
|
+
data: { message_seq: messageSeq }
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
_seg.reply = reply;
|
|
723
|
+
function image(uri, options) {
|
|
724
|
+
return {
|
|
725
|
+
type: "image",
|
|
726
|
+
data: {
|
|
727
|
+
uri,
|
|
728
|
+
sub_type: options?.subType,
|
|
729
|
+
summary: options?.summary
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
_seg.image = image;
|
|
734
|
+
function record(uri) {
|
|
735
|
+
return {
|
|
736
|
+
type: "record",
|
|
737
|
+
data: { uri }
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
_seg.record = record;
|
|
741
|
+
function video(uri, options) {
|
|
742
|
+
return {
|
|
743
|
+
type: "video",
|
|
744
|
+
data: {
|
|
745
|
+
uri,
|
|
746
|
+
thumb_uri: options?.thumbUri
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
_seg.video = video;
|
|
751
|
+
function forward(messages, options) {
|
|
752
|
+
return {
|
|
753
|
+
type: "forward",
|
|
754
|
+
data: {
|
|
755
|
+
messages,
|
|
756
|
+
title: options?.title,
|
|
757
|
+
preview: options?.preview,
|
|
758
|
+
summary: options?.summary,
|
|
759
|
+
prompt: options?.prompt
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
_seg.forward = forward;
|
|
764
|
+
})(seg || (seg = {}));
|
|
765
|
+
//#endregion
|
|
582
766
|
//#region src/core/logging.ts
|
|
583
767
|
var Logger = class {
|
|
584
768
|
logHandler;
|
|
@@ -631,6 +815,7 @@ var Context = class Context {
|
|
|
631
815
|
router = new Router();
|
|
632
816
|
logger;
|
|
633
817
|
name;
|
|
818
|
+
routeActivationResolver;
|
|
634
819
|
parent;
|
|
635
820
|
filter;
|
|
636
821
|
eventBus = mitt();
|
|
@@ -653,6 +838,8 @@ var Context = class Context {
|
|
|
653
838
|
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
654
839
|
this.name = name ?? "root";
|
|
655
840
|
this.logger = new Logger((message) => this.logHandler?.(message), `context:${this.name}`);
|
|
841
|
+
this.routeActivationResolver = createContextRouteActivationResolver(options?.routing, parent?.routeActivationResolver);
|
|
842
|
+
this.router.setActivationResolver(this.routeActivationResolver);
|
|
656
843
|
this.parent = parent;
|
|
657
844
|
this.filter = filter;
|
|
658
845
|
if (parent) {
|
|
@@ -1015,6 +1202,10 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1015
1202
|
}
|
|
1016
1203
|
createProxyContextForPlugin(plugin) {
|
|
1017
1204
|
const proxyLogger = new Logger((message) => this.logHandler?.(message), `plugin:${this.name ? `${this.name}/` : ""}${plugin.name}`);
|
|
1205
|
+
const proxyRouter = this.router.withMeta({
|
|
1206
|
+
context: this.name,
|
|
1207
|
+
plugin: plugin.name
|
|
1208
|
+
});
|
|
1018
1209
|
let proxyInjections;
|
|
1019
1210
|
if (plugin.inject) {
|
|
1020
1211
|
proxyInjections = {};
|
|
@@ -1026,6 +1217,7 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1026
1217
|
}
|
|
1027
1218
|
return new Proxy(this, { get(target, prop, receiver) {
|
|
1028
1219
|
if (prop === "logger") return proxyLogger;
|
|
1220
|
+
else if (prop === "router") return proxyRouter;
|
|
1029
1221
|
else if (proxyInjections && prop in proxyInjections) return proxyInjections[prop];
|
|
1030
1222
|
else return Reflect.get(target, prop, receiver);
|
|
1031
1223
|
} });
|
|
@@ -1382,4 +1574,4 @@ let param;
|
|
|
1382
1574
|
_param.segment = segment;
|
|
1383
1575
|
})(param || (param = {}));
|
|
1384
1576
|
//#endregion
|
|
1385
|
-
export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createMilkyClient, createMilkyWebSocketEventSource, definePlugin, filter, implementsESNextDisposable, isDisposable, milkyPackageVersion, milkyVersion, msg, param, seg };
|
|
1577
|
+
export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createContextRouteActivationResolver, 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.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"description": "Milky Bot framework",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git+https://github.com/fraqjs/fraq.git"
|
|
16
16
|
},
|
|
17
|
-
"homepage": "https://fraq.
|
|
17
|
+
"homepage": "https://fraq.dev/",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"mitt": "^3.0.1"
|