meocord 4.2.2 → 4.2.3
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/CHANGELOG.md +130 -0
- package/dist/cjs/_shared/{controller.decorator-U2NBWcoU.cjs → controller.decorator-hp5xdJU4.cjs} +512 -183
- package/dist/cjs/_shared/{startup-roots-lA5xkBFq.cjs → event-sources-DzqTODXg.cjs} +265 -80
- package/dist/cjs/_shared/{providers-B4MOFBrW.cjs → providers-BBvbnmLP.cjs} +780 -281
- package/dist/cjs/_shared/{sharded-cooldown-store-HTUb3Ii_.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-CbBHUu5P.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +432 -181
- package/dist/cjs/decorator/index.cjs +85 -120
- package/dist/cjs/testing/index.cjs +1664 -784
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/package.json.template +9 -9
- package/dist/esm/bin/app-template/src/main.ts.template +3 -2
- package/dist/esm/bin/generator.js +6 -4
- package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
- package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
- package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
- package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
- package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
- package/dist/esm/bin/helper/service-generator.helper.js +6 -4
- package/dist/esm/bin/meocord.js +106 -48
- package/dist/esm/build/native-addons.js +75 -19
- package/dist/esm/build/rsbuild-config.js +11 -3
- package/dist/esm/build/typed-externals.js +15 -2
- package/dist/esm/common/cooldown-store.js +38 -14
- package/dist/esm/common/deprecation.js +4 -1
- package/dist/esm/common/execution-context.js +3 -1
- package/dist/esm/common/explained-error.js +7 -3
- package/dist/esm/common/log-level.js +13 -9
- package/dist/esm/common/logger.js +4 -2
- package/dist/esm/common/meocord-text.js +11 -6
- package/dist/esm/common/redis-cooldown-store.js +3 -7
- package/dist/esm/common/response/flags.js +17 -1
- package/dist/esm/common/response/install-context.js +27 -16
- package/dist/esm/common/response/presenter.js +7 -3
- package/dist/esm/common/response/response-state.js +27 -5
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +17 -8
- package/dist/esm/core/autocomplete-routes.js +26 -0
- package/dist/esm/core/command-conflicts.js +15 -7
- package/dist/esm/core/command-registration.js +15 -1
- package/dist/esm/core/cooldown-runner.js +7 -9
- package/dist/esm/core/defer.js +2 -5
- package/dist/esm/core/dispatcher.js +83 -58
- package/dist/esm/core/event-requirements.js +67 -19
- package/dist/esm/core/event-sources.js +100 -0
- package/dist/esm/core/fallback.js +3 -1
- package/dist/esm/core/filter-runner.js +4 -4
- package/dist/esm/core/guard-runner.js +173 -52
- package/dist/esm/core/handler-input.js +24 -3
- package/dist/esm/core/handler-pipeline.js +44 -28
- package/dist/esm/core/handler-registry.js +51 -21
- package/dist/esm/core/input-runner.js +7 -8
- package/dist/esm/core/interceptor-runner.js +4 -5
- package/dist/esm/core/lifecycle-hooks.js +3 -8
- package/dist/esm/core/lifecycle-order.js +1 -15
- package/dist/esm/core/meocord-classes.js +14 -1
- package/dist/esm/core/meocord-factory.js +80 -53
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +5 -9
- package/dist/esm/core/message-params.js +126 -46
- package/dist/esm/core/message-routes.js +225 -45
- package/dist/esm/core/message-words.js +30 -2
- package/dist/esm/core/observer-runner.js +2 -1
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +28 -3
- package/dist/esm/core/scalar-types.js +57 -16
- package/dist/esm/core/shard-context.js +11 -10
- package/dist/esm/core/shard-exit.js +3 -3
- package/dist/esm/core/shard-manager.js +16 -6
- package/dist/esm/core/startup-roots.js +28 -8
- package/dist/esm/core/theme-resolvers.js +26 -11
- package/dist/esm/core/theme-runtime.js +23 -13
- package/dist/esm/core/theme-scope.js +27 -29
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +6 -1
- package/dist/esm/decorator/command-builder.decorator.js +14 -0
- package/dist/esm/decorator/controller-class.decorator.js +1 -1
- package/dist/esm/decorator/controller.decorator.js +48 -19
- package/dist/esm/decorator/defer.decorator.js +2 -2
- package/dist/esm/decorator/event.decorator.js +4 -5
- package/dist/esm/decorator/filter.decorator.js +2 -2
- package/dist/esm/decorator/guard.decorator.js +4 -61
- package/dist/esm/decorator/inject.decorator.js +4 -1
- package/dist/esm/decorator/interceptor.decorator.js +2 -2
- package/dist/esm/decorator/theme.decorator.js +4 -3
- package/dist/esm/decorator/validation.decorator.js +5 -5
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +29 -13
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -355
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +195 -77
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +639 -113
- package/dist/esm/testing/response-log.js +8 -0
- package/dist/esm/testing/response.js +7 -5
- package/dist/esm/testing/routing.js +48 -6
- package/dist/esm/testing/strict-mocks.js +1 -0
- package/dist/esm/testing/theme.js +3 -2
- package/dist/esm/util/common.util.js +30 -9
- package/dist/esm/util/generator-cli.util.js +6 -4
- package/dist/esm/util/inherited-env.util.js +72 -13
- package/dist/esm/util/json.util.js +10 -3
- package/dist/esm/util/meocord-cli.util.js +3 -2
- package/dist/esm/util/meocord-config-loader.util.js +14 -1
- package/dist/esm/util/meocord-config-validation.util.js +13 -3
- package/dist/esm/util/meocord-source-config.util.js +13 -3
- package/dist/esm/util/platform.util.js +3 -2
- package/dist/esm/util/refusal.util.js +29 -11
- package/dist/esm/util/registration-mode.util.js +14 -1
- package/dist/esm/util/timer-limit.util.js +4 -1
- package/dist/esm/util/tsconfig-paths.util.js +38 -7
- package/dist/esm/util/tsconfig.util.js +16 -58
- package/dist/esm/util/user-text.util.js +8 -4
- package/dist/esm/util/value.util.js +15 -4
- package/dist/types/common/index.d.cts +13 -9
- package/dist/types/common/index.d.ts +13 -9
- package/dist/types/core/index.d.cts +21 -11
- package/dist/types/core/index.d.ts +21 -11
- package/dist/types/decorator/index.d.cts +63 -63
- package/dist/types/decorator/index.d.ts +63 -63
- package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
- package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
- package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
- package/dist/types/testing/index.d.cts +137 -54
- package/dist/types/testing/index.d.ts +137 -54
- package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
- package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
- package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
- package/package.json +5 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var controller_decorator = require('./controller.decorator-
|
|
3
|
+
var controller_decorator = require('./controller.decorator-hp5xdJU4.cjs');
|
|
4
4
|
require('reflect-metadata');
|
|
5
5
|
var node_async_hooks = require('node:async_hooks');
|
|
6
6
|
var inversify = require('inversify');
|
|
@@ -11,8 +11,6 @@ var fs = require('fs');
|
|
|
11
11
|
var path = require('path');
|
|
12
12
|
var Table = require('cli-table3');
|
|
13
13
|
var node_crypto = require('node:crypto');
|
|
14
|
-
require('node:fs');
|
|
15
|
-
require('node:url');
|
|
16
14
|
|
|
17
15
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
18
16
|
|
|
@@ -104,13 +102,12 @@ function eventDecorator(event, once) {
|
|
|
104
102
|
// Generic over the method, so it is checked against the event's arguments rather than required to match exactly
|
|
105
103
|
return controller_decorator.declaring(function(target, propertyKey, _descriptor) {
|
|
106
104
|
controller_decorator.refuseOnClass(once ? '@Once' : '@On', target, propertyKey);
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
// One the class inherits for the same method and event is replaced, as for every other kind of handler
|
|
106
|
+
controller_decorator.addOwnHandler(controller_decorator.META.eventHandlers, target, {
|
|
109
107
|
event,
|
|
110
108
|
method: propertyKey,
|
|
111
109
|
once
|
|
112
|
-
});
|
|
113
|
-
Reflect.defineMetadata(controller_decorator.META.eventHandlers, handlers, target);
|
|
110
|
+
}, (other)=>other.method === propertyKey && other.event === event);
|
|
114
111
|
});
|
|
115
112
|
}
|
|
116
113
|
/**
|
|
@@ -306,14 +303,14 @@ const perCallKeys = new WeakMap();
|
|
|
306
303
|
});
|
|
307
304
|
}
|
|
308
305
|
}
|
|
309
|
-
const logger$
|
|
306
|
+
const logger$5 = new controller_decorator.Logger('Guard');
|
|
310
307
|
const warnedShared = new WeakMap();
|
|
311
308
|
/** Warns once per guard in each app that its params are set on the one instance every call shares, and why. */ function warnSharedParams(container, guardClass, why, remedy) {
|
|
312
309
|
let warned = warnedShared.get(container);
|
|
313
310
|
if (!warned) warnedShared.set(container, warned = new Set());
|
|
314
311
|
if (warned.has(guardClass)) return;
|
|
315
312
|
warned.add(guardClass);
|
|
316
|
-
logger$
|
|
313
|
+
logger$5.warn(`${guardClass.name} is one instance every call shares, and ${why}, so each call's params are set on that ` + `instance: calls that overlap can read each other's. ${remedy}, or stop binding the guard, in services, ` + `providers or a constructor that injects it, so each call gets its own.`);
|
|
317
314
|
}
|
|
318
315
|
/** Whether each of these properties can be made to read the call's params, which a frozen or sealed instance's cannot. */ function canReadPerCall(instance, keys) {
|
|
319
316
|
return keys.every((key)=>{
|
|
@@ -427,6 +424,28 @@ const warnedShared = new WeakMap();
|
|
|
427
424
|
}
|
|
428
425
|
return undefined;
|
|
429
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* The prototypes on the chain that declare `methodName` as written, base first: the handler and each override of it,
|
|
429
|
+
* looking through methods a class-level `@UseGuard` re-declared to wrap an inherited handler. A handler's method-level
|
|
430
|
+
* stages are read from each, so an override keeps its base method's, as it keeps its route.
|
|
431
|
+
*/ function stageOwners(prototype, methodName) {
|
|
432
|
+
const owners = [];
|
|
433
|
+
for(let current = prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)){
|
|
434
|
+
if (!Object.prototype.hasOwnProperty.call(current, methodName)) continue;
|
|
435
|
+
if (!Reflect.getOwnMetadata(controller_decorator.META.inheritedFrom, current, methodName)) owners.push(current);
|
|
436
|
+
}
|
|
437
|
+
return owners.reverse();
|
|
438
|
+
}
|
|
439
|
+
/** The entries a method-level stage lists under `key` on each of a handler's {@link stageOwners}, base first. */ function methodStages(prototype, methodName, key) {
|
|
440
|
+
return stageOwners(prototype, methodName).flatMap((owner)=>Reflect.getOwnMetadata(key, owner, methodName) ?? []);
|
|
441
|
+
}
|
|
442
|
+
/** The value a method-level stage holds under `key` on the nearest of a handler's {@link stageOwners} that declares one. */ function nearestMethodStage(prototype, methodName, key) {
|
|
443
|
+
for (const owner of stageOwners(prototype, methodName).reverse()){
|
|
444
|
+
const value = Reflect.getOwnMetadata(key, owner, methodName);
|
|
445
|
+
if (value !== undefined) return value;
|
|
446
|
+
}
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
430
449
|
/**
|
|
431
450
|
* Caches a function of a class's prototype and one of its handlers. Handler metadata is fixed once
|
|
432
451
|
* the classes are decorated, so dispatch resolves each handler's stages once, not on every call.
|
|
@@ -478,50 +497,165 @@ const warnedShared = new WeakMap();
|
|
|
478
497
|
* The guards dispatch runs before a handler: its classes' class-level guards, outermost first, then
|
|
479
498
|
* the method's. A method that is no handler has only its own, since class guards apply to handlers.
|
|
480
499
|
*/ const handlerGuards = perHandler((prototype, methodName)=>{
|
|
481
|
-
|
|
482
|
-
if (!source) return [];
|
|
500
|
+
if (!sourcePrototype(prototype, methodName)) return [];
|
|
483
501
|
return [
|
|
484
502
|
...handlerMethods$1(prototype).has(methodName) ? stageClasses(prototype, methodName).flatMap(classLevelGuards) : [],
|
|
485
|
-
...
|
|
503
|
+
...methodStages(prototype, methodName, controller_decorator.META.methodGuards)
|
|
504
|
+
];
|
|
505
|
+
});
|
|
506
|
+
/**
|
|
507
|
+
* The type a direct call to a handler runs as, which is the one dispatch gives it: `'event'` for an `@On` or `@Once`
|
|
508
|
+
* handler, as its first argument may be a message or an interaction; otherwise read from the first argument.
|
|
509
|
+
*/ const directCallType = perHandler((prototype, methodName)=>{
|
|
510
|
+
if (!getEventHandlers(prototype).some((handler)=>handler.method === methodName)) return undefined;
|
|
511
|
+
const routed = [
|
|
512
|
+
...Object.values(controller_decorator.getCommandMap(prototype) ?? {}).flat().map((command)=>command.methodName),
|
|
513
|
+
...controller_decorator.getMessageHandlers(prototype).map((handler)=>handler.method),
|
|
514
|
+
...controller_decorator.getReactionHandlers(prototype).map((handler)=>handler.method),
|
|
515
|
+
...controller_decorator.getAutocompleteHandlers(prototype).map((handler)=>handler.methodName)
|
|
486
516
|
];
|
|
517
|
+
// A method dispatch also routes as a command, message or reaction has no one type
|
|
518
|
+
return routed.includes(methodName) ? undefined : 'event';
|
|
487
519
|
});
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
520
|
+
/** Whether `@UseGuard` wraps the method anywhere on the chain, an override's base included. */ const isWrapped = perHandler((prototype, methodName)=>{
|
|
521
|
+
for(let current = prototype; current; current = Object.getPrototypeOf(current)){
|
|
522
|
+
if (Object.prototype.hasOwnProperty.call(current, methodName) && Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, current, methodName)) return true;
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
491
525
|
});
|
|
492
526
|
/**
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
/** Takes one pass for the wrapper being entered, if dispatch left one for this call. */ function consumeDispatchMark(first, instance, methodName) {
|
|
502
|
-
const marks = dispatched.get(markKey(first, instance));
|
|
503
|
-
const remaining = marks?.get(methodName);
|
|
504
|
-
if (!marks || !remaining) return false;
|
|
505
|
-
if (remaining > 1) marks.set(methodName, remaining - 1);
|
|
506
|
-
else marks.delete(methodName);
|
|
527
|
+
* The pass of the guarded call running. Kept per call, so a concurrent call with the same object and method runs its own
|
|
528
|
+
* guards, and closed when the call returns, so work it leaves running, such as a timer, does not carry it.
|
|
529
|
+
*/ const guardedPasses = new node_async_hooks.AsyncLocalStorage();
|
|
530
|
+
/** Takes the pass for `wrapper` when it belongs to a call whose guards have already run and has not let it through yet. */ function takeGuardedPass(wrapper, first, instance, methodName) {
|
|
531
|
+
const pass = guardedPasses.getStore();
|
|
532
|
+
if (!pass?.open || pass.instance !== instance || pass.methodName !== methodName || pass.first !== first) return false;
|
|
533
|
+
if (pass.entered.has(wrapper)) return false;
|
|
534
|
+
pass.entered.add(wrapper);
|
|
507
535
|
return true;
|
|
508
536
|
}
|
|
537
|
+
/** Runs a call whose guards have run, with its pass; `wrapper` is the one that ran them, which it has entered. */ async function runWithPass(instance, methodName, args, call, wrapper) {
|
|
538
|
+
const pass = {
|
|
539
|
+
instance,
|
|
540
|
+
methodName,
|
|
541
|
+
first: args[0],
|
|
542
|
+
entered: new Set(wrapper ? [
|
|
543
|
+
wrapper
|
|
544
|
+
] : []),
|
|
545
|
+
open: true
|
|
546
|
+
};
|
|
547
|
+
try {
|
|
548
|
+
return await guardedPasses.run(pass, call);
|
|
549
|
+
} finally{
|
|
550
|
+
pass.open = false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Adds guards to a method's class or method list, then republishes the effective list under
|
|
555
|
+
* `META.guards`, in the order they run: the bases' before the class's own, and within one class or method the
|
|
556
|
+
* latest decorator first, since it wraps outermost.
|
|
557
|
+
*/ function recordGuards(key, guards, prototype, methodName) {
|
|
558
|
+
const existing = Reflect.getOwnMetadata(key, prototype, methodName) ?? [];
|
|
559
|
+
const bases = key === controller_decorator.META.classGuards ? Reflect.getOwnMetadata(controller_decorator.META.baseClassGuards, prototype, methodName) ?? 0 : 0;
|
|
560
|
+
Reflect.defineMetadata(key, [
|
|
561
|
+
...existing.slice(0, bases),
|
|
562
|
+
...guards,
|
|
563
|
+
...existing.slice(bases)
|
|
564
|
+
], prototype, methodName);
|
|
565
|
+
const [classGuards, inheritedGuards] = [
|
|
566
|
+
controller_decorator.META.classGuards,
|
|
567
|
+
controller_decorator.META.inheritedGuards
|
|
568
|
+
].map((list)=>Reflect.getOwnMetadata(list, prototype, methodName) ?? []);
|
|
569
|
+
// The method's guards and those of the methods it overrides, base first, as dispatch runs them
|
|
570
|
+
const methodGuards = methodStages(prototype, methodName, controller_decorator.META.methodGuards);
|
|
571
|
+
Reflect.defineMetadata(controller_decorator.META.guards, [
|
|
572
|
+
...inheritedGuards,
|
|
573
|
+
...classGuards,
|
|
574
|
+
...methodGuards
|
|
575
|
+
], prototype, methodName);
|
|
576
|
+
}
|
|
577
|
+
/** The classes {@link guardOwnHandlersWithBaseGuards} has handled, as `@Controller` and each app's startup both call it. */ const guardedWithBases = new WeakSet();
|
|
578
|
+
/**
|
|
579
|
+
* Gives each handler a class declares itself the class-level guards of the classes it extends,
|
|
580
|
+
* as its inherited handlers have: recorded under `META.guards`, and wrapped so a direct call
|
|
581
|
+
* runs them when no other guard wraps the handler. `@Controller` calls this as it decorates, and
|
|
582
|
+
* {@link prepareDirectCalls} for every class an app runs, a service included. Once per class.
|
|
583
|
+
*/ function guardOwnHandlersWithBaseGuards(target) {
|
|
584
|
+
if (guardedWithBases.has(target)) return;
|
|
585
|
+
guardedWithBases.add(target);
|
|
586
|
+
const prototype = target.prototype;
|
|
587
|
+
for (const methodName of handlerMethods$1(prototype)){
|
|
588
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
|
|
589
|
+
// Inherited handlers have the chain of the class that declares them
|
|
590
|
+
if (typeof descriptor?.value !== 'function' || Reflect.getOwnMetadata(controller_decorator.META.inheritedFrom, prototype, methodName)) continue;
|
|
591
|
+
// Base first, as dispatch runs them
|
|
592
|
+
const guards = classChain(prototype, prototype).slice(1).reverse().flatMap(classLevelGuards);
|
|
593
|
+
if (guards.length === 0) continue;
|
|
594
|
+
if (!Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, prototype, methodName)) {
|
|
595
|
+
applyGuards(descriptor, prototype, methodName);
|
|
596
|
+
Object.defineProperty(prototype, methodName, descriptor);
|
|
597
|
+
}
|
|
598
|
+
recordGuards(controller_decorator.META.inheritedGuards, guards, prototype, methodName);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
509
601
|
/** The container that made each instance, so a direct call runs in its own app or testing module. */ const instanceContainers = new WeakMap();
|
|
510
602
|
/**
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
|
|
603
|
+
* Readies direct calls to the handlers of the classes an app or testing module runs, controllers and services alike:
|
|
604
|
+
* - stamps each class with the container, and records each instance the container makes as its own, so two testing
|
|
605
|
+
* modules made from one class each run a direct call on their own instance in themselves;
|
|
606
|
+
* - gives each handler a class declares itself the class-level guards of the classes it extends, and wraps each
|
|
607
|
+
* override of a guarded handler, so a direct call to it runs the guards it inherits before its body.
|
|
608
|
+
*/ function prepareDirectCalls(container, classes) {
|
|
514
609
|
for (const cls of classes){
|
|
610
|
+
Reflect.defineMetadata(controller_decorator.META.container, container, cls);
|
|
611
|
+
// Each class on the chain, base first, so an override above the one the app runs is wrapped too
|
|
612
|
+
for (const owner of classesOf(cls).reverse())guardOwnHandlersWithBaseGuards(owner);
|
|
613
|
+
wrapGuardedOverrides(cls.prototype);
|
|
515
614
|
container.onActivation(cls, (_context, instance)=>{
|
|
516
615
|
if (typeof instance === 'object' && instance !== null) instanceContainers.set(instance, container);
|
|
517
616
|
return instance;
|
|
518
617
|
});
|
|
519
618
|
}
|
|
520
619
|
}
|
|
620
|
+
/** `cls` and the classes it extends, innermost first. */ function classesOf(cls) {
|
|
621
|
+
const classes = [];
|
|
622
|
+
for(let current = cls.prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)){
|
|
623
|
+
classes.push(current.constructor);
|
|
624
|
+
}
|
|
625
|
+
return classes;
|
|
626
|
+
}
|
|
627
|
+
/** Wraps each method on the chain that overrides a handler a base method guards and is not wrapped itself. */ function wrapGuardedOverrides(prototype) {
|
|
628
|
+
for (const methodName of handlerMethods$1(prototype)){
|
|
629
|
+
let guardedBelow = false;
|
|
630
|
+
for (const owner of stageOwners(prototype, methodName)){
|
|
631
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, methodName);
|
|
632
|
+
if (guardedBelow && typeof descriptor?.value === 'function' && !Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, owner, methodName)) {
|
|
633
|
+
applyGuards(descriptor, owner, methodName);
|
|
634
|
+
Object.defineProperty(owner, methodName, descriptor);
|
|
635
|
+
}
|
|
636
|
+
if ((Reflect.getOwnMetadata(controller_decorator.META.methodGuards, owner, methodName) ?? []).length > 0) guardedBelow = true;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Wraps a method so a direct call runs its guards first: the wrapper entered first runs the whole
|
|
642
|
+
* chain dispatch resolves for the handler, and lets the ones inside it through. A call from dispatch,
|
|
643
|
+
* which has already run the guards, passes through every wrapper.
|
|
644
|
+
*/ function applyGuards(descriptor, prototype, propertyKey) {
|
|
645
|
+
const originalMethod = descriptor.value;
|
|
646
|
+
// Which wrapper a call has passed, so it passes each once
|
|
647
|
+
const wrapper = {};
|
|
648
|
+
descriptor.value = controller_decorator.wrapsMethod(async function(...args) {
|
|
649
|
+
if (takeGuardedPass(wrapper, args[0], this, propertyKey)) return originalMethod.apply(this, args);
|
|
650
|
+
return runDirectCall(this, propertyKey, args, ()=>originalMethod.apply(this, args), wrapper);
|
|
651
|
+
}, originalMethod);
|
|
652
|
+
const wrappers = Reflect.getOwnMetadata(controller_decorator.META.guardWrappers, prototype, propertyKey) ?? 0;
|
|
653
|
+
Reflect.defineMetadata(controller_decorator.META.guardWrappers, wrappers + 1, prototype, propertyKey);
|
|
654
|
+
}
|
|
521
655
|
/**
|
|
522
|
-
* Runs a direct call to a guarded handler: the handler's guards, as dispatch resolves them, then
|
|
523
|
-
* call with a pass
|
|
524
|
-
*/ async function runDirectCall(instance, methodName, args, call) {
|
|
656
|
+
* Runs a direct call to a guarded handler: the handler's guards, as dispatch resolves and types them, then
|
|
657
|
+
* the call with a pass every wrapper inside the one entered takes, so no guard runs twice.
|
|
658
|
+
*/ async function runDirectCall(instance, methodName, args, call, wrapper) {
|
|
525
659
|
const prototype = Object.getPrototypeOf(instance);
|
|
526
660
|
const controller = instance.constructor;
|
|
527
661
|
// The container that made the instance, or else the app that made the class or a class it extends: a subclass the
|
|
@@ -530,48 +664,30 @@ const wrapperCount = perHandler((prototype, methodName)=>{
|
|
|
530
664
|
if (!container) {
|
|
531
665
|
throw new Error(`${controller.name}.${methodName}: @UseGuard runs its guards through the app that made ${controller.name}, and this ` + `instance was made outside one. Inject ${controller.name}, or call it on an instance the app made.`);
|
|
532
666
|
}
|
|
667
|
+
const type = directCallType(prototype, methodName);
|
|
533
668
|
if (!await runGuards(handlerGuards(prototype, methodName), {
|
|
534
669
|
container,
|
|
535
670
|
controller,
|
|
536
671
|
methodName,
|
|
537
|
-
args
|
|
672
|
+
args,
|
|
673
|
+
type
|
|
538
674
|
})) return undefined;
|
|
539
|
-
|
|
540
|
-
if (inner <= 0) return call();
|
|
541
|
-
const key = markKey(args[0], instance);
|
|
542
|
-
let marks = dispatched.get(key);
|
|
543
|
-
if (!marks) dispatched.set(key, marks = new Map());
|
|
544
|
-
marks.set(methodName, inner);
|
|
545
|
-
try {
|
|
546
|
-
return await call();
|
|
547
|
-
} finally{
|
|
548
|
-
marks.delete(methodName);
|
|
549
|
-
}
|
|
675
|
+
return runWithPass(instance, methodName, args, call, wrapper);
|
|
550
676
|
}
|
|
551
677
|
/**
|
|
552
|
-
* Calls a handler dispatch has already guarded. Its wrappers let this one call through; the
|
|
553
|
-
*
|
|
678
|
+
* Calls a handler dispatch has already guarded. Its wrappers let this one call through; the pass
|
|
679
|
+
* closes afterwards, so a later direct call with the same (cached) object runs its guards.
|
|
554
680
|
*/ async function callGuardedHandler(instance, methodName, args) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const key = markKey(args[0], instance);
|
|
558
|
-
let marks = dispatched.get(key);
|
|
559
|
-
if (!marks) dispatched.set(key, marks = new Map());
|
|
560
|
-
marks.set(methodName, count);
|
|
561
|
-
try {
|
|
562
|
-
return await instance[methodName](...args);
|
|
563
|
-
} finally{
|
|
564
|
-
marks.delete(methodName);
|
|
565
|
-
}
|
|
681
|
+
if (!isWrapped(Object.getPrototypeOf(instance), methodName)) return instance[methodName](...args);
|
|
682
|
+
return runWithPass(instance, methodName, args, ()=>instance[methodName](...args));
|
|
566
683
|
}
|
|
567
684
|
|
|
568
|
-
/** The cooldowns on a handler: each class's, base first, then the method's. */ const handlerCooldowns = perHandler((prototype, methodName)=>{
|
|
569
|
-
|
|
570
|
-
if (!source) return [];
|
|
685
|
+
/** The cooldowns on a handler: each class's, base first, then the method's and its overrides', base first. */ const handlerCooldowns = perHandler((prototype, methodName)=>{
|
|
686
|
+
if (!sourcePrototype(prototype, methodName)) return [];
|
|
571
687
|
return [
|
|
572
688
|
// Base first, as guards run
|
|
573
689
|
...stageClasses(prototype, methodName).flatMap((cls)=>Reflect.getOwnMetadata(controller_decorator.META.classCooldowns, cls) ?? []),
|
|
574
|
-
...
|
|
690
|
+
...methodCooldowns(prototype, methodName)
|
|
575
691
|
];
|
|
576
692
|
});
|
|
577
693
|
/** The counted cooldowns of each list {@link handlerCooldowns} gives, which is the same for a handler. */ const countedCooldowns = new WeakMap();
|
|
@@ -612,9 +728,8 @@ const wrapperCount = perHandler((prototype, methodName)=>{
|
|
|
612
728
|
}
|
|
613
729
|
return counted;
|
|
614
730
|
}
|
|
615
|
-
/** The cooldowns declared on a method itself. */ function methodCooldowns(prototype, methodName) {
|
|
616
|
-
|
|
617
|
-
return source ? Reflect.getOwnMetadata(controller_decorator.META.methodCooldowns, source, methodName) ?? [] : [];
|
|
731
|
+
/** The cooldowns declared on a method itself and on the methods it overrides, base first. */ function methodCooldowns(prototype, methodName) {
|
|
732
|
+
return methodStages(prototype, methodName, controller_decorator.META.methodCooldowns);
|
|
618
733
|
}
|
|
619
734
|
/** Who and where a call came from: an interaction's or a message's user, server and channel. */ function caller(first) {
|
|
620
735
|
if (first instanceof discord_js.BaseInteraction) return {
|
|
@@ -651,7 +766,7 @@ const DEFAULT_POLICY = {
|
|
|
651
766
|
/** The app's cooldown policy, else refusing calls after a second without an answer. */ function cooldownPolicyOf(container) {
|
|
652
767
|
return container.isBound(COOLDOWN_POLICY) ? container.get(COOLDOWN_POLICY) : DEFAULT_POLICY;
|
|
653
768
|
}
|
|
654
|
-
const logger$
|
|
769
|
+
const logger$4 = new controller_decorator.Logger('Cooldown');
|
|
655
770
|
/**
|
|
656
771
|
* How long after its last failure a store must answer for its outage to end. A store that fails some calls and answers
|
|
657
772
|
* others, as a cluster with one node down does, stays in one outage rather than starting a new one with each failure.
|
|
@@ -672,15 +787,15 @@ const logger$3 = new controller_decorator.Logger('Cooldown');
|
|
|
672
787
|
});
|
|
673
788
|
const name = store.constructor.name;
|
|
674
789
|
const reason = error.timedOut ? `did not answer within ${timeoutMs} ms` : `failed: ${String(error.cause?.message ?? error.cause)}`;
|
|
675
|
-
if (failure === 'allow') logger$
|
|
676
|
-
else logger$
|
|
790
|
+
if (failure === 'allow') logger$4.warn(`The cooldown store ${name} ${reason}. Calls run uncounted until it answers again.`);
|
|
791
|
+
else logger$4.error(`The cooldown store ${name} ${reason}. Calls with a cooldown are refused until it answers again.`, error.cause ?? '');
|
|
677
792
|
}
|
|
678
793
|
/** Logs that a store answers again, once it has gone {@link OUTAGE_QUIET_MS} without a failure. */ function reportRecovery(store) {
|
|
679
794
|
const outage = outages.get(store);
|
|
680
795
|
if (!outage || Date.now() - outage.last < OUTAGE_QUIET_MS) return;
|
|
681
796
|
outages.delete(store);
|
|
682
797
|
const seconds = Math.round((outage.last - outage.since) / 1000);
|
|
683
|
-
logger$
|
|
798
|
+
logger$4.log(`The cooldown store ${store.constructor.name} answers again, after ${outage.failures} failed call(s) over ${seconds}s.`);
|
|
684
799
|
}
|
|
685
800
|
/** What a container's calls wait for before asking its store: the store's own `onReady`, which may connect it. */ const storesReady = new WeakMap();
|
|
686
801
|
/** The store operations a container's calls have under way, which its store's `onShutdown` waits for. */ const storeOperations = new WeakMap();
|
|
@@ -742,7 +857,7 @@ const logger$3 = new controller_decorator.Logger('Cooldown');
|
|
|
742
857
|
} catch (error) {
|
|
743
858
|
if (error instanceof controller_decorator.CooldownStoreError && error.timedOut && !peek && !keepLate) {
|
|
744
859
|
// Under way from now, so the store does not shut down before the call it counted is given back
|
|
745
|
-
const release = attempt.then((verdict)=>verdict.allowed ? verdict.release?.() : undefined).catch((failure)=>logger$
|
|
860
|
+
const release = attempt.then((verdict)=>verdict.allowed ? verdict.release?.() : undefined).catch((failure)=>logger$4.debug(`Could not release a call the cooldown store counted too late: ${String(failure)}`));
|
|
746
861
|
trackStoreOperation(container, release);
|
|
747
862
|
}
|
|
748
863
|
throw error instanceof controller_decorator.CooldownStoreError ? error : new controller_decorator.CooldownStoreError(error, false);
|
|
@@ -885,7 +1000,7 @@ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[
|
|
|
885
1000
|
try {
|
|
886
1001
|
return (await askWithin(container, cooldownStoreOf(container), notices, cooldownPolicyOf(container).timeoutMs, false)).allowed;
|
|
887
1002
|
} catch (error) {
|
|
888
|
-
logger$
|
|
1003
|
+
logger$4.debug(`Could not ask the cooldown store whether to tell a refused caller: ${String(error.cause ?? error)}`);
|
|
889
1004
|
return false;
|
|
890
1005
|
}
|
|
891
1006
|
}
|
|
@@ -1059,6 +1174,7 @@ function describeLength(value) {
|
|
|
1059
1174
|
if (existing === undefined) {
|
|
1060
1175
|
const name = typeof body.name === 'string' ? body.name : commandName;
|
|
1061
1176
|
unlocalizable.push(...localizationProblems(name, body));
|
|
1177
|
+
const builderName = builderClass?.name;
|
|
1062
1178
|
byKey.set(key, {
|
|
1063
1179
|
builderClass: builderClass ?? builder,
|
|
1064
1180
|
command: {
|
|
@@ -1066,6 +1182,9 @@ function describeLength(value) {
|
|
|
1066
1182
|
body,
|
|
1067
1183
|
...guilds && {
|
|
1068
1184
|
guilds
|
|
1185
|
+
},
|
|
1186
|
+
...typeof builderName === 'string' && builderName && {
|
|
1187
|
+
builder: builderName
|
|
1069
1188
|
}
|
|
1070
1189
|
}
|
|
1071
1190
|
});
|
|
@@ -1103,6 +1222,14 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
1103
1222
|
}
|
|
1104
1223
|
/** Whether a command is an activity's primary entry point, which Discord takes only globally. */ const isEntryPoint = (command)=>command.body.type === discord_js.ApplicationCommandType.PrimaryEntryPoint;
|
|
1105
1224
|
/** What a blank guild to register every command to says, as an unset variable gives one. */ const BLANK_GUILD = 'No commands were registered: the guild to register to is blank. Give a guild id, or leave it out to register where commands.* says.';
|
|
1225
|
+
/**
|
|
1226
|
+
* Names every command whose builder lists guilds but no guild id, each with its builder, and says the run registers
|
|
1227
|
+
* none of them yet still succeeds.
|
|
1228
|
+
*/ function blankGuildsWarning(commands) {
|
|
1229
|
+
const one = commands.length === 1;
|
|
1230
|
+
const lines = commands.map((command)=>` ${command.builder ? `${command.builder}: ` : ''}"${command.name}"`);
|
|
1231
|
+
return `${commands.length} ${one ? 'command is' : 'commands are'} not registered: ${one ? 'its builder lists' : 'their builders list'} ` + 'no guild ids, as when the variable holding them is unset.\n' + `${lines.join('\n')}\n` + 'Set the guild ids; a builder without guilds registers where commands.* says. `meocord register` still exits 0 for ' + 'these; the next major version (5.0) exits 1.';
|
|
1232
|
+
}
|
|
1106
1233
|
/**
|
|
1107
1234
|
* Where each command is sent: one bulk update per scope. A scope's update replaces everything the application has
|
|
1108
1235
|
* there, so the default scope is included even when it ends up empty, unless `commands.guilds` lists no guild id.
|
|
@@ -1146,6 +1273,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
1146
1273
|
guild
|
|
1147
1274
|
});
|
|
1148
1275
|
const unregistered = [];
|
|
1276
|
+
const blankGuilds = [];
|
|
1149
1277
|
for (const command of commands){
|
|
1150
1278
|
if (isEntryPoint(command)) {
|
|
1151
1279
|
if (command.guilds) {
|
|
@@ -1155,7 +1283,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
1155
1283
|
} else if (command.guilds) {
|
|
1156
1284
|
const own = ids(command.guilds);
|
|
1157
1285
|
// Falling back to the default scope could publish a staff-only command everywhere.
|
|
1158
|
-
if (own.length === 0)
|
|
1286
|
+
if (own.length === 0) blankGuilds.push(command);
|
|
1159
1287
|
for (const guild of own)add({
|
|
1160
1288
|
guild
|
|
1161
1289
|
}, command);
|
|
@@ -1169,6 +1297,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
1169
1297
|
}, command);
|
|
1170
1298
|
}
|
|
1171
1299
|
}
|
|
1300
|
+
if (blankGuilds.length > 0) logger.warn(blankGuildsWarning(blankGuilds));
|
|
1172
1301
|
if (nowhere) {
|
|
1173
1302
|
const outcome = unregistered.length === 0 ? targets.has('global') ? 'only the entry point command is registered globally' : 'nothing is registered globally' : `${unregistered.join(', ')} ${unregistered.length === 1 ? 'is' : 'are'} not registered, rather than registered globally`;
|
|
1174
1303
|
logger.warn(`commands.guilds lists no guild id, as an unset environment variable leaves it, so ${outcome}. ` + `Set the guild ids, or remove commands.guilds to register globally.`);
|
|
@@ -1379,10 +1508,7 @@ function logRegistered(target, logger) {
|
|
|
1379
1508
|
|
|
1380
1509
|
/** How long `@Defer({ mode: 'auto' })` waits for an answer by default. */ const AUTO_DEFER_AFTER_MS = 1500;
|
|
1381
1510
|
/** The latest `'auto'` acknowledges, counted from the interaction's creation: Discord allows three seconds. */ const AUTO_DEFER_LIMIT_MS = 2500;
|
|
1382
|
-
/** The `@Defer` options of a handler,
|
|
1383
|
-
const source = sourcePrototype(prototype, methodName);
|
|
1384
|
-
return source ? Reflect.getOwnMetadata(controller_decorator.META.deferOptions, source, methodName) : undefined;
|
|
1385
|
-
});
|
|
1511
|
+
/** The `@Defer` options of a handler: its own, or else those of the nearest method it overrides that has them. */ const handlerDefer = perHandler((prototype, methodName)=>nearestMethodStage(prototype, methodName, controller_decorator.META.deferOptions));
|
|
1386
1512
|
/** The kind of non-interaction handler a method is, if any: `@Defer` cannot apply to one. */ function nonInteractionHandler(prototype, methodName) {
|
|
1387
1513
|
if (controller_decorator.getMessageHandlers(prototype).some((handler)=>handler.method === methodName)) return 'message';
|
|
1388
1514
|
if (controller_decorator.getReactionHandlers(prototype).some((handler)=>handler.method === methodName)) return 'reaction';
|
|
@@ -1439,21 +1565,8 @@ function logRegistered(target, logger) {
|
|
|
1439
1565
|
*/ function isAppClassToken(token) {
|
|
1440
1566
|
return typeof token === 'function' && token !== discord_js.Client && !NATIVE_SOURCE$1.test(Function.prototype.toString.call(token));
|
|
1441
1567
|
}
|
|
1442
|
-
/**
|
|
1443
|
-
* The bound classes `cls` injects, by constructor type or `@inject` token: the classes whose hooks
|
|
1444
|
-
* run before its own.
|
|
1445
|
-
*/ function lifecycleDependencies(container, cls) {
|
|
1446
|
-
return injectedTokens(cls).filter((token)=>isAppClassToken(token) && container.isBound(token));
|
|
1447
|
-
}
|
|
1448
|
-
/** The units for a list of classes already in dependency order, as the app runs them without providers. */ function classUnits(container, classes) {
|
|
1449
|
-
return classes.map((cls)=>({
|
|
1450
|
-
token: cls,
|
|
1451
|
-
name: cls.name,
|
|
1452
|
-
dependencies: lifecycleDependencies(container, cls)
|
|
1453
|
-
}));
|
|
1454
|
-
}
|
|
1455
1568
|
|
|
1456
|
-
const logger$
|
|
1569
|
+
const logger$3 = new controller_decorator.Logger('Interceptor');
|
|
1457
1570
|
/** A function's source when the engine wrote it, as for a bound function, a built-in, or what `await` hands a promise. */ const NATIVE_SOURCE = /\{\s*\[native code\]\s*\}$/;
|
|
1458
1571
|
/**
|
|
1459
1572
|
* Whether `onRejected` is the app's own handling of a rejection, rather than an engine's step passing it on. The
|
|
@@ -1469,13 +1582,12 @@ function isInterceptorWithParams(entry) {
|
|
|
1469
1582
|
}
|
|
1470
1583
|
/**
|
|
1471
1584
|
* The interceptors around a handler: each class's, base first, down to the controller it is dispatched on, then the
|
|
1472
|
-
* method's.
|
|
1585
|
+
* method's and its overrides', base first.
|
|
1473
1586
|
*/ const handlerInterceptors = perHandler((prototype, methodName)=>{
|
|
1474
|
-
|
|
1475
|
-
if (!source) return [];
|
|
1587
|
+
if (!sourcePrototype(prototype, methodName)) return [];
|
|
1476
1588
|
return [
|
|
1477
1589
|
...stageClasses(prototype, methodName).flatMap((cls)=>Reflect.getOwnMetadata(controller_decorator.META.classInterceptors, cls) ?? []),
|
|
1478
|
-
...
|
|
1590
|
+
...methodStages(prototype, methodName, controller_decorator.META.methodInterceptors)
|
|
1479
1591
|
];
|
|
1480
1592
|
});
|
|
1481
1593
|
/** Binds `cls` and its unbound dependencies as singletons, refusing any that injects `ExecutionContext`. */ function bindShared(container, cls) {
|
|
@@ -1624,7 +1736,7 @@ function isInterceptorWithParams(entry) {
|
|
|
1624
1736
|
// Once the handlers chained from it have had their turn, and those the app's code returned a promise from settled
|
|
1625
1737
|
void taken.settled.then(()=>new Promise((resolve)=>setTimeout(resolve, 0))).then(()=>Promise.all(taken.disposing)).then(()=>{
|
|
1626
1738
|
if (!taken.failure || taken.caught) return;
|
|
1627
|
-
logger$
|
|
1739
|
+
logger$3.warn(`${cls.name} returned before ${context.getController().name}.${context.getHandlerName()} finished, which then threw; ` + 'nothing caught it, so the call could not report it:', taken.failure.error);
|
|
1628
1740
|
});
|
|
1629
1741
|
}
|
|
1630
1742
|
// Only a run left on a chain that ends unhandled is the call's; one the interceptor took on, as a timeout
|
|
@@ -1660,13 +1772,13 @@ function isInterceptorWithParams(entry) {
|
|
|
1660
1772
|
];
|
|
1661
1773
|
}
|
|
1662
1774
|
/** A handler's method and class filters, the classes innermost first, resolved once. */ const ownFilterLevels = perHandler((prototype, methodName)=>{
|
|
1663
|
-
|
|
1664
|
-
if (!source) return {
|
|
1775
|
+
if (!sourcePrototype(prototype, methodName)) return {
|
|
1665
1776
|
method: [],
|
|
1666
1777
|
classes: []
|
|
1667
1778
|
};
|
|
1668
1779
|
return {
|
|
1669
|
-
|
|
1780
|
+
// An override's filters before those of the methods it overrides, innermost first as the classes are
|
|
1781
|
+
method: stageOwners(prototype, methodName).reverse().flatMap((owner)=>Reflect.getOwnMetadata(controller_decorator.META.methodFilters, owner, methodName) ?? []),
|
|
1670
1782
|
classes: [
|
|
1671
1783
|
...stageClasses(prototype, methodName)
|
|
1672
1784
|
].reverse().flatMap((cls)=>Reflect.getOwnMetadata(controller_decorator.META.classFilters, cls) ?? [])
|
|
@@ -1710,17 +1822,16 @@ function isPipeWithParams(entry) {
|
|
|
1710
1822
|
const asList = (entries)=>Array.isArray(entries) ? entries : [
|
|
1711
1823
|
entries
|
|
1712
1824
|
];
|
|
1713
|
-
/**
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
const validation = Reflect.getOwnMetadata(controller_decorator.META.methodValidation, source, methodName);
|
|
1825
|
+
/**
|
|
1826
|
+
* The validation and pipes that run on a handler's input: `@Validate`'s first, then `@UsePipe`'s. The `@Validate` is the
|
|
1827
|
+
* handler's own, or else that of the nearest method it overrides that has one; `@UsePipe`'s are every one's, base first.
|
|
1828
|
+
*/ const handlerInputStages = perHandler((prototype, methodName)=>{
|
|
1829
|
+
const validation = nearestMethodStage(prototype, methodName, controller_decorator.META.methodValidation);
|
|
1719
1830
|
const inline = Object.entries(validation?.pipes ?? {}).flatMap(([key, entries])=>asList(entries).map((entry)=>({
|
|
1720
1831
|
key,
|
|
1721
1832
|
entry
|
|
1722
1833
|
})));
|
|
1723
|
-
const used =
|
|
1834
|
+
const used = methodStages(prototype, methodName, controller_decorator.META.methodPipes);
|
|
1724
1835
|
return {
|
|
1725
1836
|
schema: validation?.schema,
|
|
1726
1837
|
pipes: [
|
|
@@ -1786,7 +1897,7 @@ const asList = (entries)=>Array.isArray(entries) ? entries : [
|
|
|
1786
1897
|
}
|
|
1787
1898
|
|
|
1788
1899
|
/** Where a container keeps the observers its calls are reported to, in order. */ const OBSERVERS = Symbol('observers');
|
|
1789
|
-
const logger$
|
|
1900
|
+
const logger$2 = new controller_decorator.Logger('Observer');
|
|
1790
1901
|
/** Reports each entry that is not an `@Observer` class, naming where it was listed. */ function assertObservers(where, observers) {
|
|
1791
1902
|
for (const observer of observers){
|
|
1792
1903
|
if (typeof observer !== 'function' || !Reflect.getMetadata(controller_decorator.META.observerClass, observer)) {
|
|
@@ -1832,7 +1943,7 @@ function observersOf(container) {
|
|
|
1832
1943
|
*/ function notifyStart(container, context) {
|
|
1833
1944
|
const pending = [];
|
|
1834
1945
|
for (const observer of observersFor(container, context)){
|
|
1835
|
-
const report = (error)=>logger$
|
|
1946
|
+
const report = (error)=>logger$2.error(`Observer ${observer.name} threw while starting ${describeCall(context)}:`, error);
|
|
1836
1947
|
try {
|
|
1837
1948
|
const instance = container.get(observer);
|
|
1838
1949
|
if (typeof instance.onStart !== 'function') continue;
|
|
@@ -1846,8 +1957,9 @@ function observersOf(container) {
|
|
|
1846
1957
|
}
|
|
1847
1958
|
/**
|
|
1848
1959
|
* Where an interaction's answer stands: a repliable one's `ResponseState`, and an autocomplete answered
|
|
1849
|
-
* or not. `undefined` for anything that is not an interaction.
|
|
1960
|
+
* or not. `undefined` for anything that is not an interaction's call, an event carrying one included.
|
|
1850
1961
|
*/ function responsePhaseOf(context) {
|
|
1962
|
+
if (context.getType() === 'event') return undefined;
|
|
1851
1963
|
const interaction = context.getInteraction();
|
|
1852
1964
|
if (!interaction) return undefined;
|
|
1853
1965
|
if (interaction.isAutocomplete()) return interaction.responded ? 'replied' : 'unanswered';
|
|
@@ -1869,13 +1981,14 @@ function observersOf(container) {
|
|
|
1869
1981
|
try {
|
|
1870
1982
|
await container.get(observer).onSettled(context, result);
|
|
1871
1983
|
} catch (error) {
|
|
1872
|
-
logger$
|
|
1984
|
+
logger$2.error(`Observer ${observer.name} threw while observing ${describeCall(context)}:`, error);
|
|
1873
1985
|
}
|
|
1874
1986
|
}
|
|
1875
1987
|
}
|
|
1876
1988
|
|
|
1877
|
-
/** Classes with a `@UseTheme`, on the class or one of its methods. */ const THEMED_CLASSES = new WeakSet();
|
|
1878
|
-
|
|
1989
|
+
/** Classes with a `@UseTheme`, on the class or one of its methods, from either build of this version. */ const THEMED_CLASSES = controller_decorator.shared('themedClasses', ()=>new WeakSet());
|
|
1990
|
+
// One per process for this meocord version, so an app started through one build finds its theme from the other
|
|
1991
|
+
const apps = controller_decorator.shared('appThemes', ()=>new WeakMap());
|
|
1879
1992
|
/** The classes on `cls`'s prototype chain, itself first. */ function chainOf(cls) {
|
|
1880
1993
|
const chain = [];
|
|
1881
1994
|
for(let prototype = cls.prototype; prototype && prototype !== Object.prototype; prototype = Object.getPrototypeOf(prototype)){
|
|
@@ -1906,6 +2019,13 @@ const apps = new WeakMap();
|
|
|
1906
2019
|
}
|
|
1907
2020
|
controller_decorator.attachThemeCache(container.get(controller_decorator.ThemeCache), resolvers);
|
|
1908
2021
|
}
|
|
2022
|
+
/** Counts `classes`, whose handlers are prepared once the app's providers are made, among those a theme can vary by. */ function prepareLateThemes(container, classes) {
|
|
2023
|
+
const themes = apps.get(container);
|
|
2024
|
+
if (!themes || themes.varies || !classes.some((cls)=>chainOf(cls).some((link)=>THEMED_CLASSES.has(link)))) return;
|
|
2025
|
+
themes.varies = true;
|
|
2026
|
+
// Built again on its next read, with each handler's own theme
|
|
2027
|
+
themes.version = -1;
|
|
2028
|
+
}
|
|
1909
2029
|
/** The app's themes, built again when the layers beneath the app's have changed. */ function current(container) {
|
|
1910
2030
|
const themes = apps.get(container);
|
|
1911
2031
|
if (themes && themes.version !== controller_decorator.themeLayersVersion()) {
|
|
@@ -1920,13 +2040,12 @@ const apps = new WeakMap();
|
|
|
1920
2040
|
}
|
|
1921
2041
|
/**
|
|
1922
2042
|
* Decides whether the app's calls need a scope, again whenever the layers beneath it or the app read outside a call
|
|
1923
|
-
* change. They do when the handlers can differ in theme. Otherwise a call reads the theme outside a call, so it
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
2043
|
+
* change. They do when the handlers can differ in theme. Otherwise a call reads the theme outside a call, so it needs
|
|
2044
|
+
* one unless its app owns that theme: another app may claim it while the call runs, and the call keeps the theme it
|
|
2045
|
+
* started with.
|
|
1926
2046
|
*/ function rescope(container, themes) {
|
|
1927
2047
|
themes.ambientVersion = controller_decorator.ambientThemeVersion();
|
|
1928
|
-
|
|
1929
|
-
themes.scoped = themes.varies || !readsOwnTheme ? themes.app : undefined;
|
|
2048
|
+
themes.scoped = themes.varies || !controller_decorator.ownsAmbientTheme(container) ? themes.app : undefined;
|
|
1930
2049
|
}
|
|
1931
2050
|
/** The app's theme: MeoCord's defaults and the app's `@MeoCord({ theme })`, or the defaults for a container with none. */ function appTheme(container) {
|
|
1932
2051
|
return current(container)?.app ?? controller_decorator.defaultTheme();
|
|
@@ -1944,8 +2063,10 @@ const apps = new WeakMap();
|
|
|
1944
2063
|
for (const cls of stageClasses(prototype, methodName)){
|
|
1945
2064
|
theme = controller_decorator.mergeTheme(theme, Reflect.getOwnMetadata(controller_decorator.META.classTheme, cls));
|
|
1946
2065
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
2066
|
+
// The method's layer over its class's, and an override's over the method it overrides
|
|
2067
|
+
for (const owner of stageOwners(prototype, methodName)){
|
|
2068
|
+
theme = controller_decorator.mergeTheme(theme, Reflect.getOwnMetadata(controller_decorator.META.methodTheme, owner, methodName));
|
|
2069
|
+
}
|
|
1949
2070
|
byMethod.set(methodName, theme);
|
|
1950
2071
|
return theme;
|
|
1951
2072
|
}
|
|
@@ -1984,7 +2105,7 @@ const apps = new WeakMap();
|
|
|
1984
2105
|
ready
|
|
1985
2106
|
};
|
|
1986
2107
|
}
|
|
1987
|
-
const clientApps = new WeakMap();
|
|
2108
|
+
const clientApps = controller_decorator.shared('clientThemes', ()=>new WeakMap());
|
|
1988
2109
|
/** Records which app a client's interactions come to, so an answer outside any call can find its theme. */ function registerClientTheme(client, container) {
|
|
1989
2110
|
clientApps.set(client, container);
|
|
1990
2111
|
}
|
|
@@ -2155,19 +2276,35 @@ const stagesByGlobals = new WeakMap();
|
|
|
2155
2276
|
for (const entry of globals.filters)prepareFilter(container, entry);
|
|
2156
2277
|
for (const controller of controllers){
|
|
2157
2278
|
const prototype = controller.prototype;
|
|
2158
|
-
for (const method of handlerMethods(prototype))
|
|
2159
|
-
const kind = handlerDefer(prototype, method) ? nonInteractionHandler(prototype, method) : undefined;
|
|
2160
|
-
if (kind) {
|
|
2161
|
-
controller_decorator.startupError(deferMisuseError(controller.name, method, kind));
|
|
2162
|
-
continue;
|
|
2163
|
-
}
|
|
2164
|
-
for (const entry of handlerInterceptors(prototype, method))prepareInterceptor(container, entry);
|
|
2165
|
-
for (const entry of handlerFilterLevels(prototype, method, []).flat())prepareFilter(container, entry);
|
|
2166
|
-
for (const { entry } of handlerInputStages(prototype, method).pipes)preparePipe(container, entry);
|
|
2167
|
-
}
|
|
2279
|
+
for (const method of handlerMethods(prototype))prepareHandler(container, controller, method);
|
|
2168
2280
|
assertInputStagesOnInteractions(controller, prototype);
|
|
2169
2281
|
}
|
|
2170
2282
|
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Prepares one `@On` or `@Once` handler of a class the app reaches only as something it makes, as
|
|
2285
|
+
* {@link prepareHandlerStages} prepares a controller's.
|
|
2286
|
+
*
|
|
2287
|
+
* @returns The errors a controller's handler would be refused with; none when the handler is ready to run.
|
|
2288
|
+
*/ function prepareEventHandler(container, cls, method) {
|
|
2289
|
+
try {
|
|
2290
|
+
controller_decorator.collectStartupErrors(()=>{
|
|
2291
|
+
prepareHandler(container, cls, method);
|
|
2292
|
+
assertInputStages(cls, cls.prototype, method, 'event');
|
|
2293
|
+
});
|
|
2294
|
+
return [];
|
|
2295
|
+
} catch (error) {
|
|
2296
|
+
if (!controller_decorator.isRefusal(error)) throw error;
|
|
2297
|
+
return controller_decorator.startupErrorsOf(error);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
/** Binds what one handler's interceptors, filters and pipes need, refusing `@Defer` where it cannot apply. */ function prepareHandler(container, controller, method) {
|
|
2301
|
+
const prototype = controller.prototype;
|
|
2302
|
+
const kind = handlerDefer(prototype, method) ? nonInteractionHandler(prototype, method) : undefined;
|
|
2303
|
+
if (kind) return controller_decorator.startupError(deferMisuseError(controller.name, method, kind));
|
|
2304
|
+
for (const entry of handlerInterceptors(prototype, method))prepareInterceptor(container, entry);
|
|
2305
|
+
for (const entry of handlerFilterLevels(prototype, method, []).flat())prepareFilter(container, entry);
|
|
2306
|
+
for (const { entry } of handlerInputStages(prototype, method).pipes)preparePipe(container, entry);
|
|
2307
|
+
}
|
|
2171
2308
|
/** Records the app's theme for the handlers of `controllers`, once the startup checks have passed: it makes the theme cache. */ function prepareThemes(container, controllers) {
|
|
2172
2309
|
const globals = globalStagesOf(container);
|
|
2173
2310
|
configureThemes(container, globals.theme, controllers, globals.themeFor);
|
|
@@ -2213,17 +2350,17 @@ const stagesByGlobals = new WeakMap();
|
|
|
2213
2350
|
'event'
|
|
2214
2351
|
])
|
|
2215
2352
|
];
|
|
2216
|
-
for (const [method, kind] of others)
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
}
|
|
2353
|
+
for (const [method, kind] of others)assertInputStages(controller, prototype, method, kind);
|
|
2354
|
+
}
|
|
2355
|
+
/** Refuses `@Validate` and `@UsePipe` on one handler of `kind` with no params to check, and `@Cooldown` where it never counts. */ function assertInputStages(controller, prototype, method, kind) {
|
|
2356
|
+
const { schema, pipes } = handlerInputStages(prototype, method);
|
|
2357
|
+
const handler = kind === 'message' ? 'a message handler without a pattern' : `${kind === 'autocomplete' || kind === 'event' ? 'an' : 'a'} ${kind} handler`;
|
|
2358
|
+
if (schema || pipes.length > 0) {
|
|
2359
|
+
return controller_decorator.startupError(new Error(`${controller.name}.${method}: @Validate and @UsePipe are for interaction and patterned message handlers, ` + `whose options, customId params, modal fields and pattern params they check, and this is ${handler}.`));
|
|
2360
|
+
}
|
|
2361
|
+
// A controller's own @Cooldown skips these handlers; one on the method itself is a mistake.
|
|
2362
|
+
if (kind !== 'message' && methodCooldowns(prototype, method).length > 0) {
|
|
2363
|
+
controller_decorator.startupError(new Error(`${controller.name}.${method}: @Cooldown is for interaction and message handlers, and this is ${handler}.`));
|
|
2227
2364
|
}
|
|
2228
2365
|
}
|
|
2229
2366
|
/**
|
|
@@ -2251,7 +2388,7 @@ const stagesByGlobals = new WeakMap();
|
|
|
2251
2388
|
if (response) result.response = response;
|
|
2252
2389
|
return result;
|
|
2253
2390
|
}
|
|
2254
|
-
const logger = new controller_decorator.Logger('ExceptionFilter');
|
|
2391
|
+
const logger$1 = new controller_decorator.Logger('ExceptionFilter');
|
|
2255
2392
|
/**
|
|
2256
2393
|
* Hands a failed call's error to the first matching filter, level by level, then to the fallback. A
|
|
2257
2394
|
* filter that throws is logged and the fallback answers the original error; without a fallback, the
|
|
@@ -2265,7 +2402,7 @@ const logger = new controller_decorator.Logger('ExceptionFilter');
|
|
|
2265
2402
|
} catch (filterError) {
|
|
2266
2403
|
if (!fallback) throw filterError;
|
|
2267
2404
|
const name = (typeof filter === 'object' ? filter.provide : filter).name;
|
|
2268
|
-
logger.error(`Filter ${name} threw while handling an error:`, filterError);
|
|
2405
|
+
logger$1.error(`Filter ${name} threw while handling an error:`, filterError);
|
|
2269
2406
|
}
|
|
2270
2407
|
}
|
|
2271
2408
|
if (!fallback) throw error;
|
|
@@ -2471,6 +2608,23 @@ const logger = new controller_decorator.Logger('ExceptionFilter');
|
|
|
2471
2608
|
}, options);
|
|
2472
2609
|
}
|
|
2473
2610
|
|
|
2611
|
+
/** The longest delay a timer keeps: 2^31 - 1 ms, about 24.8 days. Node fires a longer one at once. */ const MAX_TIMER_MS = 2147483647;
|
|
2612
|
+
const MS_PER_SECOND = 1000;
|
|
2613
|
+
/** The most whole seconds a timer keeps, as a message about an option given in seconds names the limit. */ const MAX_TIMER_SECONDS = Math.floor(MAX_TIMER_MS / MS_PER_SECOND);
|
|
2614
|
+
/** Whether a timer keeps a delay given in seconds, a fraction of a second included. */ const timerKeepsSeconds = (seconds)=>seconds * MS_PER_SECOND <= MAX_TIMER_MS;
|
|
2615
|
+
/** A value as a refusal quotes it: a number as itself, `Infinity` and `NaN` included, a string in quotes. */ function quoted(value) {
|
|
2616
|
+
if (typeof value === 'string') return JSON.stringify(value);
|
|
2617
|
+
if (typeof value === 'number' || typeof value === 'boolean' || value === null) return String(value);
|
|
2618
|
+
return Array.isArray(value) ? 'an array' : typeof value;
|
|
2619
|
+
}
|
|
2620
|
+
/**
|
|
2621
|
+
* What is wrong with a timeout option, or `undefined` when it is a number of milliseconds a timer keeps: finite, above
|
|
2622
|
+
* 0, or 0 or more with `allowZero`, and at most `max`, {@link MAX_TIMER_MS} unless less. The caller names the option.
|
|
2623
|
+
*/ function timeoutProblem(value, { allowZero = false, max = MAX_TIMER_MS } = {}) {
|
|
2624
|
+
if (typeof value === 'number' && Number.isFinite(value) && (allowZero ? value >= 0 : value > 0) && value <= max) return undefined;
|
|
2625
|
+
return `must be a number of milliseconds ${allowZero ? '0 or more' : 'above 0'}, at most ${max} (got ${quoted(value)})`;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2474
2628
|
/** Fetches in flight for a client, by kind, server and ID, so callers asking at once share one request. */ const inFlight = new WeakMap();
|
|
2475
2629
|
/** The one request for `key`: the one in flight, or a new one from `start`, forgotten once it settles. */ function singleFlight(client, key, start) {
|
|
2476
2630
|
let requests = inFlight.get(client);
|
|
@@ -2682,7 +2836,8 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
|
|
|
2682
2836
|
}
|
|
2683
2837
|
flags.push({
|
|
2684
2838
|
name,
|
|
2685
|
-
value
|
|
2839
|
+
value,
|
|
2840
|
+
at: words.length
|
|
2686
2841
|
});
|
|
2687
2842
|
cuts.push(start, j);
|
|
2688
2843
|
i = j;
|
|
@@ -2717,6 +2872,33 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
|
|
|
2717
2872
|
}
|
|
2718
2873
|
return (rest + text.slice(from)).trimEnd();
|
|
2719
2874
|
}
|
|
2875
|
+
const caseKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase();
|
|
2876
|
+
/** Whether a flag, given bare in place `at` among the other words, is a pattern's own `--` word `literal`. */ const isLiteralFlag = (flag, literal, at, caseSensitive)=>flag.at === at && flag.value === undefined && caseKey(`--${flag.name}`, caseSensitive) === caseKey(literal, caseSensitive);
|
|
2877
|
+
/**
|
|
2878
|
+
* The flags a pattern's own `--` words take from a message, as indexes into `flags`: where the pattern has such a word,
|
|
2879
|
+
* a flag of that name given bare in that place among the message's other words is the word, not a flag. A word typed in
|
|
2880
|
+
* quotes is matched as any word is, and is taken first.
|
|
2881
|
+
*/ function literalFlags(tokens, caseSensitive, words, flags) {
|
|
2882
|
+
const taken = new Set();
|
|
2883
|
+
if (flags.length === 0) return taken;
|
|
2884
|
+
let w = 0;
|
|
2885
|
+
for (const token of tokens){
|
|
2886
|
+
if (!('literal' in token)) {
|
|
2887
|
+
// Literal words come before the optional and rest params, which a pattern ends with
|
|
2888
|
+
if (token.rest || token.optional) break;
|
|
2889
|
+
w++;
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2892
|
+
if (w < words.length && caseKey(words[w].value, caseSensitive) === caseKey(token.literal, caseSensitive)) {
|
|
2893
|
+
w++;
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
const index = flags.findIndex((flag, i)=>!taken.has(i) && isLiteralFlag(flag, token.literal, w, caseSensitive));
|
|
2897
|
+
if (index === -1) break;
|
|
2898
|
+
taken.add(index);
|
|
2899
|
+
}
|
|
2900
|
+
return taken;
|
|
2901
|
+
}
|
|
2720
2902
|
|
|
2721
2903
|
/** The ID a mention or a bare snowflake names, for the mention's kind. */ function idOf(word, mention) {
|
|
2722
2904
|
return (mention.exec(word) ?? /^(\d{17,20})$/.exec(word))?.[1];
|
|
@@ -2724,6 +2906,10 @@ const isFlagNameChar = (code, first)=>code >= 97 && code <= 122 || code >= 65 &&
|
|
|
2724
2906
|
const USER_MENTION = /^<@!?(\d{17,20})>$/;
|
|
2725
2907
|
const ROLE_MENTION = /^<@&(\d{17,20})>$/;
|
|
2726
2908
|
const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
2909
|
+
/** The server's role a word names: its mention or ID, else its name in any case. */ function roleNamed(guild, word) {
|
|
2910
|
+
const id = idOf(word, ROLE_MENTION);
|
|
2911
|
+
return id ? guild.roles.cache.get(id) : guild.roles.cache.find((candidate)=>candidate.name.toLowerCase() === word.toLowerCase());
|
|
2912
|
+
}
|
|
2727
2913
|
/** The scalar types, each turning a word into its value or `undefined`, and the Discord ones, which resolve below. */ const BUILT_IN_TYPES = controller_decorator.lookupTable({
|
|
2728
2914
|
string: (word)=>word,
|
|
2729
2915
|
int: (word)=>controller_decorator.number(word, true),
|
|
@@ -2754,7 +2940,18 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2754
2940
|
'channel'
|
|
2755
2941
|
]);
|
|
2756
2942
|
/** Whether a param type is found only in a server. */ const isGuildType = (type)=>GUILD_TYPES.has(type);
|
|
2757
|
-
/** Whether a route's pattern takes a
|
|
2943
|
+
/** Whether a route's pattern takes a `member`, `role` or `channel` param, optional or not. */ const takesGuildParam = (route)=>route.tokens.some((token)=>'param' in token && token.type !== undefined && GUILD_TYPES.has(token.type));
|
|
2944
|
+
/**
|
|
2945
|
+
* Whether a route works only in a server, as dispatch decides it: a `member`, `role` or `channel` param, optional or
|
|
2946
|
+
* not, or a required flag of one of those types. An optional flag of those types refuses a DM only when it is given.
|
|
2947
|
+
*/ const needsGuild = (route)=>takesGuildParam(route) || route.flags.some((flag)=>!flag.optional && flag.type !== undefined && GUILD_TYPES.has(flag.type));
|
|
2948
|
+
/**
|
|
2949
|
+
* Where a route works: its scope, narrowed to servers by a `member`, `role` or `channel` param, or required flag, as
|
|
2950
|
+
* dispatch narrows it.
|
|
2951
|
+
*/ function effectiveScope(route) {
|
|
2952
|
+
if (route.scope !== 'any') return route.scope;
|
|
2953
|
+
return needsGuild(route) ? 'guild' : 'any';
|
|
2954
|
+
}
|
|
2758
2955
|
/** Whether a pattern's `{name:type}` names a type: built in, words to choose from, or one the app adds. */ function isKnownParamType(type, types) {
|
|
2759
2956
|
return type in BUILT_IN_TYPES || Boolean(controller_decorator.choicesOf(type)) || appType(types, type) !== undefined;
|
|
2760
2957
|
}
|
|
@@ -2834,14 +3031,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2834
3031
|
*/ function assertMessageScope(route, message, start) {
|
|
2835
3032
|
const inGuild = message.guildId !== null && message.guildId !== undefined;
|
|
2836
3033
|
if (route.scope === 'guild' && !inGuild) {
|
|
2837
|
-
throw
|
|
2838
|
-
controller_decorator.usageIssue({
|
|
2839
|
-
key: 'meocord.usage.serverOnly'
|
|
2840
|
-
})
|
|
2841
|
-
], {
|
|
2842
|
-
serverOnly: true,
|
|
2843
|
-
quiet: start === ''
|
|
2844
|
-
});
|
|
3034
|
+
throw serverOnly(usageOf(route, start), start === '');
|
|
2845
3035
|
}
|
|
2846
3036
|
if (route.scope === 'dm' && inGuild) {
|
|
2847
3037
|
throw new controller_decorator.MessageUsageError(usageOf(route, start), [
|
|
@@ -2854,6 +3044,15 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2854
3044
|
});
|
|
2855
3045
|
}
|
|
2856
3046
|
}
|
|
3047
|
+
/** The usage error for a message sent outside a server to a command, or with a flag, that only a server has. */ const serverOnly = (usage, quiet)=>new controller_decorator.MessageUsageError(usage, [
|
|
3048
|
+
controller_decorator.usageIssue({
|
|
3049
|
+
key: 'meocord.usage.serverOnly'
|
|
3050
|
+
})
|
|
3051
|
+
], {
|
|
3052
|
+
serverOnly: true,
|
|
3053
|
+
quiet
|
|
3054
|
+
});
|
|
3055
|
+
/** Whether a message gives one of a route's flags of a type only a server has, such as `--in=#general`. */ const givesGuildFlag = (route, flags)=>flags.some((given)=>route.flags.some((flag)=>flag.type !== undefined && GUILD_TYPES.has(flag.type) && caseKey(flag.flag, route.caseSensitive) === caseKey(given.name, route.caseSensitive)));
|
|
2857
3056
|
/** Whether a value an app's `parse` returned is a ref to resolve after the guards. */ const isRef = (value)=>typeof value === 'object' && value !== null && 'cached' in value && typeof value.resolve === 'function';
|
|
2858
3057
|
/**
|
|
2859
3058
|
* The params a matched route's guards see, read from the words with no request to Discord: typed params and lists as
|
|
@@ -2876,18 +3075,20 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2876
3075
|
const issues = [];
|
|
2877
3076
|
const items = [];
|
|
2878
3077
|
for (const token of route.tokens){
|
|
2879
|
-
|
|
3078
|
+
// Only a word the message gave is typed: an own one, never what Object.prototype has under the param's name
|
|
3079
|
+
const word = 'param' in token ? controller_decorator.ownValue(raw, token.param) : undefined;
|
|
3080
|
+
if (!('param' in token) || token.type === undefined || word === undefined) continue;
|
|
2880
3081
|
if (!token.rest) {
|
|
2881
3082
|
items.push({
|
|
2882
3083
|
key: token.param,
|
|
2883
3084
|
label: token.param,
|
|
2884
3085
|
type: token.type,
|
|
2885
|
-
word
|
|
3086
|
+
word
|
|
2886
3087
|
});
|
|
2887
3088
|
continue;
|
|
2888
3089
|
}
|
|
2889
|
-
const words = splitWords(
|
|
2890
|
-
params
|
|
3090
|
+
const words = splitWords(word);
|
|
3091
|
+
controller_decorator.setOwn(params, token.param, new Array(words.length));
|
|
2891
3092
|
words.forEach(({ value }, index)=>items.push({
|
|
2892
3093
|
key: token.param,
|
|
2893
3094
|
label: token.param,
|
|
@@ -2897,19 +3098,12 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2897
3098
|
}));
|
|
2898
3099
|
}
|
|
2899
3100
|
const flagIssues = route.flags.length > 0 ? readFlags(route, message, start, params, items) : [];
|
|
3101
|
+
// readFlags takes out each flag's key, so one left out whose name Object.prototype has reads as left out again
|
|
3102
|
+
if (route.inheritedNames.length > 0) controller_decorator.shadowInherited(params, route.inheritedNames);
|
|
2900
3103
|
const guild = message.guild;
|
|
2901
|
-
if (!guild && (needsGuild(route) || items.some((item)=>GUILD_TYPES.has(item.type))))
|
|
2902
|
-
throw new controller_decorator.MessageUsageError(usage, [
|
|
2903
|
-
controller_decorator.usageIssue({
|
|
2904
|
-
key: 'meocord.usage.serverOnly'
|
|
2905
|
-
})
|
|
2906
|
-
], {
|
|
2907
|
-
serverOnly: true,
|
|
2908
|
-
quiet
|
|
2909
|
-
});
|
|
2910
|
-
}
|
|
3104
|
+
if (!guild && (needsGuild(route) || items.some((item)=>GUILD_TYPES.has(item.type)))) throw serverOnly(usage, quiet);
|
|
2911
3105
|
const put = (item, value)=>{
|
|
2912
|
-
if (item.index === undefined) params
|
|
3106
|
+
if (item.index === undefined) controller_decorator.setOwn(params, item.key, value);
|
|
2913
3107
|
else params[item.key][item.index] = value;
|
|
2914
3108
|
};
|
|
2915
3109
|
const refs = [];
|
|
@@ -2946,8 +3140,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2946
3140
|
}
|
|
2947
3141
|
} else if (type === 'role') {
|
|
2948
3142
|
// Roles are cached with the Guilds intent, so a role is known here or not at all
|
|
2949
|
-
const
|
|
2950
|
-
const role = id ? guild.roles.cache.get(id) : guild.roles.cache.find((candidate)=>candidate.name.toLowerCase() === word.toLowerCase());
|
|
3143
|
+
const role = roleNamed(guild, word);
|
|
2951
3144
|
if (role) {
|
|
2952
3145
|
refer(item, 'role', new MessageEntityRef('role', role.id, message.client, guild));
|
|
2953
3146
|
continue;
|
|
@@ -2987,9 +3180,9 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2987
3180
|
const params = {
|
|
2988
3181
|
...parsed.params
|
|
2989
3182
|
};
|
|
2990
|
-
for (const [key, value] of Object.entries(params))if (Array.isArray(value)) params
|
|
3183
|
+
for (const [key, value] of Object.entries(params))if (Array.isArray(value)) controller_decorator.setOwn(params, key, [
|
|
2991
3184
|
...value
|
|
2992
|
-
];
|
|
3185
|
+
]);
|
|
2993
3186
|
const issues = [];
|
|
2994
3187
|
for (const slot of refs){
|
|
2995
3188
|
const value = slot.ref instanceof MessageEntityRef ? found.get(slot.ref) : await slot.ref.resolve();
|
|
@@ -2997,7 +3190,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
2997
3190
|
issues.push(missingEntity(slot));
|
|
2998
3191
|
continue;
|
|
2999
3192
|
}
|
|
3000
|
-
if (slot.index === undefined) params
|
|
3193
|
+
if (slot.index === undefined) controller_decorator.setOwn(params, slot.key, value);
|
|
3001
3194
|
else params[slot.key][slot.index] = value;
|
|
3002
3195
|
}
|
|
3003
3196
|
if (issues.length > 0) throw new controller_decorator.MessageUsageError(usage, issues, {
|
|
@@ -3063,7 +3256,11 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
3063
3256
|
return {
|
|
3064
3257
|
parseArgs: async (args)=>{
|
|
3065
3258
|
assertMessageScope(route, message, start);
|
|
3066
|
-
if (given !== undefined)
|
|
3259
|
+
if (given !== undefined) {
|
|
3260
|
+
// A DM to a command only a server has is told so before what its words lack, as one whose words fit is
|
|
3261
|
+
if (!message.guild && (needsGuild(route) || givesGuildFlag(route, given.flags))) throw serverOnly(usageOf(route, start), start === '');
|
|
3262
|
+
throw new controller_decorator.MessageUsageError(usageOf(route, start), missingParams(route, given, types, message.guild));
|
|
3263
|
+
}
|
|
3067
3264
|
if (!hasTypedParams(route)) return args;
|
|
3068
3265
|
parsed = await parseMessageParams(route, params, message, start, types);
|
|
3069
3266
|
return [
|
|
@@ -3114,7 +3311,11 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
3114
3311
|
const given = new Map();
|
|
3115
3312
|
const unknown = new Set();
|
|
3116
3313
|
const text = (message.content ?? '').trim();
|
|
3117
|
-
|
|
3314
|
+
const split = splitFlagWords(text.slice(start.length));
|
|
3315
|
+
// A flag the pattern's own `--` word took is that word, and neither one of its flags nor unknown
|
|
3316
|
+
const literal = literalFlags(route.tokens, route.caseSensitive, split.words, split.flags);
|
|
3317
|
+
for (const [index, flag] of split.flags.entries()){
|
|
3318
|
+
if (literal.has(index)) continue;
|
|
3118
3319
|
const declared = route.flags.find((candidate)=>key(candidate.flag) === key(flag.name));
|
|
3119
3320
|
if (declared) given.set(declared.flag, flag);
|
|
3120
3321
|
else if (!unknown.has(flag.name)) {
|
|
@@ -3140,7 +3341,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
3140
3341
|
value: controller_decorator.userWords(value)
|
|
3141
3342
|
}
|
|
3142
3343
|
}, flag.flag));
|
|
3143
|
-
else params
|
|
3344
|
+
else controller_decorator.setOwn(params, flag.flag, on);
|
|
3144
3345
|
} else if (!given.has(flag.flag)) {
|
|
3145
3346
|
if (!flag.optional) issues.push(controller_decorator.usageIssue({
|
|
3146
3347
|
key: 'meocord.usage.missing',
|
|
@@ -3168,29 +3369,137 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
3168
3369
|
return issues;
|
|
3169
3370
|
}
|
|
3170
3371
|
/**
|
|
3171
|
-
* What is wrong with a command whose words a message named but whose pattern it does not fit,
|
|
3172
|
-
*
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3372
|
+
* What is wrong with a command whose words a message named but whose pattern it does not fit, read word by word
|
|
3373
|
+
* against the pattern: each word or required param it left out, a word given another in its place, else words
|
|
3374
|
+
* beyond what the pattern takes, else the first word its param's built-in type does not take; and then each required
|
|
3375
|
+
* flag it left out, as a message whose words fit is told. With none of those true, there are none, and the usage stands
|
|
3376
|
+
* alone.
|
|
3377
|
+
*/ function missingParams(route, given, types, guild) {
|
|
3378
|
+
const { words, flags } = given;
|
|
3379
|
+
const missing = [];
|
|
3380
|
+
// The flags given bare in place of the pattern's own `--` words, which are those words and set no flag
|
|
3381
|
+
const literal = new Set();
|
|
3382
|
+
let unfit;
|
|
3383
|
+
let w = 0;
|
|
3384
|
+
for (const token of route.tokens){
|
|
3385
|
+
if ('literal' in token) {
|
|
3386
|
+
if (w < words.length && caseKey(words[w].value, route.caseSensitive) === caseKey(token.literal, route.caseSensitive)) {
|
|
3387
|
+
w++;
|
|
3388
|
+
continue;
|
|
3180
3389
|
}
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3390
|
+
// A pattern's own `--` word given bare in its place takes no word; another word in its place is not taken
|
|
3391
|
+
const word = flags.find((flag)=>!literal.has(flag) && isLiteralFlag(flag, token.literal, w, route.caseSensitive));
|
|
3392
|
+
if (word) literal.add(word);
|
|
3393
|
+
else missing.push(controller_decorator.usageIssue({
|
|
3394
|
+
key: 'meocord.usage.missing',
|
|
3395
|
+
params: {
|
|
3396
|
+
param: token.literal
|
|
3397
|
+
}
|
|
3398
|
+
}));
|
|
3399
|
+
} else if (w === words.length) {
|
|
3400
|
+
if (!token.optional) missing.push(controller_decorator.usageIssue({
|
|
3401
|
+
key: 'meocord.usage.missing',
|
|
3402
|
+
params: {
|
|
3403
|
+
param: token.param
|
|
3404
|
+
}
|
|
3405
|
+
}, token.param));
|
|
3406
|
+
} else if (token.rest) {
|
|
3407
|
+
w = words.length;
|
|
3408
|
+
} else {
|
|
3409
|
+
// Words fill the params in order, as the usage shows them
|
|
3410
|
+
const word = words[w++].value;
|
|
3411
|
+
const type = token.type;
|
|
3412
|
+
if (!unfit && type !== undefined && isUnfit(type, word, route.caseSensitive, guild)) {
|
|
3413
|
+
unfit = {
|
|
3414
|
+
key: token.param,
|
|
3415
|
+
label: token.param,
|
|
3416
|
+
type,
|
|
3417
|
+
word
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
// With none of these, the reason may be one MeoCord has no text for, as a role's name the matcher takes only as a
|
|
3423
|
+
// mention or an ID
|
|
3424
|
+
const reasons = missing.length > 0 ? missing : w < words.length ? [
|
|
3184
3425
|
controller_decorator.usageIssue({
|
|
3185
3426
|
key: 'meocord.usage.tooManyWords'
|
|
3186
3427
|
})
|
|
3428
|
+
] : unfit ? [
|
|
3429
|
+
wrongType(unfit, types)
|
|
3430
|
+
] : [];
|
|
3431
|
+
// So one reply names all the message lacks, rather than the flags only once its words fit
|
|
3432
|
+
const missingFlags = route.flags.filter((flag)=>flag.type !== undefined && !flag.optional && !flags.some((sent)=>!literal.has(sent) && caseKey(sent.name, route.caseSensitive) === caseKey(flag.flag, route.caseSensitive))).map((flag)=>controller_decorator.usageIssue({
|
|
3433
|
+
key: 'meocord.usage.missing',
|
|
3434
|
+
params: {
|
|
3435
|
+
param: `--${flag.flag}`
|
|
3436
|
+
}
|
|
3437
|
+
}, flag.flag));
|
|
3438
|
+
return [
|
|
3439
|
+
...reasons,
|
|
3440
|
+
...missingFlags
|
|
3187
3441
|
];
|
|
3188
3442
|
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Whether a param of a type is known not to take a word: a built-in type or words to choose from that the word doesn't
|
|
3445
|
+
* fit. A role is also found by its name, so only a word that names none of the server's roles is unfit.
|
|
3446
|
+
*/ function isUnfit(type, word, caseSensitive, guild) {
|
|
3447
|
+
if (type === 'role') return guild !== null && roleNamed(guild, word) === undefined;
|
|
3448
|
+
return (type in BUILT_IN_TYPES || Boolean(controller_decorator.choicesOf(type))) && !fitsParamType(type, word, caseSensitive);
|
|
3449
|
+
}
|
|
3189
3450
|
/** Whether a route's params need resolving: it declares a type for one, or has flags. */ const hasTypedParams = (route)=>route.flags.length > 0 || route.tokens.some((token)=>'param' in token && token.type !== undefined);
|
|
3190
3451
|
|
|
3452
|
+
const logger = new controller_decorator.Logger('MeoCord');
|
|
3191
3453
|
/** Whether a route's scope lets it run where the message was sent. */ const fitsScope = (scope, inGuild)=>inGuild === undefined || scope === 'any' || scope === 'guild' === inGuild;
|
|
3192
3454
|
const PARAM = /^\{(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\.\.\.)?(\?)?\}$/;
|
|
3193
3455
|
const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
3456
|
+
/**
|
|
3457
|
+
* A whole brace pair that PARAM or FLAG refused, read loosely to tell its name from its type: anything up to `:` or `}`,
|
|
3458
|
+
* then the marks a param ends with, `...` and `?`, wherever they were written.
|
|
3459
|
+
*/ const LOOSE_FLAG = /^\{--([^:}]*?)(?::([^}]*?))?(\.\.\.)?(\?)?\}$/;
|
|
3460
|
+
const LOOSE_PARAM = /^\{([^:}]*?)(?::([^}]*?))?(\.\.\.)?(\?)?\}$/;
|
|
3461
|
+
/** A name or type with marks after it, as one written out of order carries them. */ const MARKED = /^(.*?)((?:\.\.\.|\?)+)$/;
|
|
3462
|
+
/** A param's name, and a flag's, which also starts with a letter, as a message could not give it otherwise. */ const PARAM_NAME = /^\w+$/;
|
|
3463
|
+
const FLAG_NAME = /^[A-Za-z]\w*$/;
|
|
3464
|
+
/** A param as its pattern writes it, which the grammar spells only one way. */ const writtenParam = (token)=>`{${token.param}${token.type ? `:${token.type}` : ''}${token.rest ? '...' : ''}${token.optional ? '?' : ''}}`;
|
|
3465
|
+
/** A flag as its pattern writes it. */ const writtenFlag = (flag)=>`{--${flag.flag}${flag.type ? `:${flag.type}` : ''}${flag.optional ? '?' : ''}}`;
|
|
3466
|
+
/**
|
|
3467
|
+
* Why a word with a brace in it is no param or flag: text around its braces, or a whole pair whose name, type or marks
|
|
3468
|
+
* the syntax doesn't take. Each names the rule broken, with the word mended as an example.
|
|
3469
|
+
*/ function notAParam(word) {
|
|
3470
|
+
const flag = LOOSE_FLAG.exec(word);
|
|
3471
|
+
if (flag) {
|
|
3472
|
+
const [, name, type, rest] = flag;
|
|
3473
|
+
if (rest || word.includes('...')) {
|
|
3474
|
+
return `"${word}" is not a flag: a flag doesn't take the rest of the message; a param does, such as {${mended(unmarked(name), 'param')}...}.`;
|
|
3475
|
+
}
|
|
3476
|
+
if (unmarked(name) !== name || type !== undefined && unmarked(type) !== type) {
|
|
3477
|
+
const typed = type === undefined ? '' : `:${unmarked(type)}`;
|
|
3478
|
+
return `"${word}" is not a flag: its ? goes last, as in {--${unmarked(name)}${typed}?}.`;
|
|
3479
|
+
}
|
|
3480
|
+
if (!FLAG_NAME.test(name)) {
|
|
3481
|
+
return `"${word}" is not a flag: a flag's name starts with a letter, then letters, digits or _, such as {--${mended(name, 'flag')}}.`;
|
|
3482
|
+
}
|
|
3483
|
+
return `"${word}" is not a flag: a type is a name, or words to choose from joined by |, such as {--${name}:on|off}.`;
|
|
3484
|
+
}
|
|
3485
|
+
const param = LOOSE_PARAM.exec(word);
|
|
3486
|
+
if (param) {
|
|
3487
|
+
const [, name, type] = param;
|
|
3488
|
+
if (unmarked(name) !== name || type !== undefined && unmarked(type) !== type) {
|
|
3489
|
+
const typed = type === undefined ? '' : `:${unmarked(type)}`;
|
|
3490
|
+
const ordered = `{${unmarked(name)}${typed}${word.includes('...') ? '...' : ''}${word.includes('?') ? '?' : ''}}`;
|
|
3491
|
+
return `"${word}" is not a param: its marks go last, ... then ?, as in ${ordered}.`;
|
|
3492
|
+
}
|
|
3493
|
+
if (!PARAM_NAME.test(name)) return `"${word}" is not a param: a param's name is ASCII letters, digits and _, such as {${mended(name, 'param')}}.`;
|
|
3494
|
+
return `"${word}" is not a param: a type is a name, or words to choose from joined by |, such as {${name}:on|off}.`;
|
|
3495
|
+
}
|
|
3496
|
+
return `"${word}" is not a param: a param is a whole word, such as {name}, {name:type}, {name...}, {name?} or a flag, {--name}.`;
|
|
3497
|
+
}
|
|
3498
|
+
/** A name or type without the marks written after it, `...` and `?`. */ const unmarked = (text)=>MARKED.exec(text)?.[1] ?? text;
|
|
3499
|
+
/** A name with each character the syntax doesn't take as `_`, and a flag's led by a letter, for an example. */ function mended(name, kind) {
|
|
3500
|
+
const word = name.replace(/\W/g, '_') || kind;
|
|
3501
|
+
return kind === 'flag' && !/^[A-Za-z]/.test(word) ? `flag${word}` : word;
|
|
3502
|
+
}
|
|
3194
3503
|
/**
|
|
3195
3504
|
* Reads a pattern into its words and flags. Throws for a pattern of flags alone, a param that is not a whole word, a
|
|
3196
3505
|
* name given twice, a rest param that is not last, a word after an optional param that is not optional too, an untyped
|
|
@@ -3226,9 +3535,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
3226
3535
|
words.forEach((word, index)=>{
|
|
3227
3536
|
const match = PARAM.exec(word);
|
|
3228
3537
|
if (!match) {
|
|
3229
|
-
if (/[{}]/.test(word))
|
|
3230
|
-
throw new Error(`"${word}" is not a param: a param is a whole word, such as {name}, {name:type}, {name...}, {name?} or a flag, {--name}.`);
|
|
3231
|
-
}
|
|
3538
|
+
if (/[{}]/.test(word)) throw new Error(notAParam(word));
|
|
3232
3539
|
tokens.push({
|
|
3233
3540
|
literal: word
|
|
3234
3541
|
});
|
|
@@ -3251,7 +3558,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
3251
3558
|
const next = tokens[index + 1];
|
|
3252
3559
|
if (!('param' in token) || !token.optional || !next) return;
|
|
3253
3560
|
if (!('param' in next) || !next.optional) {
|
|
3254
|
-
throw new Error(
|
|
3561
|
+
throw new Error(`${writtenParam(token)} is optional, so only optional params may follow it; "${'param' in next ? writtenParam(next) : next.literal}" is not.`);
|
|
3255
3562
|
}
|
|
3256
3563
|
if (token.type === undefined || token.type === 'string') {
|
|
3257
3564
|
throw new Error(`{${token.param}${token.type ? ':string' : ''}?} comes before another optional param, so it needs a type that tells its words ` + `from the next param's, such as {${token.param}:int?}: as text, it would take every word.`);
|
|
@@ -3264,10 +3571,15 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
3264
3571
|
rest: params.some((token)=>token.rest),
|
|
3265
3572
|
optional: params.some((token)=>token.optional)
|
|
3266
3573
|
});
|
|
3574
|
+
const inheritedNames = [
|
|
3575
|
+
...params.map((token)=>token.param),
|
|
3576
|
+
...flags.map((flag)=>flag.flag)
|
|
3577
|
+
].filter((name)=>name in Object.prototype);
|
|
3267
3578
|
return {
|
|
3268
3579
|
tokens,
|
|
3269
3580
|
flags,
|
|
3270
|
-
specificity
|
|
3581
|
+
specificity,
|
|
3582
|
+
inheritedNames
|
|
3271
3583
|
};
|
|
3272
3584
|
}
|
|
3273
3585
|
/** A prefix setting as a list, where `''` stands for none. */ function prefixList(prefix) {
|
|
@@ -3318,7 +3630,20 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
3318
3630
|
...mention
|
|
3319
3631
|
]);
|
|
3320
3632
|
}
|
|
3321
|
-
/**
|
|
3633
|
+
/** Where two routes of equal rank and shape part: the one with a required param where the other has an optional one comes first. */ function compareOptional(a, b) {
|
|
3634
|
+
for(let i = 0; i < Math.min(a.length, b.length); i++){
|
|
3635
|
+
const [aToken, bToken] = [
|
|
3636
|
+
a[i],
|
|
3637
|
+
b[i]
|
|
3638
|
+
];
|
|
3639
|
+
if ('param' in aToken && 'param' in bToken && aToken.optional !== bToken.optional) return aToken.optional ? 1 : -1;
|
|
3640
|
+
}
|
|
3641
|
+
return 0;
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Whether two routes accept some of the same messages: a start they share, and words that match alike. `'optional'`
|
|
3645
|
+
* when they do only because a required param and an optional one, in one position, both take a word there.
|
|
3646
|
+
*/ function sameMessages(a, b, options) {
|
|
3322
3647
|
// A case-insensitive word matches every message the case-sensitive one does
|
|
3323
3648
|
const exact = a.caseSensitive && b.caseSensitive;
|
|
3324
3649
|
const key = (start)=>typeof start === 'string' && !exact ? start.toLowerCase() : start;
|
|
@@ -3336,13 +3661,55 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
3336
3661
|
].some((start)=>theirs.has(key(start)));
|
|
3337
3662
|
});
|
|
3338
3663
|
if (!startAlike || a.tokens.length !== b.tokens.length) return false;
|
|
3339
|
-
|
|
3664
|
+
let byOptional = false;
|
|
3665
|
+
const alike = a.tokens.every((token, i)=>{
|
|
3340
3666
|
const other = b.tokens[i];
|
|
3341
3667
|
if ('literal' in token) {
|
|
3342
3668
|
return 'literal' in other && (exact ? token.literal === other.literal : token.literal.toLowerCase() === other.literal.toLowerCase());
|
|
3343
3669
|
}
|
|
3344
|
-
|
|
3670
|
+
if (!('param' in other) || token.rest !== other.rest) return false;
|
|
3671
|
+
if (token.optional !== other.optional) byOptional = true;
|
|
3672
|
+
return true;
|
|
3345
3673
|
});
|
|
3674
|
+
return alike && (byOptional ? 'optional' : true);
|
|
3675
|
+
}
|
|
3676
|
+
/** What a static empty prefix list does now, which a prefix function's `[]` doesn't: take messages as they are. */ const EMPTY_PREFIX_LIST = "`[]` takes messages as they are, with no prefix; write `''` for that";
|
|
3677
|
+
/**
|
|
3678
|
+
* The warnings for message handlers a bot starts with today though a message can't reach them as written, each once,
|
|
3679
|
+
* for the app or the handler: a static empty prefix list, which takes messages as they are, where a prefix function's
|
|
3680
|
+
* `[]` starts no command; and two flags of a command that reads flags in any case whose names differ only in case, the
|
|
3681
|
+
* second of which is never set. The next major version (5.0) changes the first and refuses the second.
|
|
3682
|
+
*/ function messageRouteWarnings(routes, options) {
|
|
3683
|
+
const warnings = [];
|
|
3684
|
+
if (Array.isArray(options.prefix) && options.prefix.length === 0) {
|
|
3685
|
+
warnings.push(`@MeoCord({ messages: { prefix: [] } }): ${EMPTY_PREFIX_LIST}. In the next major version (5.0), \`[]\` starts no command, ` + "as a prefix function's `[]` does.");
|
|
3686
|
+
}
|
|
3687
|
+
const seen = new Map();
|
|
3688
|
+
for (const route of routes){
|
|
3689
|
+
// A handler's aliases share its options and flags, so each is read once; one method can carry several handlers
|
|
3690
|
+
const pattern = route.aliasOf ?? route.pattern;
|
|
3691
|
+
const methods = seen.get(route.controllerClass) ?? seen.set(route.controllerClass, new Map()).get(route.controllerClass);
|
|
3692
|
+
const patterns = methods.get(route.method) ?? methods.set(route.method, new Set()).get(route.method);
|
|
3693
|
+
if (patterns.has(pattern)) continue;
|
|
3694
|
+
patterns.add(pattern);
|
|
3695
|
+
const handler = `${route.controllerClass.name}.${route.method}: @MessageHandler('${pattern}'`;
|
|
3696
|
+
const own = controller_decorator.getMessageHandlers(route.controllerClass.prototype).find((candidate)=>candidate.method === route.method && candidate.pattern === pattern)?.options;
|
|
3697
|
+
if (Array.isArray(own?.prefix) && own.prefix.length === 0) {
|
|
3698
|
+
warnings.push(`${handler}, { prefix: [] }): ${EMPTY_PREFIX_LIST}, or \`false\` for no prefix and no mention. In the next major ` + 'version (5.0), `[]` starts no command.');
|
|
3699
|
+
}
|
|
3700
|
+
if (route.caseSensitive) continue;
|
|
3701
|
+
const first = new Map();
|
|
3702
|
+
for (const flag of route.flags){
|
|
3703
|
+
const same = first.get(flag.flag.toLowerCase());
|
|
3704
|
+
if (!same) {
|
|
3705
|
+
first.set(flag.flag.toLowerCase(), flag);
|
|
3706
|
+
continue;
|
|
3707
|
+
}
|
|
3708
|
+
warnings.push(`${handler}): ${writtenFlag(same)} and ${writtenFlag(flag)} differ only in case, and this command reads flags in any ` + `case, so --${flag.flag} always sets ${writtenFlag(same)} and ${writtenFlag(flag)} is never set. Rename one, or set ` + 'caseSensitive: true. The next major version (5.0) refuses this.');
|
|
3709
|
+
break;
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
return warnings;
|
|
3346
3713
|
}
|
|
3347
3714
|
/**
|
|
3348
3715
|
* Every patterned message handler of the given controllers, most specific first, read from metadata
|
|
@@ -3411,14 +3778,21 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
3411
3778
|
});
|
|
3412
3779
|
}
|
|
3413
3780
|
}
|
|
3414
|
-
routes.sort((a, b)=>b.specificity - a.specificity || compareShapes(a.tokens, b.tokens) || Number(takesAppStart(a)) - Number(takesAppStart(b)));
|
|
3781
|
+
routes.sort((a, b)=>b.specificity - a.specificity || compareShapes(a.tokens, b.tokens) || compareOptional(a.tokens, b.tokens) || Number(takesAppStart(a)) - Number(takesAppStart(b)));
|
|
3415
3782
|
for(let i = 0; i < routes.length; i++){
|
|
3416
3783
|
for(let j = i + 1; j < routes.length; j++){
|
|
3417
3784
|
const [a, b] = [
|
|
3418
3785
|
routes[i],
|
|
3419
3786
|
routes[j]
|
|
3420
3787
|
];
|
|
3421
|
-
|
|
3788
|
+
const same = sameMessages(a, b, options);
|
|
3789
|
+
if (!same) continue;
|
|
3790
|
+
// Ranked by their params, so the same one answers whatever the order; 5.0 refuses these as it does the rest
|
|
3791
|
+
if (same === 'optional') {
|
|
3792
|
+
if (a.controllerClass === b.controllerClass && a.method === b.method) continue;
|
|
3793
|
+
controller_decorator.warnOnce(logger, `${a.controllerClass.name}.${a.method}: ${describeRoute(a)} and ${describeRoute(b)} in ${b.controllerClass.name}.${b.method} can ` + `take the same messages, as one takes a word where the other has an optional param; ${a.controllerClass.name}.${a.method} ` + 'answers those. Change one pattern, or give one its own prefix. The next major version (5.0) stops the bot at startup for these.');
|
|
3794
|
+
continue;
|
|
3795
|
+
}
|
|
3422
3796
|
// One handler under two spellings, such as 'hello' and 'Hello', is one route, its pattern's over an alias's
|
|
3423
3797
|
if (a.controllerClass === b.controllerClass && a.method === b.method) {
|
|
3424
3798
|
if (a.aliasOf && !b.aliasOf) routes[i] = b;
|
|
@@ -3463,7 +3837,7 @@ const FLAG = /^\{--(\w+)(?::([\w-]+(?:\|[\w-]+)*))?(\?)?\}$/;
|
|
|
3463
3837
|
if (scope !== 'dm') return;
|
|
3464
3838
|
const param = tokens.find((token)=>'param' in token && token.type !== undefined && isGuildType(token.type));
|
|
3465
3839
|
const flag = flags.find((candidate)=>candidate.type !== undefined && isGuildType(candidate.type));
|
|
3466
|
-
const shown = param ?
|
|
3840
|
+
const shown = param ? writtenParam(param) : flag && writtenFlag(flag);
|
|
3467
3841
|
if (shown) throw new Error(`scope is 'dm', but ${shown} is found only in a server.`);
|
|
3468
3842
|
}
|
|
3469
3843
|
/** Refuses a handler's `mention` other than `'only'`. */ function assertMention({ mention }) {
|
|
@@ -3575,11 +3949,11 @@ const trieNode = ()=>({
|
|
|
3575
3949
|
for(let t = 0; t < tail.length && w < words.length; t++){
|
|
3576
3950
|
const token = tail[t];
|
|
3577
3951
|
if (token.rest) {
|
|
3578
|
-
params
|
|
3952
|
+
controller_decorator.setOwn(params, token.param, restFrom(text, words[w].start, cuts));
|
|
3579
3953
|
return params;
|
|
3580
3954
|
}
|
|
3581
3955
|
if (t < tail.length - 1 && !fitsParamType(token.type, words[w].value, caseSensitive)) continue;
|
|
3582
|
-
params
|
|
3956
|
+
controller_decorator.setOwn(params, token.param, words[w++].value);
|
|
3583
3957
|
}
|
|
3584
3958
|
return w === words.length ? params : undefined;
|
|
3585
3959
|
}
|
|
@@ -3624,9 +3998,12 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
3624
3998
|
return;
|
|
3625
3999
|
}
|
|
3626
4000
|
if ('literal' in token) {
|
|
4001
|
+
var _node1;
|
|
3627
4002
|
const key = wordKey(token.literal, route.caseSensitive);
|
|
3628
4003
|
let next = node.words.get(key);
|
|
3629
4004
|
if (!next) node.words.set(key, next = trieNode());
|
|
4005
|
+
// Such a word typed bare is taken out as a flag before the words are walked, so it is matched as one too
|
|
4006
|
+
if (route.flags.length > 0 && token.literal.startsWith('--')) ((_node1 = node).flagWords ?? (_node1.flagWords = new Map())).set(key, next);
|
|
3630
4007
|
node = next;
|
|
3631
4008
|
continue;
|
|
3632
4009
|
}
|
|
@@ -3645,6 +4022,8 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
3645
4022
|
];
|
|
3646
4023
|
for (const group of all){
|
|
3647
4024
|
const flagged = group.flagged;
|
|
4025
|
+
// A pattern's own leading `--` word stays reachable there: given bare, it is taken out as a flag, so the message
|
|
4026
|
+
// seems to start with one
|
|
3648
4027
|
if (flagged) group.flaggedAfterFlag = {
|
|
3649
4028
|
...flagged,
|
|
3650
4029
|
words: new Map()
|
|
@@ -3685,7 +4064,16 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
3685
4064
|
if (first === undefined || node.param || node.rests.length > 0 || node.tails.length > 0 || node.ends.length > 0) return true;
|
|
3686
4065
|
return node.words.has(first);
|
|
3687
4066
|
}
|
|
3688
|
-
/**
|
|
4067
|
+
/**
|
|
4068
|
+
* The ranks of every route the words reach from `node`. A pattern's own `--` word is reached by that word, or by a flag
|
|
4069
|
+
* of its name given bare in its place, which takes no word.
|
|
4070
|
+
*/ function reach(node, words, i, text, cuts, caseSensitive, flags, found) {
|
|
4071
|
+
if (node.flagWords) {
|
|
4072
|
+
for (const flag of flags){
|
|
4073
|
+
const next = flag.at === i && flag.value === undefined ? node.flagWords.get(wordKey(`--${flag.name}`, caseSensitive)) : undefined;
|
|
4074
|
+
if (next) reach(next, words, i, text, cuts, caseSensitive, flags, found);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
3689
4077
|
for (const tail of node.tails)if (assignTail(tail.tokens, words, i, text, cuts, caseSensitive)) found.push(tail.rank);
|
|
3690
4078
|
if (i === words.length) {
|
|
3691
4079
|
for (const rank of node.ends)found.push(rank);
|
|
@@ -3693,22 +4081,34 @@ const wordKey = (word, caseSensitive)=>caseSensitive ? word : word.toLowerCase()
|
|
|
3693
4081
|
}
|
|
3694
4082
|
for (const rank of node.rests)found.push(rank);
|
|
3695
4083
|
const next = node.words.get(wordKey(words[i].value, caseSensitive));
|
|
3696
|
-
if (next) reach(next, words, i + 1, text, cuts, caseSensitive, found);
|
|
3697
|
-
if (node.param) reach(node.param, words, i + 1, text, cuts, caseSensitive, found);
|
|
4084
|
+
if (next) reach(next, words, i + 1, text, cuts, caseSensitive, flags, found);
|
|
4085
|
+
if (node.param) reach(node.param, words, i + 1, text, cuts, caseSensitive, flags, found);
|
|
3698
4086
|
}
|
|
3699
|
-
/**
|
|
4087
|
+
/**
|
|
4088
|
+
* The params a route's words capture, given words its pattern is known to match. A pattern's own `--` word that the
|
|
4089
|
+
* message gave as a flag, one of `taken`, takes no word.
|
|
4090
|
+
*/ function paramsOf$1(route, words, text, cuts, taken) {
|
|
3700
4091
|
const tail = tailStart(route.tokens);
|
|
3701
|
-
const params =
|
|
3702
|
-
|
|
4092
|
+
const params = {};
|
|
4093
|
+
let left = taken;
|
|
4094
|
+
let w = 0;
|
|
4095
|
+
for(let i = 0; i < (tail === -1 ? route.tokens.length : tail) && w < words.length; i++){
|
|
3703
4096
|
const token = route.tokens[i];
|
|
3704
|
-
if ('literal' in token)
|
|
4097
|
+
if ('literal' in token) {
|
|
4098
|
+
if (left > 0 && token.literal.startsWith('--') && wordKey(words[w].value, route.caseSensitive) !== wordKey(token.literal, route.caseSensitive)) left--;
|
|
4099
|
+
else w++;
|
|
4100
|
+
continue;
|
|
4101
|
+
}
|
|
3705
4102
|
if (token.rest) {
|
|
3706
|
-
params
|
|
3707
|
-
|
|
4103
|
+
controller_decorator.setOwn(params, token.param, restFrom(text, words[w].start, cuts));
|
|
4104
|
+
return params;
|
|
3708
4105
|
}
|
|
3709
|
-
params
|
|
4106
|
+
controller_decorator.setOwn(params, token.param, words[w++].value);
|
|
3710
4107
|
}
|
|
3711
|
-
return params
|
|
4108
|
+
return tail === -1 ? params : {
|
|
4109
|
+
...params,
|
|
4110
|
+
...assignTail(route.tokens.slice(tail), words, w, text, cuts, route.caseSensitive)
|
|
4111
|
+
};
|
|
3712
4112
|
}
|
|
3713
4113
|
/**
|
|
3714
4114
|
* The text after the start a message used for the group's routes, or `undefined` when it used none: a mention
|
|
@@ -3761,7 +4161,7 @@ const indexes = new WeakMap();
|
|
|
3761
4161
|
const found = [];
|
|
3762
4162
|
if (mayEnter(group.root, first)) {
|
|
3763
4163
|
const words = wordsOf(rest);
|
|
3764
|
-
reach(group.root, words, 0, rest, NONE, group.caseSensitive, found);
|
|
4164
|
+
reach(group.root, words, 0, rest, NONE, group.caseSensitive, NONE, found);
|
|
3765
4165
|
for (const rank of found){
|
|
3766
4166
|
if (fitsScope(routes[rank].scope, starts.inGuild)) {
|
|
3767
4167
|
if (!best || rank < best.rank) best = {
|
|
@@ -3790,7 +4190,7 @@ const indexes = new WeakMap();
|
|
|
3790
4190
|
};
|
|
3791
4191
|
found.length = 0;
|
|
3792
4192
|
const from = scanned.words[0]?.start === 0 ? flagged : group.flaggedAfterFlag;
|
|
3793
|
-
reach(from, scanned.words, 0, rest, scanned.cuts, group.caseSensitive, found);
|
|
4193
|
+
reach(from, scanned.words, 0, rest, scanned.cuts, group.caseSensitive, scanned.flags, found);
|
|
3794
4194
|
for (const rank of found){
|
|
3795
4195
|
const candidate = {
|
|
3796
4196
|
rank,
|
|
@@ -3807,11 +4207,16 @@ const indexes = new WeakMap();
|
|
|
3807
4207
|
const chosen = best ?? outside;
|
|
3808
4208
|
if (!chosen) return undefined;
|
|
3809
4209
|
const route = routes[chosen.rank];
|
|
3810
|
-
|
|
3811
|
-
|
|
4210
|
+
// The flags the pattern's own `--` words took are those words, and set none of its flags
|
|
4211
|
+
const taken = literalFlags(route.tokens, route.caseSensitive, chosen.words, chosen.flags);
|
|
4212
|
+
const params = paramsOf$1(route, chosen.words, chosen.rest, chosen.cuts, taken.size);
|
|
4213
|
+
chosen.flags.forEach(({ name, value }, index)=>{
|
|
4214
|
+
if (taken.has(index)) return;
|
|
3812
4215
|
const declared = route.flags.find((flag)=>wordKey(flag.flag, route.caseSensitive) === wordKey(name, route.caseSensitive));
|
|
3813
|
-
if (declared) params
|
|
3814
|
-
}
|
|
4216
|
+
if (declared) controller_decorator.setOwn(params, declared.flag, value ?? '');
|
|
4217
|
+
});
|
|
4218
|
+
// A param or flag left out reads as left out, not as what Object.prototype has under its name
|
|
4219
|
+
if (route.inheritedNames.length > 0) controller_decorator.shadowInherited(params, route.inheritedNames);
|
|
3815
4220
|
return {
|
|
3816
4221
|
route,
|
|
3817
4222
|
params,
|
|
@@ -3841,19 +4246,28 @@ const indexes = new WeakMap();
|
|
|
3841
4246
|
if (first !== undefined && !group.commands.has(first)) continue;
|
|
3842
4247
|
const plain = splitWords(rest);
|
|
3843
4248
|
// A route with flags counts the words left once the message's flags are taken out
|
|
3844
|
-
const
|
|
4249
|
+
const scanned = mayEnter(group.flagged, first) && rest.includes('--') ? splitFlagWords(rest) : {
|
|
4250
|
+
words: plain,
|
|
4251
|
+
flags: NONE
|
|
4252
|
+
};
|
|
3845
4253
|
const candidates = group.commands.get(wordKey(plain[0]?.value ?? '', group.caseSensitive)) ?? [];
|
|
3846
4254
|
for (const rank of candidates){
|
|
3847
4255
|
if (best && rank >= best.rank) break;
|
|
3848
|
-
const
|
|
3849
|
-
const
|
|
3850
|
-
|
|
4256
|
+
const flagged = routes[rank].flags.length > 0;
|
|
4257
|
+
const words = flagged ? scanned.words : plain;
|
|
4258
|
+
const flags = flagged ? scanned.flags : NONE;
|
|
4259
|
+
const taken = commandWordsTaken(commandWordsOf(routes[rank].tokens), words, flags, group.caseSensitive);
|
|
4260
|
+
if (taken !== -1) {
|
|
3851
4261
|
const named = {
|
|
3852
4262
|
rank,
|
|
3853
4263
|
start: text.slice(0, text.length - rest.length),
|
|
3854
|
-
given:
|
|
4264
|
+
given: {
|
|
4265
|
+
words,
|
|
4266
|
+
flags
|
|
4267
|
+
}
|
|
3855
4268
|
};
|
|
3856
|
-
|
|
4269
|
+
// Where it works as dispatch answers it, so a DM's usage comes from a route that works there
|
|
4270
|
+
if (fitsScope(effectiveScope(routes[rank]), starts.inGuild)) {
|
|
3857
4271
|
best = named;
|
|
3858
4272
|
break;
|
|
3859
4273
|
}
|
|
@@ -3868,6 +4282,17 @@ const indexes = new WeakMap();
|
|
|
3868
4282
|
given: chosen.given
|
|
3869
4283
|
};
|
|
3870
4284
|
}
|
|
4285
|
+
/**
|
|
4286
|
+
* How many of a message's words a route's command words take, or -1 when the message names another command. A
|
|
4287
|
+
* command's own `--` word may be given bare as a flag in its place, and takes no word.
|
|
4288
|
+
*/ function commandWordsTaken(command, words, flags, caseSensitive) {
|
|
4289
|
+
let w = 0;
|
|
4290
|
+
for (const word of command){
|
|
4291
|
+
if (words[w] && wordKey(words[w].value, caseSensitive) === wordKey(word, caseSensitive)) w++;
|
|
4292
|
+
else if (!flags.some((flag)=>isLiteralFlag(flag, word, w, caseSensitive))) return -1;
|
|
4293
|
+
}
|
|
4294
|
+
return w;
|
|
4295
|
+
}
|
|
3871
4296
|
/**
|
|
3872
4297
|
* The subcommands a message names the parent of, when no pattern matches it and it names no command: the routes whose
|
|
3873
4298
|
* command words begin with the most of the message's words, short of all of them, as `!config` does for `config set …`
|
|
@@ -3910,11 +4335,18 @@ const indexes = new WeakMap();
|
|
|
3910
4335
|
}
|
|
3911
4336
|
return found.length > 0 ? found : undefined;
|
|
3912
4337
|
}
|
|
4338
|
+
/**
|
|
4339
|
+
* Whether a message has text a handler could take; dispatch runs no handler for one without. Content that isn't a
|
|
4340
|
+
* string, as a mock's placeholder without strict mocks, is no text.
|
|
4341
|
+
*/ function hasMessageText(content) {
|
|
4342
|
+
return typeof content === 'string' && content.trim() !== '';
|
|
4343
|
+
}
|
|
3913
4344
|
/**
|
|
3914
4345
|
* What a test calling a handler with a message alone passes as its params, as dispatch builds them: `undefined` for a
|
|
3915
|
-
* listener, `{}` for a message
|
|
3916
|
-
* the command without fitting it, the route, start and `given`, the
|
|
3917
|
-
* dispatch would give the message to this handler among
|
|
4346
|
+
* listener, `{}` for a message whose content isn't a string, as a mock's without strict mocks, else what the pattern
|
|
4347
|
+
* captures with the route and start; for one naming the command without fitting it, the route, start and `given`, the
|
|
4348
|
+
* message's words and flags. Either only when dispatch would give the message to this handler among
|
|
4349
|
+
* `controllers`; otherwise why it does not reach the handler, a message with no text included.
|
|
3918
4350
|
*/ async function messageParamsFor(controllerClass, methodName, message, options, controllers = [
|
|
3919
4351
|
controllerClass
|
|
3920
4352
|
]) {
|
|
@@ -3926,6 +4358,13 @@ const indexes = new WeakMap();
|
|
|
3926
4358
|
if (typeof message.content !== 'string') return {
|
|
3927
4359
|
params: {}
|
|
3928
4360
|
};
|
|
4361
|
+
const handler = `${controllerClass.name}.${methodName}`;
|
|
4362
|
+
const pattern = routes.find((route)=>!route.aliasOf)?.pattern ?? routes[0].pattern;
|
|
4363
|
+
if (!hasMessageText(message.content)) {
|
|
4364
|
+
return {
|
|
4365
|
+
mismatch: `message has no text, so dispatch runs no handler for it: give the mock the content ${handler}'s pattern '${pattern}' takes.`
|
|
4366
|
+
};
|
|
4367
|
+
}
|
|
3929
4368
|
const botId = message.client?.user?.id;
|
|
3930
4369
|
const all = buildMessageRoutes(controllers, options);
|
|
3931
4370
|
const starts = await messageStartsFor(all, options, message, typeof botId === 'string' ? botId : undefined);
|
|
@@ -3934,7 +4373,7 @@ const indexes = new WeakMap();
|
|
|
3934
4373
|
if (target && (target.route.controllerClass !== controllerClass || target.route.method !== methodName)) {
|
|
3935
4374
|
const other = `${target.route.controllerClass.name}.${target.route.method}`;
|
|
3936
4375
|
return {
|
|
3937
|
-
mismatch: `message '${message.content}' does not reach ${
|
|
4376
|
+
mismatch: `message '${message.content}' does not reach ${handler}: dispatch runs ${other}.`
|
|
3938
4377
|
};
|
|
3939
4378
|
}
|
|
3940
4379
|
if (target && 'params' in target) return {
|
|
@@ -3948,9 +4387,8 @@ const indexes = new WeakMap();
|
|
|
3948
4387
|
start: target.start,
|
|
3949
4388
|
given: target.given
|
|
3950
4389
|
};
|
|
3951
|
-
const pattern = routes.find((route)=>!route.aliasOf)?.pattern ?? routes[0].pattern;
|
|
3952
4390
|
return {
|
|
3953
|
-
mismatch: `message '${message.content}' does not match ${
|
|
4391
|
+
mismatch: `message '${message.content}' does not match ${handler}'s pattern '${pattern}'.`
|
|
3954
4392
|
};
|
|
3955
4393
|
}
|
|
3956
4394
|
|
|
@@ -3979,12 +4417,13 @@ const indexes = new WeakMap();
|
|
|
3979
4417
|
const text = content.trim();
|
|
3980
4418
|
const rest = afterStart(text, starts.prefixes.filter((prefix)=>prefix !== ''), starts.mention, caseSensitive);
|
|
3981
4419
|
if (!rest) return undefined;
|
|
3982
|
-
const [first] = splitWords(rest);
|
|
4420
|
+
const [first, second] = splitWords(rest);
|
|
3983
4421
|
const key = (word)=>caseSensitive ? word : word.toLowerCase();
|
|
3984
4422
|
if (!first || !words.some((word)=>key(word) === key(first.value))) return undefined;
|
|
4423
|
+
// From where the next word starts, as the help word may have been written in quotes
|
|
3985
4424
|
return {
|
|
3986
4425
|
start: text.slice(0, text.length - rest.length),
|
|
3987
|
-
query: rest.slice(
|
|
4426
|
+
query: second ? rest.slice(second.start).trim() : ''
|
|
3988
4427
|
};
|
|
3989
4428
|
}
|
|
3990
4429
|
/** The start a caller would type a route with here: its own prefix, a mention, or the start the help message used. */ function displayStart(route, start, starts) {
|
|
@@ -3993,11 +4432,6 @@ const indexes = new WeakMap();
|
|
|
3993
4432
|
if (route.prefix !== undefined) return route.prefix[0] ?? '';
|
|
3994
4433
|
return start;
|
|
3995
4434
|
}
|
|
3996
|
-
/** Where a route works: its scope, narrowed to servers by a `member`, `role` or `channel` param or flag. */ function effectiveScope(route) {
|
|
3997
|
-
if (route.scope !== 'any') return route.scope;
|
|
3998
|
-
const guildOnly = route.tokens.some((token)=>'param' in token && token.type !== undefined && isGuildType(token.type)) || route.flags.some((flag)=>flag.type !== undefined && isGuildType(flag.type));
|
|
3999
|
-
return guildOnly ? 'guild' : 'any';
|
|
4000
|
-
}
|
|
4001
4435
|
/** A route's params and flags, each with what it takes in words. */ function paramsOf(route, types, render) {
|
|
4002
4436
|
const params = route.tokens.flatMap((token)=>{
|
|
4003
4437
|
if (!('param' in token)) return [];
|
|
@@ -4298,6 +4732,28 @@ const COMMAND_TYPES = new Set([
|
|
|
4298
4732
|
};
|
|
4299
4733
|
}
|
|
4300
4734
|
/** The message routes the app's dispatcher reads, by the app's registry, so its help and dispatch read one table. */ const dispatchedRoutes = new WeakMap();
|
|
4735
|
+
/** What every entry has: its class and method, and the metadata on them. */ function entryBase(controller, method) {
|
|
4736
|
+
const context = new controller_decorator.HandlerExecutionContext({
|
|
4737
|
+
controller,
|
|
4738
|
+
methodName: method,
|
|
4739
|
+
args: []
|
|
4740
|
+
});
|
|
4741
|
+
return {
|
|
4742
|
+
controller,
|
|
4743
|
+
method,
|
|
4744
|
+
get: context.get.bind(context),
|
|
4745
|
+
getAll: context.getAll.bind(context)
|
|
4746
|
+
};
|
|
4747
|
+
}
|
|
4748
|
+
const lateEventSources = new WeakMap();
|
|
4749
|
+
// The late sources each registry's cached entries were read with, so a list made before they arrived is read again
|
|
4750
|
+
const collectedWith = new WeakMap();
|
|
4751
|
+
/**
|
|
4752
|
+
* Has `registry` list the `@On` and `@Once` handlers that run on `sources`, the instances the app attaches them for,
|
|
4753
|
+
* where they are on a class it does not list already.
|
|
4754
|
+
*/ function listEventSources(registry, sources) {
|
|
4755
|
+
lateEventSources.set(registry, sources);
|
|
4756
|
+
}
|
|
4301
4757
|
/** Has `registry` work out message help from `routes`, the table the app's dispatcher routes messages with. */ function shareMessageRoutes(registry, routes) {
|
|
4302
4758
|
dispatchedRoutes.set(registry, routes);
|
|
4303
4759
|
}
|
|
@@ -4338,7 +4794,11 @@ const COMMAND_TYPES = new Set([
|
|
|
4338
4794
|
* @param filter - Narrows the list by kind, controller, or both.
|
|
4339
4795
|
* @returns The handlers, class by class in the order the app makes its classes, each after what it injects.
|
|
4340
4796
|
*/ list(filter = {}) {
|
|
4341
|
-
|
|
4797
|
+
const late = lateEventSources.get(this);
|
|
4798
|
+
if (!this.entries || collectedWith.get(this) !== late) {
|
|
4799
|
+
this.entries = this.collect();
|
|
4800
|
+
collectedWith.set(this, late);
|
|
4801
|
+
}
|
|
4342
4802
|
return this.entries.filter((entry)=>(filter.kind === undefined || entry.kind === filter.kind) && (filter.controller === undefined || entry.controller === filter.controller));
|
|
4343
4803
|
}
|
|
4344
4804
|
/**
|
|
@@ -4362,8 +4822,9 @@ const COMMAND_TYPES = new Set([
|
|
|
4362
4822
|
* }
|
|
4363
4823
|
* ```
|
|
4364
4824
|
*/ async messageHelp(message, query) {
|
|
4825
|
+
// Until a dispatcher shares its table, the same one built from the same controllers, so help lists what dispatch reaches
|
|
4365
4826
|
const routes = dispatchedRoutes.get(this) ?? this.messageRoutes ?? (this.messageRoutes = buildMessageRoutes([
|
|
4366
|
-
...this.
|
|
4827
|
+
...this.controllers
|
|
4367
4828
|
], this.messages));
|
|
4368
4829
|
const starts = await messageStarts(this.messages, message, message.client?.user?.id);
|
|
4369
4830
|
const text = (message.content ?? '').trim();
|
|
@@ -4393,19 +4854,7 @@ const COMMAND_TYPES = new Set([
|
|
|
4393
4854
|
const entries = [];
|
|
4394
4855
|
for (const controller of this.classes){
|
|
4395
4856
|
const prototype = controller.prototype;
|
|
4396
|
-
const base = (method)=>
|
|
4397
|
-
const context = new controller_decorator.HandlerExecutionContext({
|
|
4398
|
-
controller,
|
|
4399
|
-
methodName: method,
|
|
4400
|
-
args: []
|
|
4401
|
-
});
|
|
4402
|
-
return {
|
|
4403
|
-
controller,
|
|
4404
|
-
method,
|
|
4405
|
-
get: context.get.bind(context),
|
|
4406
|
-
getAll: context.getAll.bind(context)
|
|
4407
|
-
};
|
|
4408
|
-
};
|
|
4857
|
+
const base = (method)=>entryBase(controller, method);
|
|
4409
4858
|
for (const [name, metas] of Object.entries(controller_decorator.getCommandMap(prototype) ?? {})){
|
|
4410
4859
|
for (const { methodName, type, builder } of metas){
|
|
4411
4860
|
if (COMMAND_TYPES.has(type)) {
|
|
@@ -4468,34 +4917,35 @@ const COMMAND_TYPES = new Set([
|
|
|
4468
4917
|
});
|
|
4469
4918
|
}
|
|
4470
4919
|
}
|
|
4920
|
+
// The classes the app reaches only as something it makes, listed once each, with the handlers that run on them
|
|
4921
|
+
const listed = new Set(this.classes);
|
|
4922
|
+
for (const { cls, handlers } of lateEventSources.get(this) ?? []){
|
|
4923
|
+
if (listed.has(cls)) continue;
|
|
4924
|
+
listed.add(cls);
|
|
4925
|
+
for (const { event, method, once } of handlers)entries.push({
|
|
4926
|
+
...entryBase(cls, method),
|
|
4927
|
+
kind: 'event',
|
|
4928
|
+
name: event,
|
|
4929
|
+
once
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4471
4932
|
return entries;
|
|
4472
4933
|
}
|
|
4473
4934
|
/**
|
|
4474
|
-
* @param classes - The app's classes to read handlers from. The factory fills the
|
|
4475
|
-
* once the app is bound; entries are read on the first {@link list}.
|
|
4935
|
+
* @param classes - The app's classes to read handlers from, controllers and services alike. The factory fills the
|
|
4936
|
+
* list once the app is bound; entries are read on the first {@link list}.
|
|
4476
4937
|
* @param messages - The app's `messages` options, whose `caseSensitive` message entries' `matches` follows.
|
|
4477
4938
|
* @param translator - The app's translator, read when {@link messageHelp} words its labels; none for English.
|
|
4478
|
-
|
|
4939
|
+
* @param controllers - The app's controllers, the classes dispatch routes messages to, which {@link messageHelp}
|
|
4940
|
+
* lists; `classes` when not given.
|
|
4941
|
+
*/ constructor(classes, messages = {}, translator = ()=>undefined, controllers = classes){
|
|
4479
4942
|
this.classes = classes;
|
|
4480
4943
|
this.messages = messages;
|
|
4481
4944
|
this.translator = translator;
|
|
4945
|
+
this.controllers = controllers;
|
|
4482
4946
|
}
|
|
4483
4947
|
}
|
|
4484
4948
|
|
|
4485
|
-
/** The longest delay a timer keeps: 2^31 - 1 ms, about 24.8 days. Node fires a longer one at once. */ const MAX_TIMER_MS = 2147483647;
|
|
4486
|
-
/** A value as a refusal quotes it: a number as itself, `Infinity` and `NaN` included, a string in quotes. */ function quoted(value) {
|
|
4487
|
-
if (typeof value === 'string') return JSON.stringify(value);
|
|
4488
|
-
if (typeof value === 'number' || typeof value === 'boolean' || value === null) return String(value);
|
|
4489
|
-
return Array.isArray(value) ? 'an array' : typeof value;
|
|
4490
|
-
}
|
|
4491
|
-
/**
|
|
4492
|
-
* What is wrong with a timeout option, or `undefined` when it is a number of milliseconds a timer keeps: finite, above
|
|
4493
|
-
* 0, or 0 or more with `allowZero`, and at most `max`, {@link MAX_TIMER_MS} unless less. The caller names the option.
|
|
4494
|
-
*/ function timeoutProblem(value, { allowZero = false, max = MAX_TIMER_MS } = {}) {
|
|
4495
|
-
if (typeof value === 'number' && Number.isFinite(value) && (allowZero ? value >= 0 : value > 0) && value <= max) return undefined;
|
|
4496
|
-
return `must be a number of milliseconds ${allowZero ? '0 or more' : 'above 0'}, at most ${max} (got ${quoted(value)})`;
|
|
4497
|
-
}
|
|
4498
|
-
|
|
4499
4949
|
/** Where each shard's client keeps the function that runs a `ShardContext.call` in that shard. */ const SHARD_CALL_KEY = Symbol.for('meocord.shardCall');
|
|
4500
4950
|
const packArgs = (args)=>Object.assign({
|
|
4501
4951
|
length: args.length
|
|
@@ -4603,11 +5053,11 @@ async function withTimeout(promise, ms, what) {
|
|
|
4603
5053
|
*
|
|
4604
5054
|
* With process sharding that is one result per shard; with one process, a single result listing every
|
|
4605
5055
|
* shard. The arguments and the result pass as JSON in every mode, one process and tests included, so a
|
|
4606
|
-
* `Date` arrives as a string
|
|
4607
|
-
* `BigInt`, fails the call. A method whose params JSON would
|
|
4608
|
-
* the argument is refused, naming what to declare instead. An
|
|
4609
|
-
* that throws, lacks the service, or does not answer within
|
|
4610
|
-
* others.
|
|
5056
|
+
* `Date` arrives as a string, a `Map` as `{}` and a class instance as its own fields, without its getters, wherever
|
|
5057
|
+
* it runs, and a value JSON cannot carry, such as a `BigInt`, fails the call. A method whose params JSON would
|
|
5058
|
+
* change, such as one taking a `Date`, cannot be called: the argument is refused, naming what to declare instead. An
|
|
5059
|
+
* `undefined` argument arrives as `undefined`. A process that throws, lacks the service, or does not answer within
|
|
5060
|
+
* 10 seconds gives an error result instead of failing the others.
|
|
4611
5061
|
*
|
|
4612
5062
|
* @param service - The controller, the service, or a class a provider stands in for; each process resolves its own
|
|
4613
5063
|
* instance.
|
|
@@ -4713,6 +5163,18 @@ async function withTimeout(promise, ms, what) {
|
|
|
4713
5163
|
controller_decorator.ThemeCache,
|
|
4714
5164
|
controller_decorator.Translator
|
|
4715
5165
|
];
|
|
5166
|
+
/**
|
|
5167
|
+
* The tokens the bot binds before an app's providers, which `@MeoCord({ providers })` cannot provide: `Translator` too
|
|
5168
|
+
* when the app gives `i18n`.
|
|
5169
|
+
*/ const boundBeforeProviders = (i18n)=>[
|
|
5170
|
+
discord_js.Client,
|
|
5171
|
+
HandlerRegistry,
|
|
5172
|
+
ShardContext,
|
|
5173
|
+
controller_decorator.CooldownStore,
|
|
5174
|
+
...i18n ? [
|
|
5175
|
+
controller_decorator.Translator
|
|
5176
|
+
] : []
|
|
5177
|
+
];
|
|
4716
5178
|
const created = (cls)=>`create it with \`new ${cls.name}(…)\``;
|
|
4717
5179
|
/** What to do in place of injecting each of MeoCord's classes an app makes itself; the testing module adds its own. */ const advice = new Map([
|
|
4718
5180
|
[
|
|
@@ -4751,6 +5213,18 @@ const isFactoryProvider = (provider)=>'useFactory' in provider;
|
|
|
4751
5213
|
return `'${String(token)}'`;
|
|
4752
5214
|
}
|
|
4753
5215
|
const isToken = (value)=>typeof value === 'function' || typeof value === 'string' || typeof value === 'symbol';
|
|
5216
|
+
/**
|
|
5217
|
+
* Checks one of an app's own `@MeoCord({ providers })` as the bot does. A provider of a token MeoCord binds later, or
|
|
5218
|
+
* for each call, is warned about until 5.0 refuses it; one of a token `isOwn` names, which MeoCord binds before the
|
|
5219
|
+
* providers, is refused.
|
|
5220
|
+
*
|
|
5221
|
+
* @returns The refusal, for the caller to report, or `undefined`.
|
|
5222
|
+
*/ function appProviderRefusal(appName, token, isOwn, logger) {
|
|
5223
|
+
if (token === controller_decorator.ThemeCache || token === controller_decorator.ExecutionContext) {
|
|
5224
|
+
controller_decorator.warnOnce(logger, `${appName}: @MeoCord({ providers }) provides ${tokenName(token)}, which MeoCord binds itself; ` + 'the next major version (5.0) refuses it. Remove the provider.');
|
|
5225
|
+
}
|
|
5226
|
+
return isOwn(token) ? new Error(`${appName}: @MeoCord({ providers }) cannot provide ${tokenName(token)}, which MeoCord binds itself.`) : undefined;
|
|
5227
|
+
}
|
|
4754
5228
|
/**
|
|
4755
5229
|
* Checks each provider's shape and indexes them by token, reporting each that cannot be bound, such as one with no
|
|
4756
5230
|
* token, not exactly one of `useValue`, `useClass` and `useFactory`, one that injects `ExecutionContext`, or a token
|
|
@@ -4810,6 +5284,10 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
|
|
|
4810
5284
|
}
|
|
4811
5285
|
return map;
|
|
4812
5286
|
}
|
|
5287
|
+
/** For each container, how to forget the value each factory provider made, so a start that failed is made afresh. */ const madeValues = new WeakMap();
|
|
5288
|
+
/** Forgets every value `container`'s factory providers made, so the next resolve runs each factory again. */ function forgetFactoryValues(container) {
|
|
5289
|
+
for (const forget of madeValues.get(container) ?? [])forget();
|
|
5290
|
+
}
|
|
4813
5291
|
/**
|
|
4814
5292
|
* Binds one provider into `container` as a singleton. `bindClass` binds a class a provider depends
|
|
4815
5293
|
* on, as the app binds its own, so a provided class or a factory's `inject` can name any class.
|
|
@@ -4830,6 +5308,10 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
|
|
|
4830
5308
|
// Made once, as a singleton, but kept only once made: a factory that throws or rejects runs again on the next
|
|
4831
5309
|
// resolve, so a start() after it failed tries it again. Resolves while one is pending share it.
|
|
4832
5310
|
let kept;
|
|
5311
|
+
madeValues.set(container, [
|
|
5312
|
+
...madeValues.get(container) ?? [],
|
|
5313
|
+
()=>kept = undefined
|
|
5314
|
+
]);
|
|
4833
5315
|
container.bind(token).toDynamicValue((context)=>{
|
|
4834
5316
|
if (kept) return kept.value;
|
|
4835
5317
|
// Resolved in dependency order before anything needs it, so each injected value is already made
|
|
@@ -5001,11 +5483,13 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
|
|
|
5001
5483
|
* Runs every factory in `order`, awaiting those that return a promise, so each value is made before
|
|
5002
5484
|
* anything that injects it is resolved. Rejects with {@link providerFailure} for the first that fails. With `control`,
|
|
5003
5485
|
* it makes no further value once the app is stopping.
|
|
5486
|
+
*
|
|
5487
|
+
* @returns Whether every factory was made: `false` when the app began stopping first.
|
|
5004
5488
|
*/ async function resolveProviders(container, providers, order, control) {
|
|
5005
5489
|
for (const token of order){
|
|
5006
5490
|
const provider = providers.get(token);
|
|
5007
5491
|
if (!provider || !isFactoryProvider(provider)) continue;
|
|
5008
|
-
if (control?.stopped()) return;
|
|
5492
|
+
if (control?.stopped()) return false;
|
|
5009
5493
|
control?.pending(tokenName(token));
|
|
5010
5494
|
try {
|
|
5011
5495
|
await container.getAsync(token);
|
|
@@ -5015,12 +5499,14 @@ const isToken = (value)=>typeof value === 'function' || typeof value === 'string
|
|
|
5015
5499
|
control?.pending(undefined);
|
|
5016
5500
|
}
|
|
5017
5501
|
}
|
|
5502
|
+
return true;
|
|
5018
5503
|
}
|
|
5019
5504
|
|
|
5020
5505
|
exports.BUILT_IN_TYPES = BUILT_IN_TYPES;
|
|
5021
5506
|
exports.COOLDOWN_POLICY = COOLDOWN_POLICY;
|
|
5022
5507
|
exports.HandlerRegistry = HandlerRegistry;
|
|
5023
5508
|
exports.MAX_TIMER_MS = MAX_TIMER_MS;
|
|
5509
|
+
exports.MAX_TIMER_SECONDS = MAX_TIMER_SECONDS;
|
|
5024
5510
|
exports.On = On;
|
|
5025
5511
|
exports.Once = Once;
|
|
5026
5512
|
exports.SENT_AS = SENT_AS;
|
|
@@ -5030,7 +5516,11 @@ exports.THEMED_CLASSES = THEMED_CLASSES;
|
|
|
5030
5516
|
exports.adviseInPlaceOfInjecting = adviseInPlaceOfInjecting;
|
|
5031
5517
|
exports.appObservers = appObservers;
|
|
5032
5518
|
exports.appPresenterOf = appPresenterOf;
|
|
5519
|
+
exports.appProviderRefusal = appProviderRefusal;
|
|
5033
5520
|
exports.appStages = appStages;
|
|
5521
|
+
exports.appTheme = appTheme;
|
|
5522
|
+
exports.appliesTo = appliesTo;
|
|
5523
|
+
exports.applyGuards = applyGuards;
|
|
5034
5524
|
exports.assertObservers = assertObservers;
|
|
5035
5525
|
exports.assertProvided = assertProvided;
|
|
5036
5526
|
exports.assertProvidedServices = assertProvidedServices;
|
|
@@ -5042,37 +5532,41 @@ exports.bindGlobalStages = bindGlobalStages;
|
|
|
5042
5532
|
exports.bindObservers = bindObservers;
|
|
5043
5533
|
exports.bindProvider = bindProvider;
|
|
5044
5534
|
exports.bindsOwnToken = bindsOwnToken;
|
|
5535
|
+
exports.boundBeforeProviders = boundBeforeProviders;
|
|
5045
5536
|
exports.buildMessageRoutes = buildMessageRoutes;
|
|
5046
5537
|
exports.claimAmbientAppTheme = claimAmbientAppTheme;
|
|
5047
5538
|
exports.claimCooldownNotice = claimCooldownNotice;
|
|
5048
5539
|
exports.claimStoreDownNotice = claimStoreDownNotice;
|
|
5049
|
-
exports.classChain = classChain;
|
|
5050
5540
|
exports.classDecorators = classDecorators;
|
|
5051
5541
|
exports.classLevelGuards = classLevelGuards;
|
|
5052
|
-
exports.classUnits = classUnits;
|
|
5053
5542
|
exports.commandNameOf = commandNameOf;
|
|
5054
5543
|
exports.commandWordsOf = commandWordsOf;
|
|
5055
5544
|
exports.computeMessageHelp = computeMessageHelp;
|
|
5056
|
-
exports.consumeDispatchMark = consumeDispatchMark;
|
|
5057
5545
|
exports.cooldownPolicyFrom = cooldownPolicyFrom;
|
|
5058
5546
|
exports.declaringPrototype = declaringPrototype;
|
|
5059
5547
|
exports.deferMisuseError = deferMisuseError;
|
|
5060
5548
|
exports.defineStageTypes = defineStageTypes;
|
|
5061
5549
|
exports.fitsScope = fitsScope;
|
|
5550
|
+
exports.forgetFactoryValues = forgetFactoryValues;
|
|
5062
5551
|
exports.getEventHandlers = getEventHandlers;
|
|
5063
5552
|
exports.globalStagesOf = globalStagesOf;
|
|
5553
|
+
exports.guardOwnHandlersWithBaseGuards = guardOwnHandlersWithBaseGuards;
|
|
5064
5554
|
exports.handleUnroutedError = handleUnroutedError;
|
|
5065
5555
|
exports.handlerCooldowns = handlerCooldowns;
|
|
5066
5556
|
exports.handlerMethods = handlerMethods$1;
|
|
5067
5557
|
exports.handlerStageClasses = handlerStageClasses;
|
|
5068
5558
|
exports.handlerStages = handlerStages;
|
|
5559
|
+
exports.hasMessageText = hasMessageText;
|
|
5069
5560
|
exports.helpInvocation = helpInvocation;
|
|
5070
5561
|
exports.helpWords = helpWords;
|
|
5071
5562
|
exports.injectedTokens = injectedTokens;
|
|
5072
5563
|
exports.isAppClassToken = isAppClassToken;
|
|
5073
5564
|
exports.isClassProvider = isClassProvider;
|
|
5074
5565
|
exports.isGuardWithParams = isGuardWithParams;
|
|
5566
|
+
exports.isGuildType = isGuildType;
|
|
5075
5567
|
exports.isListable = isListable;
|
|
5568
|
+
exports.isValueProvider = isValueProvider;
|
|
5569
|
+
exports.listEventSources = listEventSources;
|
|
5076
5570
|
exports.makeInjectable = makeInjectable;
|
|
5077
5571
|
exports.markStartupChecked = markStartupChecked;
|
|
5078
5572
|
exports.matchHelpRequest = matchHelpRequest;
|
|
@@ -5082,17 +5576,21 @@ exports.matchMessageSubcommands = matchMessageSubcommands;
|
|
|
5082
5576
|
exports.meocordClasses = meocordClasses;
|
|
5083
5577
|
exports.messageCommandHooks = messageCommandHooks;
|
|
5084
5578
|
exports.messageParamsFor = messageParamsFor;
|
|
5579
|
+
exports.messageRouteWarnings = messageRouteWarnings;
|
|
5085
5580
|
exports.messageStarts = messageStarts;
|
|
5086
5581
|
exports.messageStartsFor = messageStartsFor;
|
|
5087
5582
|
exports.needsGuild = needsGuild;
|
|
5088
5583
|
exports.nonInteractionHandler = nonInteractionHandler;
|
|
5089
5584
|
exports.observeUnclaimed = observeUnclaimed;
|
|
5090
5585
|
exports.prepareAppPresenter = prepareAppPresenter;
|
|
5586
|
+
exports.prepareDirectCalls = prepareDirectCalls;
|
|
5587
|
+
exports.prepareEventHandler = prepareEventHandler;
|
|
5091
5588
|
exports.prepareHandlerStages = prepareHandlerStages;
|
|
5589
|
+
exports.prepareLateThemes = prepareLateThemes;
|
|
5092
5590
|
exports.prepareThemes = prepareThemes;
|
|
5093
5591
|
exports.providerMap = providerMap;
|
|
5094
5592
|
exports.reachableClasses = reachableClasses;
|
|
5095
|
-
exports.
|
|
5593
|
+
exports.recordGuards = recordGuards;
|
|
5096
5594
|
exports.registerClientTheme = registerClientTheme;
|
|
5097
5595
|
exports.registerCommands = registerCommands;
|
|
5098
5596
|
exports.registrationKey = registrationKey;
|
|
@@ -5100,7 +5598,6 @@ exports.releaseAmbientAppTheme = releaseAmbientAppTheme;
|
|
|
5100
5598
|
exports.renderMessageHelp = renderMessageHelp;
|
|
5101
5599
|
exports.resolutionOrder = resolutionOrder;
|
|
5102
5600
|
exports.resolveProviders = resolveProviders;
|
|
5103
|
-
exports.runDirectCall = runDirectCall;
|
|
5104
5601
|
exports.runGuards = runGuards;
|
|
5105
5602
|
exports.runHandler = runHandler;
|
|
5106
5603
|
exports.runInAppTheme = runInAppTheme;
|
|
@@ -5114,7 +5611,9 @@ exports.stageTypes = stageTypes;
|
|
|
5114
5611
|
exports.staticMessageStarts = staticMessageStarts;
|
|
5115
5612
|
exports.storeOperationsSettled = storeOperationsSettled;
|
|
5116
5613
|
exports.subcommandUsageError = subcommandUsageError;
|
|
5614
|
+
exports.takesGuildParam = takesGuildParam;
|
|
5117
5615
|
exports.timeoutProblem = timeoutProblem;
|
|
5616
|
+
exports.timerKeepsSeconds = timerKeepsSeconds;
|
|
5118
5617
|
exports.tokenDependencies = tokenDependencies;
|
|
5119
5618
|
exports.tokenName = tokenName;
|
|
5120
5619
|
exports.usesAppPrefix = usesAppPrefix;
|