@fraqjs/fraq 0.11.0 → 0.12.1
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 +157 -61
- package/dist/index.mjs +454 -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,41 @@ 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 RouteActivationInput = RouteActivation | readonly RouteActivation[];
|
|
5551
|
+
type RouteDescriptor =
|
|
5552
|
+
| {
|
|
5553
|
+
type: 'command';
|
|
5554
|
+
path: readonly string[];
|
|
5555
|
+
name: string;
|
|
5556
|
+
aliases?: readonly string[];
|
|
5557
|
+
meta?: RouteMeta;
|
|
5558
|
+
}
|
|
5559
|
+
| {
|
|
5560
|
+
type: 'rawPattern';
|
|
5561
|
+
path: readonly string[];
|
|
5562
|
+
meta?: RouteMeta;
|
|
5563
|
+
};
|
|
5564
|
+
type RouteActivationResolver = (route: RouteDescriptor, session: Session) => RouteActivationInput | undefined;
|
|
5565
|
+
declare const defaultRouteActivationResolver: RouteActivationResolver;
|
|
5584
5566
|
type RouteEntry =
|
|
5585
5567
|
| {
|
|
5586
5568
|
type: 'command';
|
|
@@ -5605,11 +5587,13 @@ type RouteBranch =
|
|
|
5605
5587
|
type: 'command';
|
|
5606
5588
|
path: string[];
|
|
5607
5589
|
command: Command<Pattern>;
|
|
5590
|
+
meta?: RouteMeta;
|
|
5608
5591
|
}
|
|
5609
5592
|
| {
|
|
5610
5593
|
type: 'rawPattern';
|
|
5611
5594
|
path: string[];
|
|
5612
5595
|
rawPattern: RawPattern<Pattern>;
|
|
5596
|
+
meta?: RouteMeta;
|
|
5613
5597
|
};
|
|
5614
5598
|
type RouteMatchResult =
|
|
5615
5599
|
| {
|
|
@@ -5617,16 +5601,22 @@ type RouteMatchResult =
|
|
|
5617
5601
|
path: string[];
|
|
5618
5602
|
command: Command<Pattern>;
|
|
5619
5603
|
params: any;
|
|
5604
|
+
activation?: RouteActivation;
|
|
5620
5605
|
}
|
|
5621
5606
|
| {
|
|
5622
5607
|
type: 'rawPattern';
|
|
5623
5608
|
path: string[];
|
|
5624
5609
|
rawPattern: RawPattern<Pattern>;
|
|
5625
5610
|
params: any;
|
|
5611
|
+
activation?: RouteActivation;
|
|
5626
5612
|
};
|
|
5627
5613
|
declare class Router {
|
|
5628
|
-
private
|
|
5629
|
-
private
|
|
5614
|
+
private entries;
|
|
5615
|
+
private groups;
|
|
5616
|
+
private activationResolver;
|
|
5617
|
+
private scopeMeta?;
|
|
5618
|
+
setActivationResolver(resolver: RouteActivationResolver): this;
|
|
5619
|
+
withMeta(meta: RouteMeta): Router;
|
|
5630
5620
|
command(name: string): CommandBuilder;
|
|
5631
5621
|
command<P extends Pattern>(command: Command<P>): this;
|
|
5632
5622
|
rawPattern(): CommandBuilder<{}, RawPattern<{}>>;
|
|
@@ -5638,17 +5628,107 @@ declare class Router {
|
|
|
5638
5628
|
branches(session: Session): RouteBranch[];
|
|
5639
5629
|
match(session: Session, message: IncomingMessage): RouteMatchResult | undefined;
|
|
5640
5630
|
dispatch(session: Session, message: IncomingMessage): Promise<boolean>;
|
|
5641
|
-
private
|
|
5642
|
-
private
|
|
5631
|
+
private describeBranch;
|
|
5632
|
+
private consumeActivation;
|
|
5633
|
+
private matchBranch;
|
|
5634
|
+
private matchPath;
|
|
5643
5635
|
private matchCommand;
|
|
5644
|
-
private matchGroup;
|
|
5645
5636
|
private matchRawPattern;
|
|
5646
5637
|
private branchesFrom;
|
|
5638
|
+
private applyScopeMeta;
|
|
5647
5639
|
private resolveAliasConflicts;
|
|
5648
5640
|
private validatePattern;
|
|
5649
5641
|
private capturePattern;
|
|
5650
5642
|
}
|
|
5651
5643
|
//#endregion
|
|
5644
|
+
//#region src/core/activation.d.ts
|
|
5645
|
+
type ContextRouteActivationByScene = Partial<
|
|
5646
|
+
Record<IncomingMessage['message_scene'] | 'default', RouteActivationInput>
|
|
5647
|
+
>;
|
|
5648
|
+
type ContextRouteActivation = RouteActivationInput | ContextRouteActivationByScene;
|
|
5649
|
+
interface ContextRouteActivationMatcher {
|
|
5650
|
+
type?: RouteDescriptor['type'] | readonly RouteDescriptor['type'][];
|
|
5651
|
+
plugin?: string | readonly string[];
|
|
5652
|
+
context?: string | readonly string[];
|
|
5653
|
+
tag?: string | readonly string[];
|
|
5654
|
+
command?: string | readonly string[];
|
|
5655
|
+
path?: readonly string[];
|
|
5656
|
+
route?: readonly string[];
|
|
5657
|
+
predicate?: (route: RouteDescriptor, session: Session) => boolean;
|
|
5658
|
+
}
|
|
5659
|
+
interface ContextRouteActivationRule {
|
|
5660
|
+
match?: ContextRouteActivationMatcher;
|
|
5661
|
+
activation: ContextRouteActivation;
|
|
5662
|
+
}
|
|
5663
|
+
interface ContextRouteActivationConfig {
|
|
5664
|
+
default?: ContextRouteActivation;
|
|
5665
|
+
rules?: ContextRouteActivationRule | readonly ContextRouteActivationRule[];
|
|
5666
|
+
}
|
|
5667
|
+
interface ContextRoutingOptions {
|
|
5668
|
+
activation?: ContextRouteActivationConfig;
|
|
5669
|
+
activationResolver?: RouteActivationResolver;
|
|
5670
|
+
}
|
|
5671
|
+
declare function createContextRouteActivationResolver(
|
|
5672
|
+
routing: ContextRoutingOptions | undefined,
|
|
5673
|
+
fallback?: RouteActivationResolver,
|
|
5674
|
+
): RouteActivationResolver;
|
|
5675
|
+
//#endregion
|
|
5676
|
+
//#region src/protocol/endpoint.d.ts
|
|
5677
|
+
type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T];
|
|
5678
|
+
type AllOptional<T> = RequiredKeys<T> extends never ? true : false;
|
|
5679
|
+
type RawApiEndpoint<E extends keyof ApiEndpoints$1> = {
|
|
5680
|
+
request: ApiEndpoints$1[E]['request_ZodInput'] extends null ? null : ApiEndpoints$1[E]['request_ZodInput'];
|
|
5681
|
+
response: ApiEndpoints$1[E]['response'] extends null ? null : ApiEndpoints$1[E]['response'];
|
|
5682
|
+
};
|
|
5683
|
+
type ApiEndpointFunction<E extends keyof ApiEndpoints$1> = RawApiEndpoint<E>['request'] extends null
|
|
5684
|
+
? () => Promise<RawApiEndpoint<E>['response']>
|
|
5685
|
+
: AllOptional<RawApiEndpoint<E>['request']> extends true
|
|
5686
|
+
? (params?: RawApiEndpoint<E>['request']) => Promise<RawApiEndpoint<E>['response']>
|
|
5687
|
+
: (params: RawApiEndpoint<E>['request']) => Promise<RawApiEndpoint<E>['response']>;
|
|
5688
|
+
type ApiEndpoints = { [E in keyof ApiEndpoints$1]: ApiEndpointFunction<E> };
|
|
5689
|
+
type EventMap = {
|
|
5690
|
+
[K in Event['event_type']]: Extract<
|
|
5691
|
+
Event,
|
|
5692
|
+
{
|
|
5693
|
+
event_type: K;
|
|
5694
|
+
}
|
|
5695
|
+
>;
|
|
5696
|
+
};
|
|
5697
|
+
//#endregion
|
|
5698
|
+
//#region src/protocol/client.d.ts
|
|
5699
|
+
interface MilkyClient extends ApiEndpoints {}
|
|
5700
|
+
declare class MilkyClientBase {
|
|
5701
|
+
readonly baseUrl: string;
|
|
5702
|
+
readonly wsBaseUrl: string;
|
|
5703
|
+
private readonly accessToken?;
|
|
5704
|
+
private readonly baseHeaders;
|
|
5705
|
+
constructor(
|
|
5706
|
+
baseUrl: string | URL,
|
|
5707
|
+
options?: {
|
|
5708
|
+
accessToken?: string;
|
|
5709
|
+
},
|
|
5710
|
+
);
|
|
5711
|
+
callApi(endpoint: string, params?: unknown): Promise<unknown>;
|
|
5712
|
+
}
|
|
5713
|
+
declare function createMilkyClient(...params: ConstructorParameters<typeof MilkyClientBase>): MilkyClient;
|
|
5714
|
+
//#endregion
|
|
5715
|
+
//#region src/protocol/event.d.ts
|
|
5716
|
+
interface MilkyEventSubscription {
|
|
5717
|
+
closed: Promise<void>;
|
|
5718
|
+
stop(): void | Promise<void>;
|
|
5719
|
+
}
|
|
5720
|
+
interface MilkyEventSource {
|
|
5721
|
+
readonly name?: string;
|
|
5722
|
+
start(onEvent: (event: Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
|
|
5723
|
+
}
|
|
5724
|
+
interface MilkyWebSocketEventSourceOptions {
|
|
5725
|
+
accessToken?: string;
|
|
5726
|
+
}
|
|
5727
|
+
declare function createMilkyWebSocketEventSource(
|
|
5728
|
+
baseUrl: string | URL,
|
|
5729
|
+
options?: MilkyWebSocketEventSourceOptions,
|
|
5730
|
+
): MilkyEventSource;
|
|
5731
|
+
//#endregion
|
|
5652
5732
|
//#region src/core/filter.d.ts
|
|
5653
5733
|
type Filter = { [K in keyof EventMap]?: (event: EventMap[K]) => boolean };
|
|
5654
5734
|
declare namespace filter {
|
|
@@ -5731,6 +5811,7 @@ interface ContextOptions {
|
|
|
5731
5811
|
maxDelayMs?: number;
|
|
5732
5812
|
};
|
|
5733
5813
|
logHandler?: LogHandler;
|
|
5814
|
+
routing?: ContextRoutingOptions;
|
|
5734
5815
|
}
|
|
5735
5816
|
interface ContextUrlOptions {
|
|
5736
5817
|
accessToken?: string;
|
|
@@ -5741,6 +5822,7 @@ declare class Context {
|
|
|
5741
5822
|
readonly router: Router;
|
|
5742
5823
|
readonly logger: Logger;
|
|
5743
5824
|
readonly name: string;
|
|
5825
|
+
readonly routeActivationResolver: RouteActivationResolver;
|
|
5744
5826
|
private readonly parent?;
|
|
5745
5827
|
private readonly filter?;
|
|
5746
5828
|
private readonly eventBus;
|
|
@@ -5848,6 +5930,12 @@ export {
|
|
|
5848
5930
|
CommandBuilder,
|
|
5849
5931
|
Context,
|
|
5850
5932
|
ContextOptions,
|
|
5933
|
+
ContextRouteActivation,
|
|
5934
|
+
ContextRouteActivationByScene,
|
|
5935
|
+
ContextRouteActivationConfig,
|
|
5936
|
+
ContextRouteActivationMatcher,
|
|
5937
|
+
ContextRouteActivationRule,
|
|
5938
|
+
ContextRoutingOptions,
|
|
5851
5939
|
ContextUrlOptions,
|
|
5852
5940
|
Disposable,
|
|
5853
5941
|
EventMap,
|
|
@@ -5868,9 +5956,14 @@ export {
|
|
|
5868
5956
|
Pattern,
|
|
5869
5957
|
Plugin,
|
|
5870
5958
|
RawPattern,
|
|
5959
|
+
RouteActivation,
|
|
5960
|
+
RouteActivationInput,
|
|
5961
|
+
RouteActivationResolver,
|
|
5871
5962
|
RouteBranch,
|
|
5963
|
+
RouteDescriptor,
|
|
5872
5964
|
RouteEntry,
|
|
5873
5965
|
RouteMatchResult,
|
|
5966
|
+
RouteMeta,
|
|
5874
5967
|
Router,
|
|
5875
5968
|
ServiceClass,
|
|
5876
5969
|
Session,
|
|
@@ -5878,12 +5971,15 @@ export {
|
|
|
5878
5971
|
SessionReplyOptions,
|
|
5879
5972
|
TypeInstruction,
|
|
5880
5973
|
combineLogHandlers,
|
|
5974
|
+
createContextRouteActivationResolver,
|
|
5881
5975
|
createMilkyClient,
|
|
5882
5976
|
createMilkyWebSocketEventSource,
|
|
5977
|
+
defaultRouteActivationResolver,
|
|
5883
5978
|
definePlugin,
|
|
5884
5979
|
filter,
|
|
5885
5980
|
implementsESNextDisposable,
|
|
5886
5981
|
isDisposable,
|
|
5982
|
+
mergeRouteMeta,
|
|
5887
5983
|
type types_d_exports as milky,
|
|
5888
5984
|
milkyPackageVersion,
|
|
5889
5985
|
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 routeActivationInputs(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);
|
|
480
374
|
}
|
|
481
375
|
}
|
|
482
|
-
|
|
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();
|
|
381
|
+
}
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
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) {
|
|
@@ -578,6 +485,292 @@ var Router = class Router {
|
|
|
578
485
|
return params;
|
|
579
486
|
}
|
|
580
487
|
};
|
|
488
|
+
function routeActivationInputs(activations) {
|
|
489
|
+
return isRouteActivationArray(activations) ? activations : [activations];
|
|
490
|
+
}
|
|
491
|
+
function isRouteActivationArray(activations) {
|
|
492
|
+
return Array.isArray(activations);
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/core/activation.ts
|
|
496
|
+
function createContextRouteActivationResolver(routing, fallback = defaultRouteActivationResolver) {
|
|
497
|
+
if (!routing) return fallback;
|
|
498
|
+
if (routing.activation && routing.activationResolver) throw new Error("Context routing cannot specify both activation and activationResolver.");
|
|
499
|
+
if (routing.activationResolver) return routing.activationResolver;
|
|
500
|
+
if (routing.activation) return compileContextRouteActivationConfig(routing.activation, fallback);
|
|
501
|
+
return fallback;
|
|
502
|
+
}
|
|
503
|
+
function compileContextRouteActivationConfig(config, fallback) {
|
|
504
|
+
return (route, session) => {
|
|
505
|
+
for (const rule of contextRouteActivationRules(config.rules)) {
|
|
506
|
+
if (!matchesContextRouteActivationRule(route, session, rule)) continue;
|
|
507
|
+
const activation = resolveContextRouteActivation(rule.activation, session.raw.message_scene);
|
|
508
|
+
if (activation !== void 0) return activation;
|
|
509
|
+
}
|
|
510
|
+
if (config.default !== void 0) {
|
|
511
|
+
const activation = resolveContextRouteActivation(config.default, session.raw.message_scene);
|
|
512
|
+
if (activation !== void 0) return activation;
|
|
513
|
+
}
|
|
514
|
+
return fallback(route, session);
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function contextRouteActivationRules(rules) {
|
|
518
|
+
if (rules === void 0) return [];
|
|
519
|
+
return isContextRouteActivationRuleArray(rules) ? rules : [rules];
|
|
520
|
+
}
|
|
521
|
+
function isContextRouteActivationRuleArray(rules) {
|
|
522
|
+
return Array.isArray(rules);
|
|
523
|
+
}
|
|
524
|
+
function resolveContextRouteActivation(activation, scene) {
|
|
525
|
+
if (isRouteActivationInput(activation)) return activation;
|
|
526
|
+
return activation[scene] ?? activation.default;
|
|
527
|
+
}
|
|
528
|
+
function isRouteActivationInput(activation) {
|
|
529
|
+
return Array.isArray(activation) || isRouteActivation(activation);
|
|
530
|
+
}
|
|
531
|
+
function isRouteActivation(activation) {
|
|
532
|
+
return "type" in activation;
|
|
533
|
+
}
|
|
534
|
+
function matchesContextRouteActivationRule(route, session, rule) {
|
|
535
|
+
const match = rule.match;
|
|
536
|
+
if (!match) return true;
|
|
537
|
+
if (!matchesStringSelector(match.type, route.type)) return false;
|
|
538
|
+
if (!matchesStringSelector(match.plugin, routeMetaString(route, "plugin"))) return false;
|
|
539
|
+
if (!matchesStringSelector(match.context, routeMetaString(route, "context"))) return false;
|
|
540
|
+
if (!matchesTagSelector(match.tag, route.meta?.tags)) return false;
|
|
541
|
+
if (match.command !== void 0 && (route.type !== "command" || !matchesStringSelector(match.command, route.name))) return false;
|
|
542
|
+
if (match.path !== void 0 && !sameStringArray(match.path, route.path)) return false;
|
|
543
|
+
if (match.route !== void 0 && !sameStringArray(match.route, routeSegments(route))) return false;
|
|
544
|
+
if (match.predicate && match.predicate(route, session) !== true) return false;
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
function routeMetaString(route, key) {
|
|
548
|
+
const value = route.meta?.[key];
|
|
549
|
+
return typeof value === "string" ? value : void 0;
|
|
550
|
+
}
|
|
551
|
+
function matchesStringSelector(selector, value) {
|
|
552
|
+
if (selector === void 0) return true;
|
|
553
|
+
if (value === void 0) return false;
|
|
554
|
+
return typeof selector === "string" ? selector === value : selector.includes(value);
|
|
555
|
+
}
|
|
556
|
+
function matchesTagSelector(selector, tags) {
|
|
557
|
+
if (selector === void 0) return true;
|
|
558
|
+
if (!tags) return false;
|
|
559
|
+
return typeof selector === "string" ? tags.includes(selector) : selector.some((tag) => tags.includes(tag));
|
|
560
|
+
}
|
|
561
|
+
function routeSegments(route) {
|
|
562
|
+
if (route.type === "command") return [...route.path, route.name];
|
|
563
|
+
return route.path;
|
|
564
|
+
}
|
|
565
|
+
function sameStringArray(left, right) {
|
|
566
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
567
|
+
}
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region src/protocol/client.ts
|
|
570
|
+
var MilkyClientBase = class {
|
|
571
|
+
baseUrl;
|
|
572
|
+
wsBaseUrl;
|
|
573
|
+
accessToken;
|
|
574
|
+
baseHeaders;
|
|
575
|
+
constructor(baseUrl, options) {
|
|
576
|
+
const normalizedBaseUrl = baseUrl.toString();
|
|
577
|
+
this.baseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1) : normalizedBaseUrl;
|
|
578
|
+
this.wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
|
|
579
|
+
this.accessToken = options?.accessToken;
|
|
580
|
+
this.baseHeaders = { "Content-Type": "application/json" };
|
|
581
|
+
if (options?.accessToken) this.baseHeaders.Authorization = `Bearer ${options.accessToken}`;
|
|
582
|
+
}
|
|
583
|
+
async callApi(endpoint, params) {
|
|
584
|
+
const response = await fetch(`${this.baseUrl}/api/${endpoint}`, {
|
|
585
|
+
method: "POST",
|
|
586
|
+
body: JSON.stringify(params ?? {}),
|
|
587
|
+
headers: this.baseHeaders
|
|
588
|
+
});
|
|
589
|
+
if (!response.ok) throw new Error(`API call ${endpoint} failed with HTTP status ${response.status}`);
|
|
590
|
+
const json = await response.json();
|
|
591
|
+
if (json.status === "failed") throw new Error(`API call ${endpoint} failed with retcode ${json.retcode}: ${json.message}`);
|
|
592
|
+
return json.data;
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
function createMilkyClient(...params) {
|
|
596
|
+
const base = new MilkyClientBase(...params);
|
|
597
|
+
return new Proxy(base, { get(target, endpoint) {
|
|
598
|
+
if (typeof endpoint === "string" && endpoint.includes("_")) return (params) => target.callApi(endpoint, params);
|
|
599
|
+
else return target[endpoint];
|
|
600
|
+
} });
|
|
601
|
+
}
|
|
602
|
+
//#endregion
|
|
603
|
+
//#region src/protocol/event.ts
|
|
604
|
+
function createMilkyWebSocketEventSource(baseUrl, options) {
|
|
605
|
+
const normalizedBaseUrl = baseUrl.toString();
|
|
606
|
+
const wsBaseUrl = normalizedBaseUrl.endsWith("/") ? normalizedBaseUrl.slice(0, -1).replace(/^http/, "ws") : normalizedBaseUrl.replace(/^http/, "ws");
|
|
607
|
+
return {
|
|
608
|
+
name: "websocket",
|
|
609
|
+
async start(onEvent) {
|
|
610
|
+
const ws = new WebSocket(`${wsBaseUrl}/event${options?.accessToken ? `?access_token=${options.accessToken}` : ""}`);
|
|
611
|
+
let closeSubscription = () => {};
|
|
612
|
+
const closed = new Promise((resolve, reject) => {
|
|
613
|
+
closeSubscription = (error) => {
|
|
614
|
+
if (error) reject(error);
|
|
615
|
+
else resolve();
|
|
616
|
+
};
|
|
617
|
+
});
|
|
618
|
+
ws.addEventListener("message", async (event) => {
|
|
619
|
+
try {
|
|
620
|
+
if (typeof event.data !== "string") throw new Error(`Expected text frame, got ${typeof event.data}`);
|
|
621
|
+
await onEvent(JSON.parse(event.data));
|
|
622
|
+
} catch (error) {
|
|
623
|
+
closeSubscription(error);
|
|
624
|
+
ws.close();
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
await new Promise((resolve, reject) => {
|
|
628
|
+
ws.addEventListener("open", () => resolve(), { once: true });
|
|
629
|
+
ws.addEventListener("error", (event) => reject(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
630
|
+
});
|
|
631
|
+
ws.addEventListener("error", (event) => closeSubscription(/* @__PURE__ */ new Error(`WebSocket error: ${event}`)));
|
|
632
|
+
ws.addEventListener("close", () => closeSubscription(), { once: true });
|
|
633
|
+
return {
|
|
634
|
+
closed,
|
|
635
|
+
stop() {
|
|
636
|
+
ws.close();
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
//#endregion
|
|
643
|
+
//#region src/protocol/segment.ts
|
|
644
|
+
function msg(strings, ...values) {
|
|
645
|
+
let buffer = "";
|
|
646
|
+
const segments = [];
|
|
647
|
+
for (let i = 0; i < strings.length; i++) {
|
|
648
|
+
buffer += strings[i];
|
|
649
|
+
if (i < values.length) {
|
|
650
|
+
const value = values[i];
|
|
651
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") buffer += value.toString();
|
|
652
|
+
else {
|
|
653
|
+
if (buffer) {
|
|
654
|
+
segments.push({
|
|
655
|
+
type: "text",
|
|
656
|
+
data: { text: buffer }
|
|
657
|
+
});
|
|
658
|
+
buffer = "";
|
|
659
|
+
}
|
|
660
|
+
segments.push(value);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (buffer) segments.push({
|
|
665
|
+
type: "text",
|
|
666
|
+
data: { text: buffer }
|
|
667
|
+
});
|
|
668
|
+
return trimBoundaryTextSegments(segments);
|
|
669
|
+
}
|
|
670
|
+
function isTextSegment(segment) {
|
|
671
|
+
return segment.type === "text";
|
|
672
|
+
}
|
|
673
|
+
function withText(segment, text) {
|
|
674
|
+
return {
|
|
675
|
+
...segment,
|
|
676
|
+
data: {
|
|
677
|
+
...segment.data,
|
|
678
|
+
text
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function trimBoundaryTextSegments(segments) {
|
|
683
|
+
const result = [...segments];
|
|
684
|
+
const first = result[0];
|
|
685
|
+
if (first && isTextSegment(first)) {
|
|
686
|
+
const text = first.data.text.trimStart();
|
|
687
|
+
if (text) result[0] = withText(first, text);
|
|
688
|
+
else result.shift();
|
|
689
|
+
}
|
|
690
|
+
const lastIndex = result.length - 1;
|
|
691
|
+
const last = result[lastIndex];
|
|
692
|
+
if (last && isTextSegment(last)) {
|
|
693
|
+
const text = last.data.text.trimEnd();
|
|
694
|
+
if (text) result[lastIndex] = withText(last, text);
|
|
695
|
+
else result.pop();
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
698
|
+
}
|
|
699
|
+
let seg;
|
|
700
|
+
(function(_seg) {
|
|
701
|
+
function mention(userId) {
|
|
702
|
+
return {
|
|
703
|
+
type: "mention",
|
|
704
|
+
data: { user_id: userId }
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
_seg.mention = mention;
|
|
708
|
+
function mentionAll() {
|
|
709
|
+
return {
|
|
710
|
+
type: "mention_all",
|
|
711
|
+
data: {}
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
_seg.mentionAll = mentionAll;
|
|
715
|
+
function face(faceId, options) {
|
|
716
|
+
return {
|
|
717
|
+
type: "face",
|
|
718
|
+
data: {
|
|
719
|
+
face_id: faceId.toString(),
|
|
720
|
+
is_large: options?.isLarge ?? false
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
_seg.face = face;
|
|
725
|
+
function reply(messageSeq) {
|
|
726
|
+
return {
|
|
727
|
+
type: "reply",
|
|
728
|
+
data: { message_seq: messageSeq }
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
_seg.reply = reply;
|
|
732
|
+
function image(uri, options) {
|
|
733
|
+
return {
|
|
734
|
+
type: "image",
|
|
735
|
+
data: {
|
|
736
|
+
uri,
|
|
737
|
+
sub_type: options?.subType,
|
|
738
|
+
summary: options?.summary
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
_seg.image = image;
|
|
743
|
+
function record(uri) {
|
|
744
|
+
return {
|
|
745
|
+
type: "record",
|
|
746
|
+
data: { uri }
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
_seg.record = record;
|
|
750
|
+
function video(uri, options) {
|
|
751
|
+
return {
|
|
752
|
+
type: "video",
|
|
753
|
+
data: {
|
|
754
|
+
uri,
|
|
755
|
+
thumb_uri: options?.thumbUri
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
_seg.video = video;
|
|
760
|
+
function forward(messages, options) {
|
|
761
|
+
return {
|
|
762
|
+
type: "forward",
|
|
763
|
+
data: {
|
|
764
|
+
messages,
|
|
765
|
+
title: options?.title,
|
|
766
|
+
preview: options?.preview,
|
|
767
|
+
summary: options?.summary,
|
|
768
|
+
prompt: options?.prompt
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
_seg.forward = forward;
|
|
773
|
+
})(seg || (seg = {}));
|
|
581
774
|
//#endregion
|
|
582
775
|
//#region src/core/logging.ts
|
|
583
776
|
var Logger = class {
|
|
@@ -631,6 +824,7 @@ var Context = class Context {
|
|
|
631
824
|
router = new Router();
|
|
632
825
|
logger;
|
|
633
826
|
name;
|
|
827
|
+
routeActivationResolver;
|
|
634
828
|
parent;
|
|
635
829
|
filter;
|
|
636
830
|
eventBus = mitt();
|
|
@@ -653,6 +847,8 @@ var Context = class Context {
|
|
|
653
847
|
this.logHandler = options?.logHandler ?? parent?.logHandler;
|
|
654
848
|
this.name = name ?? "root";
|
|
655
849
|
this.logger = new Logger((message) => this.logHandler?.(message), `context:${this.name}`);
|
|
850
|
+
this.routeActivationResolver = createContextRouteActivationResolver(options?.routing, parent?.routeActivationResolver);
|
|
851
|
+
this.router.setActivationResolver(this.routeActivationResolver);
|
|
656
852
|
this.parent = parent;
|
|
657
853
|
this.filter = filter;
|
|
658
854
|
if (parent) {
|
|
@@ -1015,6 +1211,10 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1015
1211
|
}
|
|
1016
1212
|
createProxyContextForPlugin(plugin) {
|
|
1017
1213
|
const proxyLogger = new Logger((message) => this.logHandler?.(message), `plugin:${this.name ? `${this.name}/` : ""}${plugin.name}`);
|
|
1214
|
+
const proxyRouter = this.router.withMeta({
|
|
1215
|
+
context: this.name,
|
|
1216
|
+
plugin: plugin.name
|
|
1217
|
+
});
|
|
1018
1218
|
let proxyInjections;
|
|
1019
1219
|
if (plugin.inject) {
|
|
1020
1220
|
proxyInjections = {};
|
|
@@ -1026,6 +1226,7 @@ and implement the dispose method to clean up resources when the context stops.
|
|
|
1026
1226
|
}
|
|
1027
1227
|
return new Proxy(this, { get(target, prop, receiver) {
|
|
1028
1228
|
if (prop === "logger") return proxyLogger;
|
|
1229
|
+
else if (prop === "router") return proxyRouter;
|
|
1029
1230
|
else if (proxyInjections && prop in proxyInjections) return proxyInjections[prop];
|
|
1030
1231
|
else return Reflect.get(target, prop, receiver);
|
|
1031
1232
|
} });
|
|
@@ -1382,4 +1583,4 @@ let param;
|
|
|
1382
1583
|
_param.segment = segment;
|
|
1383
1584
|
})(param || (param = {}));
|
|
1384
1585
|
//#endregion
|
|
1385
|
-
export { CommandBuilder, Context, Logger, Parameter, Router, combineLogHandlers, createMilkyClient, createMilkyWebSocketEventSource, definePlugin, filter, implementsESNextDisposable, isDisposable, milkyPackageVersion, milkyVersion, msg, param, seg };
|
|
1586
|
+
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.1",
|
|
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"
|