alemonjs 2.1.68 → 2.1.70

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.
@@ -0,0 +1,722 @@
1
+ import { defineRouter } from '../define-router.js';
2
+ import '../store.js';
3
+ import '../../core/utils.js';
4
+ import 'flatted';
5
+ import 'fs';
6
+ import 'path';
7
+ import 'yaml';
8
+ import '../../cbp/processor/config.js';
9
+ import { Format } from '../message-format.js';
10
+ import '../hook-event-context.js';
11
+ import { useMessage } from '../hook-use/message.js';
12
+ import '../message-format-old.js';
13
+ import { checkFallbackHint } from './fallback.js';
14
+ import { normalizeRoutePath, parseMessageText } from './parser.js';
15
+ import { validateRouteArgsForCommand } from './validator.js';
16
+
17
+ function normalizePathCondition(path) {
18
+ if (path === undefined) {
19
+ return undefined;
20
+ }
21
+ return normalizeRoutePath(path);
22
+ }
23
+ function mergeConditions(parent, current) {
24
+ const parentPath = normalizePathCondition(parent.path);
25
+ const currentPath = normalizePathCondition(current.path);
26
+ let path;
27
+ if (currentPath !== undefined) {
28
+ path = parentPath ? normalizeRoutePath(`${parentPath} ${currentPath}`) : currentPath;
29
+ }
30
+ else {
31
+ path = parentPath;
32
+ }
33
+ return {
34
+ path,
35
+ events: current.events ?? parent.events,
36
+ platforms: current.platforms ?? parent.platforms,
37
+ routeText: current.routeText ?? parent.routeText,
38
+ keyPolicy: current.keyPolicy ?? parent.keyPolicy,
39
+ duplicateKey: current.duplicateKey ?? parent.duplicateKey,
40
+ fallback: current.fallback ?? parent.fallback,
41
+ redispatch: current.redispatch ?? parent.redispatch
42
+ };
43
+ }
44
+ function normalizeRouteConfig(config) {
45
+ if (typeof config === 'string') {
46
+ return {
47
+ path: normalizeRoutePath(config)
48
+ };
49
+ }
50
+ const path = config.path;
51
+ if (!path) {
52
+ throw new Error('route path is required');
53
+ }
54
+ return {
55
+ path: normalizeRoutePath(path),
56
+ events: config.events,
57
+ platforms: config.platforms,
58
+ schema: config.schema
59
+ };
60
+ }
61
+ function normalizeResConfig(config, defaults) {
62
+ return {
63
+ events: defaults.events ?? config.events,
64
+ platforms: defaults.platforms ?? config.platforms,
65
+ regular: defaults.regular ?? config.regular
66
+ };
67
+ }
68
+ function getImporterLabel(importer, index) {
69
+ return importer.name || `anonymous_${index + 1}`;
70
+ }
71
+ function normalizeInteractionKey(messageText) {
72
+ return String(messageText ?? '')
73
+ .trim()
74
+ .replace(/^notAutoConfirmation:/, '');
75
+ }
76
+ function getEventPlatform(event) {
77
+ return typeof event.Platform === 'string' ? event.Platform : undefined;
78
+ }
79
+ function getPlatformRouteTextRule(routeText, platform) {
80
+ if (!routeText) {
81
+ return undefined;
82
+ }
83
+ if (platform && routeText.byPlatform?.[platform]) {
84
+ const platformRule = routeText.byPlatform[platform];
85
+ return {
86
+ prefixes: platformRule.prefixes ?? routeText.prefixes,
87
+ stripPrefix: platformRule.stripPrefix ?? routeText.stripPrefix,
88
+ allowBare: platformRule.allowBare ?? routeText.allowBare
89
+ };
90
+ }
91
+ return {
92
+ prefixes: routeText.prefixes,
93
+ stripPrefix: routeText.stripPrefix,
94
+ allowBare: routeText.allowBare
95
+ };
96
+ }
97
+ function normalizeScopedRouteText(messageText, routeText) {
98
+ const rawText = String(messageText ?? '').trim();
99
+ if (!rawText) {
100
+ return null;
101
+ }
102
+ if (!routeText?.prefixes?.length) {
103
+ return rawText;
104
+ }
105
+ const prefix = routeText.prefixes.find(item => rawText.startsWith(item));
106
+ if (!prefix) {
107
+ if (routeText.allowBare) {
108
+ return rawText;
109
+ }
110
+ return null;
111
+ }
112
+ if (!routeText.stripPrefix) {
113
+ return rawText;
114
+ }
115
+ const stripped = rawText.slice(prefix.length).trim();
116
+ return stripped || null;
117
+ }
118
+ function getRouteMessageText(event) {
119
+ const routeMessageText = event.__route?.text;
120
+ if (typeof routeMessageText === 'string' && routeMessageText.trim()) {
121
+ return routeMessageText;
122
+ }
123
+ return event.MessageText;
124
+ }
125
+ function getLookupCandidates(event, keyPolicy) {
126
+ const eventName = String(event.name ?? '');
127
+ const routeMessageText = getRouteMessageText(event);
128
+ if (eventName.includes('interaction')) {
129
+ const interactionKey = normalizeInteractionKey(routeMessageText);
130
+ if (!interactionKey) {
131
+ return null;
132
+ }
133
+ const underscoreIndex = interactionKey.indexOf('_');
134
+ const prefixKey = interactionKey.startsWith('select') ? 'select' : underscoreIndex > 0 ? interactionKey.slice(0, underscoreIndex) : interactionKey;
135
+ return {
136
+ normalizedCommand: interactionKey,
137
+ rawArgs: [],
138
+ candidates: [
139
+ { key: interactionKey, keyLength: 1 },
140
+ { key: prefixKey, keyLength: 1 }
141
+ ]
142
+ };
143
+ }
144
+ const parsed = parseMessageText(routeMessageText);
145
+ if (!parsed) {
146
+ return null;
147
+ }
148
+ const oneKey = parsed.tokens[0] ?? '';
149
+ const twoKey = parsed.tokens.length >= 2 ? `${parsed.tokens[0]} ${parsed.tokens[1]}` : '';
150
+ const maxWords = keyPolicy?.maxWords ?? 2;
151
+ const candidates = maxWords === 1
152
+ ? [{ key: oneKey, keyLength: 1 }]
153
+ : [
154
+ { key: twoKey, keyLength: 2 },
155
+ { key: oneKey, keyLength: 1 }
156
+ ];
157
+ return {
158
+ normalizedCommand: parsed.normalizedText,
159
+ rawArgs: parsed.tokens,
160
+ candidates
161
+ };
162
+ }
163
+ function buildRouteParams(route, parsedArgs) {
164
+ const params = {};
165
+ const args = route.config.schema?.args ?? [];
166
+ for (let index = 0; index < args.length; index += 1) {
167
+ params[args[index].name] = parsedArgs[index];
168
+ }
169
+ return params;
170
+ }
171
+ function attachRouteContext(event, context) {
172
+ event.__route = context;
173
+ }
174
+ function isOnResponseValue(value) {
175
+ return typeof value === 'object' && value !== null && 'current' in value;
176
+ }
177
+ function hasSelectValue(value) {
178
+ return typeof value === 'object' && value !== null && 'select' in value;
179
+ }
180
+ function isExecutable(value) {
181
+ return typeof value === 'function';
182
+ }
183
+ function unwrapImportedValue(value) {
184
+ if (typeof value === 'object' && value !== null && 'default' in value) {
185
+ return value.default;
186
+ }
187
+ return value;
188
+ }
189
+ function toExecutableList(value, eventName) {
190
+ const unwrapped = unwrapImportedValue(value);
191
+ if (eventName && hasSelectValue(unwrapped) && unwrapped.select) {
192
+ const selects = Array.isArray(unwrapped.select) ? unwrapped.select : [unwrapped.select];
193
+ if (!selects.includes(eventName)) {
194
+ return [];
195
+ }
196
+ }
197
+ if (isExecutable(unwrapped)) {
198
+ return [unwrapped];
199
+ }
200
+ if (!isOnResponseValue(unwrapped)) {
201
+ return [];
202
+ }
203
+ if (Array.isArray(unwrapped.current)) {
204
+ return unwrapped.current.filter(isExecutable);
205
+ }
206
+ return isExecutable(unwrapped.current) ? [unwrapped.current] : [];
207
+ }
208
+ class RouteGroup {
209
+ app;
210
+ middlewares;
211
+ options;
212
+ scopeId;
213
+ constructor(app, middlewares, options, scopeId) {
214
+ this.app = app;
215
+ this.middlewares = middlewares;
216
+ this.options = options;
217
+ this.scopeId = scopeId;
218
+ }
219
+ group(options, ...middlewares) {
220
+ return new RouteGroup(this.app, [...this.middlewares, ...middlewares], mergeConditions(this.options, options), this.scopeId);
221
+ }
222
+ use(config, ...importers) {
223
+ return this.app.register(config, [...this.middlewares, ...importers], this.options, this.scopeId);
224
+ }
225
+ }
226
+ class Router {
227
+ defaults;
228
+ globalImporters = [];
229
+ topLevelEntries = [];
230
+ scopes = new Map();
231
+ routes = new Map();
232
+ defaultScopeId;
233
+ constructor(options = {}) {
234
+ this.defaults = {
235
+ regular: options.regular,
236
+ events: options.events,
237
+ platforms: options.platforms
238
+ };
239
+ }
240
+ static create(options = {}) {
241
+ return new Router(options);
242
+ }
243
+ group(options, ...middlewares) {
244
+ const mergedOptions = mergeConditions({ events: this.defaults.events, platforms: this.defaults.platforms }, options);
245
+ const scopeId = this.createScope(mergedOptions);
246
+ return new RouteGroup(this, middlewares, mergedOptions, scopeId);
247
+ }
248
+ use(config, ...importers) {
249
+ const scopeId = this.ensureDefaultScope();
250
+ return this.register(config, importers, { events: this.defaults.events, platforms: this.defaults.platforms }, scopeId);
251
+ }
252
+ res(config, ...importers) {
253
+ const res = {
254
+ config: normalizeResConfig(config, this.defaults),
255
+ importers
256
+ };
257
+ this.globalImporters.push(res);
258
+ this.topLevelEntries.push({
259
+ kind: 'res',
260
+ res
261
+ });
262
+ return this.globalImporters;
263
+ }
264
+ register(config, importers, inheritedOptions, scopeId) {
265
+ if (Array.isArray(config)) {
266
+ return config.map(item => this.registerOne(item, importers, inheritedOptions, scopeId));
267
+ }
268
+ return this.registerOne(config, importers, inheritedOptions, scopeId);
269
+ }
270
+ registerOne(config, importers, inheritedOptions, scopeId) {
271
+ const normalized = normalizeRouteConfig(config);
272
+ const mergedOptions = mergeConditions(inheritedOptions, {
273
+ path: normalized.path,
274
+ events: normalized.events,
275
+ platforms: normalized.platforms
276
+ });
277
+ const fullPath = normalizePathCondition(mergedOptions.path) ?? '';
278
+ const events = mergedOptions.events ?? [];
279
+ const platforms = mergedOptions.platforms;
280
+ const tokenLength = fullPath.split(/\s+/).filter(Boolean).length;
281
+ const keyLength = tokenLength >= 2 ? 2 : 1;
282
+ if (events.length === 0) {
283
+ throw new Error(`route "${fullPath}" must declare events`);
284
+ }
285
+ const route = {
286
+ config: {
287
+ ...normalized,
288
+ path: fullPath,
289
+ events,
290
+ platforms
291
+ },
292
+ scopeId,
293
+ keyLength,
294
+ importers,
295
+ inheritedImporters: importers.slice(0, Math.max(importers.length - 1, 0))
296
+ };
297
+ for (const eventName of events) {
298
+ const routes = this.routes.get(eventName) ?? {
299
+ one: new Map(),
300
+ two: new Map()
301
+ };
302
+ const targetRoutes = keyLength === 2 ? routes.two : routes.one;
303
+ const existedRoute = targetRoutes.get(fullPath);
304
+ if (existedRoute) {
305
+ const duplicateMessage = `[router-map] 检测到重复指令注册: event=${eventName} path=${fullPath} oldScope=${existedRoute.scopeId} newScope=${scopeId}`;
306
+ const duplicateStrategy = mergedOptions.duplicateKey ?? 'warn';
307
+ if (duplicateStrategy === 'throw') {
308
+ throw new Error(duplicateMessage);
309
+ }
310
+ if (duplicateStrategy === 'warn') {
311
+ logger.warn(duplicateMessage);
312
+ }
313
+ }
314
+ targetRoutes.set(fullPath, route);
315
+ this.routes.set(eventName, routes);
316
+ }
317
+ return route;
318
+ }
319
+ inspect() {
320
+ return Array.from(this.routes.entries()).map(([eventName, routes]) => ({
321
+ eventName,
322
+ routes: [...routes.two.values(), ...routes.one.values()].map(route => ({
323
+ path: route.config.path,
324
+ importerCount: route.importers.length
325
+ }))
326
+ }));
327
+ }
328
+ get values() {
329
+ return [...this.buildTopLevelEntries(), this.buildFallbackEntry()];
330
+ }
331
+ get define() {
332
+ return defineRouter(this.values);
333
+ }
334
+ match(event, scopeId) {
335
+ const eventName = String(event.name ?? '');
336
+ const scope = scopeId ? this.scopes.get(scopeId) : undefined;
337
+ const lookup = getLookupCandidates(event, scope?.options.keyPolicy);
338
+ if (!eventName || !lookup) {
339
+ return { matched: false };
340
+ }
341
+ const routes = this.routes.get(eventName);
342
+ if (!routes) {
343
+ return { matched: false };
344
+ }
345
+ const candidates = lookup.candidates.map(candidate => ({
346
+ key: candidate.key,
347
+ route: candidate.keyLength === 2 ? routes.two.get(candidate.key) : routes.one.get(candidate.key)
348
+ }));
349
+ for (const candidate of candidates) {
350
+ const route = candidate.route;
351
+ if (!route) {
352
+ continue;
353
+ }
354
+ if (scopeId && route.scopeId !== scopeId) {
355
+ continue;
356
+ }
357
+ const platform = getEventPlatform(event);
358
+ if (route.config.platforms?.length && platform && !route.config.platforms.includes(platform)) {
359
+ continue;
360
+ }
361
+ const rawArgs = lookup.rawArgs.slice(route.keyLength);
362
+ const validation = validateRouteArgsForCommand(route.config.path, rawArgs, route.config.schema);
363
+ if (!validation.valid) {
364
+ return {
365
+ matched: true,
366
+ route,
367
+ eventName,
368
+ normalizedCommand: lookup.normalizedCommand,
369
+ rawArgs,
370
+ parsedArgs: [],
371
+ validation,
372
+ matchedPath: candidate.key,
373
+ importerCount: route.importers.length,
374
+ importerLabels: route.importers.map(getImporterLabel)
375
+ };
376
+ }
377
+ return {
378
+ matched: true,
379
+ route,
380
+ eventName,
381
+ normalizedCommand: lookup.normalizedCommand,
382
+ rawArgs,
383
+ parsedArgs: validation.parsedArgs,
384
+ validation,
385
+ matchedPath: candidate.key,
386
+ importerCount: route.importers.length,
387
+ importerLabels: route.importers.map(getImporterLabel)
388
+ };
389
+ }
390
+ return { matched: false };
391
+ }
392
+ dispatch(event, depth = 0) {
393
+ return this.dispatchInScope(event, undefined, depth);
394
+ }
395
+ async dispatchInScope(event, scopeId, depth = 0) {
396
+ const scope = scopeId ? this.scopes.get(scopeId) : undefined;
397
+ const maxRedispatchDepth = scope?.options.redispatch?.maxDepth ?? 3;
398
+ if (depth > maxRedispatchDepth) {
399
+ return {
400
+ matched: false,
401
+ stopped: true,
402
+ reason: 'redispatch_limit',
403
+ eventName: String(event.name ?? ''),
404
+ rawArgs: [],
405
+ parsedArgs: [],
406
+ importerCount: 0,
407
+ importerLabels: []
408
+ };
409
+ }
410
+ const match = this.match(event, scopeId);
411
+ if (!match.matched) {
412
+ return {
413
+ matched: false,
414
+ stopped: false,
415
+ reason: 'unmatched',
416
+ eventName: String(event.name ?? ''),
417
+ rawArgs: [],
418
+ parsedArgs: [],
419
+ importerCount: 0,
420
+ importerLabels: []
421
+ };
422
+ }
423
+ if (!match.validation.valid) {
424
+ attachRouteContext(event, {
425
+ key: match.route.config.path,
426
+ text: match.normalizedCommand,
427
+ rawArgs: match.rawArgs,
428
+ parsedArgs: [],
429
+ params: {}
430
+ });
431
+ return {
432
+ matched: true,
433
+ stopped: true,
434
+ reason: 'validation_failed',
435
+ eventName: match.eventName,
436
+ commandKey: match.route.config.path,
437
+ matchedPath: match.matchedPath,
438
+ normalizedCommand: match.normalizedCommand,
439
+ rawArgs: match.rawArgs,
440
+ parsedArgs: [],
441
+ params: {},
442
+ importerCount: match.importerCount,
443
+ importerLabels: match.importerLabels,
444
+ validation: match.validation
445
+ };
446
+ }
447
+ const initialMessageText = String(event.MessageText ?? '');
448
+ const params = buildRouteParams(match.route, match.parsedArgs);
449
+ attachRouteContext(event, {
450
+ key: match.route.config.path,
451
+ text: match.normalizedCommand,
452
+ rawArgs: match.rawArgs,
453
+ parsedArgs: match.parsedArgs,
454
+ params
455
+ });
456
+ const executed = await this.runImporters(match.route.importers, event, depth);
457
+ return {
458
+ matched: true,
459
+ stopped: executed.stopped,
460
+ reason: executed.reason,
461
+ eventName: match.eventName,
462
+ commandKey: match.route.config.path,
463
+ matchedPath: match.matchedPath,
464
+ normalizedCommand: match.normalizedCommand,
465
+ rawArgs: match.rawArgs,
466
+ parsedArgs: match.parsedArgs,
467
+ params,
468
+ importerCount: match.importerCount,
469
+ importerLabels: match.importerLabels,
470
+ validation: match.validation,
471
+ rewrittenMessageText: initialMessageText === String(event.MessageText ?? '') ? undefined : String(event.MessageText ?? '')
472
+ };
473
+ }
474
+ async handleScopeEvent(scopeId, event, next) {
475
+ const scope = this.scopes.get(scopeId);
476
+ const previousRoute = event.__route;
477
+ const platform = getEventPlatform(event);
478
+ const routeTextRule = getPlatformRouteTextRule(scope?.options.routeText, platform);
479
+ const scopedText = normalizeScopedRouteText(typeof event.MessageText === 'string' ? event.MessageText : undefined, routeTextRule);
480
+ if (routeTextRule && !scopedText) {
481
+ await next();
482
+ return;
483
+ }
484
+ if (scopedText) {
485
+ attachRouteContext(event, {
486
+ key: '',
487
+ text: scopedText,
488
+ rawArgs: [],
489
+ parsedArgs: [],
490
+ params: {}
491
+ });
492
+ }
493
+ const result = await this.dispatchInScope(event, scopeId);
494
+ if (!result.matched && result.reason === 'unmatched') {
495
+ if (previousRoute) {
496
+ event.__route = previousRoute;
497
+ }
498
+ else {
499
+ delete event.__route;
500
+ }
501
+ await next();
502
+ return;
503
+ }
504
+ this.replyValidationFailure(result);
505
+ }
506
+ async handleFallbackEvent(event, next) {
507
+ const applicableScopeIds = this.getApplicableScopeIds(event);
508
+ if (applicableScopeIds.length === 0) {
509
+ await next();
510
+ return;
511
+ }
512
+ const fallbackOptions = applicableScopeIds.map(scopeId => this.scopes.get(scopeId)?.options.fallback).find(Boolean) ?? {
513
+ suggest: true,
514
+ allowPrefixMatch: true
515
+ };
516
+ if (fallbackOptions.suggest === false) {
517
+ await next();
518
+ return;
519
+ }
520
+ const eventName = String(event.name ?? '');
521
+ const routeKeys = this.inspect()
522
+ .filter(item => item.eventName === eventName)
523
+ .flatMap(item => item.routes)
524
+ .filter(route => {
525
+ const routeEntry = this.routes.get(eventName)?.one.get(route.path) ?? this.routes.get(eventName)?.two.get(route.path);
526
+ return routeEntry ? applicableScopeIds.includes(routeEntry.scopeId) : false;
527
+ })
528
+ .map(route => route.path);
529
+ const fallback = checkFallbackHint(typeof event.MessageText === 'string' ? event.MessageText : undefined, routeKeys, fallbackOptions);
530
+ if (!fallback.matched) {
531
+ await next();
532
+ return;
533
+ }
534
+ const [message] = useMessage();
535
+ const md = Format.createMarkdown();
536
+ const format = Format.create();
537
+ md.addText(fallback.message ?? '未匹配到可用指令');
538
+ format.addMarkdown(md);
539
+ void message.send({ format });
540
+ }
541
+ replyValidationFailure(result) {
542
+ if (result.reason !== 'validation_failed') {
543
+ return;
544
+ }
545
+ const validation = result.validation;
546
+ const [message] = useMessage();
547
+ const md = Format.createMarkdown();
548
+ const format = Format.create();
549
+ md.addText(validation && !validation.valid ? validation.error : '参数校验失败');
550
+ if (validation && !validation.valid && validation.usage) {
551
+ md.addNewline();
552
+ md.addText(`应使用:${validation.usage}`);
553
+ }
554
+ else if (result.commandKey) {
555
+ md.addNewline();
556
+ md.addText(`请补全 \`${result.commandKey}\` 所需参数。`);
557
+ }
558
+ format.addMarkdown(md);
559
+ void message.send({ format });
560
+ }
561
+ buildTopLevelEntries() {
562
+ return this.topLevelEntries.flatMap(item => (item.kind === 'res' ? this.buildResEntriesFor(item.res) : this.buildScopeEntriesFor(item.scopeId)));
563
+ }
564
+ buildResEntriesFor(item) {
565
+ const eventNames = item.config.events ?? [];
566
+ const regular = item.config.regular;
567
+ const platforms = item.config.platforms;
568
+ return item.importers
569
+ .map(importer => {
570
+ const base = {
571
+ selects: eventNames,
572
+ regular,
573
+ handler: async () => unwrapImportedValue(await importer())
574
+ };
575
+ if (!platforms?.length) {
576
+ return base;
577
+ }
578
+ return {
579
+ ...base,
580
+ platform: [...platforms]
581
+ };
582
+ })
583
+ .flat();
584
+ }
585
+ buildScopeEntriesFor(scopeId) {
586
+ const scope = this.scopes.get(scopeId);
587
+ if (!scope) {
588
+ return [];
589
+ }
590
+ const selects = (scope.options.events ?? this.defaults.events ?? Array.from(this.routes.keys()));
591
+ const platforms = scope.options.platforms;
592
+ const base = {
593
+ selects,
594
+ handler: () => {
595
+ return Promise.resolve(async (event, next) => {
596
+ await this.handleScopeEvent(scopeId, event, next);
597
+ });
598
+ }
599
+ };
600
+ if (!platforms?.length) {
601
+ return [base];
602
+ }
603
+ return [
604
+ {
605
+ ...base,
606
+ platform: [...platforms]
607
+ }
608
+ ];
609
+ }
610
+ buildFallbackEntry() {
611
+ const defaultEvents = this.defaults.events ?? [];
612
+ const eventNames = Array.from(new Set([...defaultEvents, ...Array.from(this.routes.keys())]));
613
+ return {
614
+ selects: eventNames,
615
+ handler: () => {
616
+ return Promise.resolve(async (event, next) => {
617
+ await this.handleFallbackEvent(event, next);
618
+ });
619
+ }
620
+ };
621
+ }
622
+ ensureDefaultScope() {
623
+ if (this.defaultScopeId) {
624
+ return this.defaultScopeId;
625
+ }
626
+ this.defaultScopeId = this.createScope({
627
+ events: this.defaults.events,
628
+ platforms: this.defaults.platforms
629
+ });
630
+ return this.defaultScopeId;
631
+ }
632
+ createScope(options) {
633
+ const id = `scope_${this.topLevelEntries.filter(item => item.kind === 'scope').length + 1}`;
634
+ this.scopes.set(id, {
635
+ id,
636
+ options
637
+ });
638
+ this.topLevelEntries.push({
639
+ kind: 'scope',
640
+ scopeId: id
641
+ });
642
+ return id;
643
+ }
644
+ getApplicableScopeIds(event) {
645
+ const eventName = String(event.name ?? '');
646
+ const platform = getEventPlatform(event);
647
+ const messageText = typeof event.MessageText === 'string' ? event.MessageText : undefined;
648
+ return this.topLevelEntries
649
+ .filter((item) => item.kind === 'scope')
650
+ .map(item => item.scopeId)
651
+ .filter(scopeId => {
652
+ const scope = this.scopes.get(scopeId);
653
+ if (!scope) {
654
+ return false;
655
+ }
656
+ if (scope.options.events?.length && !scope.options.events.includes(eventName)) {
657
+ return false;
658
+ }
659
+ if (scope.options.platforms?.length && platform && !scope.options.platforms.includes(platform)) {
660
+ return false;
661
+ }
662
+ const routeTextRule = getPlatformRouteTextRule(scope.options.routeText, platform);
663
+ if (!routeTextRule) {
664
+ return true;
665
+ }
666
+ return Boolean(normalizeScopedRouteText(messageText, routeTextRule));
667
+ });
668
+ }
669
+ runImporters(importers, event, depth) {
670
+ const visit = async (index) => {
671
+ if (index >= importers.length) {
672
+ return { stopped: false, reason: 'handler_completed' };
673
+ }
674
+ const beforeMessageText = String(event.MessageText ?? '');
675
+ const imported = await importers[index]();
676
+ const executableList = toExecutableList(imported, typeof event.name === 'string' ? event.name : undefined);
677
+ if (executableList.length === 0) {
678
+ return { stopped: true, reason: 'unsupported_handler' };
679
+ }
680
+ let downstreamRequested = false;
681
+ let downstreamResult = null;
682
+ const next = async () => {
683
+ downstreamRequested = true;
684
+ downstreamResult = await visit(index + 1);
685
+ };
686
+ for (const executable of executableList) {
687
+ const result = await executable(event, next);
688
+ if (downstreamRequested) {
689
+ if (downstreamResult) {
690
+ return downstreamResult;
691
+ }
692
+ break;
693
+ }
694
+ if (result === false) {
695
+ return { stopped: true, reason: 'handler_returned_false' };
696
+ }
697
+ if (result === true) {
698
+ continue;
699
+ }
700
+ return { stopped: true, reason: 'handler_completed' };
701
+ }
702
+ if (downstreamRequested) {
703
+ if (downstreamResult) {
704
+ return downstreamResult;
705
+ }
706
+ const afterMessageText = String(event.MessageText ?? '');
707
+ if (index === importers.length - 1 && afterMessageText && afterMessageText !== beforeMessageText) {
708
+ const redispatched = await this.dispatch(event, depth + 1);
709
+ return {
710
+ stopped: redispatched.stopped,
711
+ reason: redispatched.reason
712
+ };
713
+ }
714
+ return { stopped: false, reason: 'handler_completed' };
715
+ }
716
+ return await visit(index + 1);
717
+ };
718
+ return visit(0);
719
+ }
720
+ }
721
+
722
+ export { Router };