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
package/dist/cjs/_shared/{controller.decorator-U2NBWcoU.cjs → controller.decorator-hp5xdJU4.cjs}
RENAMED
|
@@ -91,9 +91,22 @@ let problem;
|
|
|
91
91
|
* module, or imports a file that isn't there. Otherwise its code threw as it ran.
|
|
92
92
|
*/ function fixedInConfig(error) {
|
|
93
93
|
const code = error?.code;
|
|
94
|
-
if (error
|
|
94
|
+
if (unparsable(error) || code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;
|
|
95
95
|
return typeof code === 'string' && code.startsWith('ERR_REQUIRE_');
|
|
96
96
|
}
|
|
97
|
+
/** The name of Bun's error for a module it could not parse; several of them come together in an AggregateError. */ const BUN_PARSE_ERROR = 'BuildMessage';
|
|
98
|
+
/**
|
|
99
|
+
* Whether a module could not be parsed, so none of its code ran: on Node, a SyntaxError thrown from compiling it, whose
|
|
100
|
+
* first frame is Node's own; on Bun, its parse error. A SyntaxError the config's code throws as it runs, from
|
|
101
|
+
* `JSON.parse` or `new RegExp` on an environment value, is not one.
|
|
102
|
+
*/ function unparsable(error) {
|
|
103
|
+
const bunParseError = (each)=>each?.name === BUN_PARSE_ERROR;
|
|
104
|
+
if (bunParseError(error)) return true;
|
|
105
|
+
if (error instanceof AggregateError) return error.errors.length > 0 && error.errors.every(bunParseError);
|
|
106
|
+
if (!(error instanceof SyntaxError)) return false;
|
|
107
|
+
const firstFrame = error.stack?.split('\n').find((line)=>line.trimStart().startsWith('at '));
|
|
108
|
+
return firstFrame === undefined || /[( ]node:/.test(firstFrame);
|
|
109
|
+
}
|
|
97
110
|
/**
|
|
98
111
|
* The package a module-not-found error names, when it is a package rather than a file: `dotenv` for `dotenv/config`,
|
|
99
112
|
* `@scope/name` for `@scope/name/sub`, with the installed package that imports it, if one does. Undefined for any other
|
|
@@ -128,7 +141,7 @@ let problem;
|
|
|
128
141
|
try {
|
|
129
142
|
// Called through a variable so a bundler does not try to resolve the path while building the
|
|
130
143
|
// application; the file is only there once the build is done.
|
|
131
|
-
const load = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/controller.decorator-
|
|
144
|
+
const load = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('_shared/controller.decorator-hp5xdJU4.cjs', document.baseURI).href)));
|
|
132
145
|
const loaded = load(compiledPath);
|
|
133
146
|
return loaded.default ?? loaded;
|
|
134
147
|
} catch (error) {
|
|
@@ -142,6 +155,28 @@ let problem;
|
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
157
|
|
|
158
|
+
var version = "4.2.3";
|
|
159
|
+
var packageJson = {
|
|
160
|
+
version: version};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The name of a key the ES module and CommonJS builds of this meocord version share, and no other version has, for
|
|
164
|
+
* `Symbol.for`: a process that loads both builds of one install keeps one state under it, and two installed versions
|
|
165
|
+
* keep theirs apart. `Symbol.for(sharedName(...))` written out types as a `unique symbol`.
|
|
166
|
+
*/ const sharedName = (name)=>`meocord@${packageJson.version}:${name}`;
|
|
167
|
+
/** The key named {@link sharedName}. */ function sharedKey(name) {
|
|
168
|
+
return Symbol.for(sharedName(name));
|
|
169
|
+
}
|
|
170
|
+
/** The state both builds of this version keep on `globalThis` under `name`, made by whichever asks first. */ function shared(name, make) {
|
|
171
|
+
const key = sharedKey(name);
|
|
172
|
+
const store = globalThis;
|
|
173
|
+
if (!Object.hasOwn(store, key)) Object.defineProperty(store, key, {
|
|
174
|
+
value: make(),
|
|
175
|
+
configurable: true
|
|
176
|
+
});
|
|
177
|
+
return store[key];
|
|
178
|
+
}
|
|
179
|
+
|
|
145
180
|
/** The environment variable that sets the level for one run, over `logLevel` in meocord.config.ts. */ const LOG_LEVEL_ENV = 'MEOCORD_LOG_LEVEL';
|
|
146
181
|
/** Each level's rank: a line prints when its rank is at least the level's. `silent` outranks every line. */ const LOG_LEVEL_RANK = {
|
|
147
182
|
debug: 0,
|
|
@@ -151,32 +186,35 @@ let problem;
|
|
|
151
186
|
silent: 4
|
|
152
187
|
};
|
|
153
188
|
const isLogLevel = (value)=>typeof value === 'string' && Object.hasOwn(LOG_LEVEL_RANK, value);
|
|
154
|
-
|
|
155
|
-
|
|
189
|
+
// One per process for this meocord version, so both builds print at one level and warn about a bad variable once
|
|
190
|
+
const level = shared('logLevel', ()=>({
|
|
191
|
+
threshold: undefined,
|
|
192
|
+
rejectedEnv: undefined
|
|
193
|
+
}));
|
|
156
194
|
/**
|
|
157
195
|
* The rank a line needs to print. Resolved on first use and kept, so logging reads neither the
|
|
158
196
|
* environment nor the config again: `MEOCORD_LOG_LEVEL`, then `logLevel`, then `debug` in development
|
|
159
197
|
* and `log` otherwise. `logLevel` applies to the built bot only; the CLI and tests would otherwise read
|
|
160
198
|
* it from whatever `dist` a previous build left.
|
|
161
199
|
*/ function logThreshold() {
|
|
162
|
-
if (threshold === undefined) {
|
|
200
|
+
if (level.threshold === undefined) {
|
|
163
201
|
// The config first: loading it runs its dotenv call, which may set the variable
|
|
164
202
|
const configured = isBuiltApplication() ? loadMeoCordConfig()?.logLevel : undefined;
|
|
165
203
|
const raw = process.env[LOG_LEVEL_ENV];
|
|
166
204
|
// An environment variable is often written in capitals, DEBUG for debug
|
|
167
205
|
const fromEnv = raw?.toLowerCase();
|
|
168
|
-
rejectedEnv = raw && !isLogLevel(fromEnv) ? raw : undefined;
|
|
169
|
-
const
|
|
170
|
-
threshold = LOG_LEVEL_RANK[
|
|
206
|
+
level.rejectedEnv = raw && !isLogLevel(fromEnv) ? raw : undefined;
|
|
207
|
+
const chosen = isLogLevel(fromEnv) ? fromEnv : isLogLevel(configured) ? configured : process.env.NODE_ENV === 'development' ? 'debug' : 'log';
|
|
208
|
+
level.threshold = LOG_LEVEL_RANK[chosen];
|
|
171
209
|
}
|
|
172
|
-
return threshold;
|
|
210
|
+
return level.threshold;
|
|
173
211
|
}
|
|
174
212
|
/**
|
|
175
213
|
* An unknown `MEOCORD_LOG_LEVEL` the last resolution ignored, handed out once so the logger can warn
|
|
176
214
|
* about it; undefined afterwards.
|
|
177
215
|
*/ function takeRejectedLogLevel() {
|
|
178
|
-
const rejected = rejectedEnv;
|
|
179
|
-
rejectedEnv = undefined;
|
|
216
|
+
const rejected = level.rejectedEnv;
|
|
217
|
+
level.rejectedEnv = undefined;
|
|
180
218
|
return rejected;
|
|
181
219
|
}
|
|
182
220
|
|
|
@@ -204,7 +242,8 @@ const tagColours = TAG_COLOURS;
|
|
|
204
242
|
* The shortest value registered: a bot's credential is far longer, and a short value, such as a test's placeholder,
|
|
205
243
|
* would replace ordinary words in every line.
|
|
206
244
|
*/ const MIN_HIDDEN_LENGTH = 16;
|
|
207
|
-
|
|
245
|
+
// One per process for this meocord version, so a value hidden through one build stays out of the other's lines
|
|
246
|
+
const hiddenValues = shared('hiddenValues', ()=>new Set());
|
|
208
247
|
/**
|
|
209
248
|
* Keeps `value`, such as the bot's credential, out of every line Logger prints from now on, whatever object, string
|
|
210
249
|
* or error carries it: trimmed, and without the `Bot ` or `Bearer ` prefix discord.js strips.
|
|
@@ -345,7 +384,8 @@ const redact = (text)=>{
|
|
|
345
384
|
}
|
|
346
385
|
}
|
|
347
386
|
|
|
348
|
-
|
|
387
|
+
// One per process for this meocord version, so a warning both builds give prints once
|
|
388
|
+
const warned = shared('deprecationWarnings', ()=>new Set());
|
|
349
389
|
/** Warns once per run that `old` goes in 5.0, naming what to use instead. */ function warnDeprecated(logger, old, replacement) {
|
|
350
390
|
warnOnce(logger, `${old} is deprecated and will be removed in the next major version (5.0). Use ${replacement} instead.`);
|
|
351
391
|
}
|
|
@@ -403,28 +443,6 @@ const warned = new Set();
|
|
|
403
443
|
}
|
|
404
444
|
});
|
|
405
445
|
|
|
406
|
-
var version = "4.2.2";
|
|
407
|
-
var packageJson = {
|
|
408
|
-
version: version};
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* The name of a key the ES module and CommonJS builds of this meocord version share, and no other version has, for
|
|
412
|
-
* `Symbol.for`: a process that loads both builds of one install keeps one state under it, and two installed versions
|
|
413
|
-
* keep theirs apart. `Symbol.for(sharedName(...))` written out types as a `unique symbol`.
|
|
414
|
-
*/ const sharedName = (name)=>`meocord@${packageJson.version}:${name}`;
|
|
415
|
-
/** The key named {@link sharedName}. */ function sharedKey(name) {
|
|
416
|
-
return Symbol.for(sharedName(name));
|
|
417
|
-
}
|
|
418
|
-
/** The state both builds of this version keep on `globalThis` under `name`, made by whichever asks first. */ function shared(name, make) {
|
|
419
|
-
const key = sharedKey(name);
|
|
420
|
-
const store = globalThis;
|
|
421
|
-
if (!Object.hasOwn(store, key)) Object.defineProperty(store, key, {
|
|
422
|
-
value: make(),
|
|
423
|
-
configurable: true
|
|
424
|
-
});
|
|
425
|
-
return store[key];
|
|
426
|
-
}
|
|
427
|
-
|
|
428
446
|
// One per process for this meocord version, so a call's theme reads the same from its ES module and CommonJS builds
|
|
429
447
|
const scope = shared('themeScope', ()=>new node_async_hooks.AsyncLocalStorage());
|
|
430
448
|
const isPlainObject$1 = (value)=>{
|
|
@@ -487,50 +505,50 @@ const merges = new WeakMap();
|
|
|
487
505
|
if (!merged) byLayer.set(layer, merged = deepFreeze(mergeInto(theme, layer)));
|
|
488
506
|
return merged;
|
|
489
507
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
508
|
+
/**
|
|
509
|
+
* The theme state outside a call, one for both builds of this version so either reads what the other set: the defaults
|
|
510
|
+
* with what the deprecated `Theme` statics set beneath every theme, the app whose theme is read outside a call, and how
|
|
511
|
+
* an answer outside a call finds its theme.
|
|
512
|
+
*/ const state = shared('themeState', ()=>({
|
|
513
|
+
defaults: DEFAULT_THEME,
|
|
514
|
+
legacyVersion: 0,
|
|
515
|
+
ambient: undefined,
|
|
516
|
+
ambientVersion: 0,
|
|
517
|
+
themeOutsideCalls: undefined
|
|
518
|
+
}));
|
|
494
519
|
/** MeoCord's defaults, with what the deprecated `Theme` statics set over them. */ function defaultTheme() {
|
|
495
|
-
return defaults;
|
|
520
|
+
return state.defaults;
|
|
496
521
|
}
|
|
497
522
|
/** The number of times the legacy layer has changed, so a theme built on an older one is built again. */ function themeLayersVersion() {
|
|
498
|
-
return legacyVersion;
|
|
523
|
+
return state.legacyVersion;
|
|
499
524
|
}
|
|
500
525
|
/** Sets the layer the deprecated `Theme` statics write, merged over MeoCord's defaults. */ function setLegacyThemeLayer(layer) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
legacyVersion++;
|
|
526
|
+
state.defaults = mergeTheme(DEFAULT_THEME, layer === undefined ? undefined : copyLayer(layer));
|
|
527
|
+
state.legacyVersion++;
|
|
504
528
|
}
|
|
505
|
-
// The theme outside any call: the one of the app or testing module that claimed it, until it gives it up
|
|
506
|
-
let ambient;
|
|
507
|
-
let ambientVersion = 0;
|
|
508
529
|
/** The number of times the app read outside a call has changed, so each app decides again whether its calls need a scope. */ function ambientThemeVersion() {
|
|
509
|
-
return ambientVersion;
|
|
510
|
-
}
|
|
511
|
-
/** Whether some app's theme is the one read outside a call. */ function hasAmbientTheme() {
|
|
512
|
-
return ambient !== undefined;
|
|
530
|
+
return state.ambientVersion;
|
|
513
531
|
}
|
|
514
532
|
/**
|
|
515
533
|
* Makes an app's theme the one read outside a call, when no other app has: a bot runs one app, and code it runs
|
|
516
534
|
* outside a handler, such as a scheduled job, then reads that app's theme. A second app in the same process keeps
|
|
517
535
|
* its theme to its own calls.
|
|
518
536
|
*/ function claimAmbientTheme(owner, theme) {
|
|
519
|
-
if (ambient && ambient.owner !== owner) return false;
|
|
520
|
-
if (!ambient) ambientVersion++;
|
|
521
|
-
ambient = {
|
|
537
|
+
if (state.ambient && state.ambient.owner !== owner) return false;
|
|
538
|
+
if (!state.ambient) state.ambientVersion++;
|
|
539
|
+
state.ambient = {
|
|
522
540
|
owner,
|
|
523
541
|
theme
|
|
524
542
|
};
|
|
525
543
|
return true;
|
|
526
544
|
}
|
|
527
545
|
/** Gives up the theme read outside a call, when `owner` has it: its start failed, or it has shut down. */ function releaseAmbientTheme(owner) {
|
|
528
|
-
if (ambient?.owner !== owner) return;
|
|
529
|
-
ambient = undefined;
|
|
530
|
-
ambientVersion++;
|
|
546
|
+
if (state.ambient?.owner !== owner) return;
|
|
547
|
+
state.ambient = undefined;
|
|
548
|
+
state.ambientVersion++;
|
|
531
549
|
}
|
|
532
550
|
/** Whether `owner` is the app whose theme is read outside a call. */ function ownsAmbientTheme(owner) {
|
|
533
|
-
return ambient?.owner === owner;
|
|
551
|
+
return state.ambient?.owner === owner;
|
|
534
552
|
}
|
|
535
553
|
/** Runs `fn` with `theme` as the theme of the call, for everything it runs, awaits or starts. */ function runWithTheme(theme, fn) {
|
|
536
554
|
return scope.run({
|
|
@@ -546,10 +564,8 @@ let ambientVersion = 0;
|
|
|
546
564
|
/** Runs `fn` in `themeScope`, whose theme may be replaced while it runs, as a per-server theme is found. */ function runInThemeScope(themeScope, fn) {
|
|
547
565
|
return scope.run(themeScope, fn);
|
|
548
566
|
}
|
|
549
|
-
// Set by the runtime: the theme an app gives an interaction answered outside any call, as in a collector
|
|
550
|
-
let themeOutsideCalls;
|
|
551
567
|
/** Sets how an answer outside any call finds its theme: from the interaction's app, server and user. */ function setThemeOutsideCalls(resolve) {
|
|
552
|
-
themeOutsideCalls = resolve;
|
|
568
|
+
state.themeOutsideCalls = resolve;
|
|
553
569
|
}
|
|
554
570
|
/**
|
|
555
571
|
* The theme an answer to `interaction` uses: the running call's, or, outside any call, as in a collector's callback,
|
|
@@ -557,7 +573,7 @@ let themeOutsideCalls;
|
|
|
557
573
|
*/ function themeForInteraction(interaction) {
|
|
558
574
|
const call = scope.getStore();
|
|
559
575
|
if (call) return call.theme;
|
|
560
|
-
return themeOutsideCalls?.(interaction) ?? useTheme();
|
|
576
|
+
return state.themeOutsideCalls?.(interaction) ?? useTheme();
|
|
561
577
|
}
|
|
562
578
|
/**
|
|
563
579
|
* The theme of the running call, with every role present.
|
|
@@ -586,7 +602,7 @@ let themeOutsideCalls;
|
|
|
586
602
|
*
|
|
587
603
|
* @group Responses
|
|
588
604
|
*/ function useTheme() {
|
|
589
|
-
return scope.getStore()?.theme ?? ambient?.theme() ?? defaults;
|
|
605
|
+
return scope.getStore()?.theme ?? state.ambient?.theme() ?? state.defaults;
|
|
590
606
|
}
|
|
591
607
|
/**
|
|
592
608
|
* Makes a function run in the theme of the call that binds it, wherever it is called from later.
|
|
@@ -635,7 +651,10 @@ const EXPLAINED = Symbol.for('meocord.explained');
|
|
|
635
651
|
*
|
|
636
652
|
* Use it where a bot logs a startup failure, so an error MeoCord explained is not logged twice: an app
|
|
637
653
|
* `MeoCordFactory.create()` refuses, such as two handlers for one command, or Discord refusing the bot token or a
|
|
638
|
-
* privileged intent at login.
|
|
654
|
+
* privileged intent at login. A built bot has `process.exitCode` set for such an error already, and a start that a stop
|
|
655
|
+
* ended, which is explained too, leaves it as it was, so the process exits 0. Outside a build, as a script run by hand,
|
|
656
|
+
* `MeoCordFactory.create()` sets no code for an app it refuses, so a catch that returns early for an explained error,
|
|
657
|
+
* as below, sets `process.exitCode` itself there. The error is otherwise unchanged.
|
|
639
658
|
*
|
|
640
659
|
* @param error - An error `MeoCordFactory.create()` threw or `app.start()` rejected with.
|
|
641
660
|
* @returns `true` when MeoCord explained the error.
|
|
@@ -651,9 +670,10 @@ const EXPLAINED = Symbol.for('meocord.explained');
|
|
|
651
670
|
* await app.start()
|
|
652
671
|
* }
|
|
653
672
|
*
|
|
654
|
-
* // An error MeoCord explained, such as an app it refuses or a token Discord refused, is already logged
|
|
673
|
+
* // An error MeoCord explained, such as an app it refuses or a token Discord refused, is already logged, with the exit code set
|
|
655
674
|
* bootstrap().catch(error => {
|
|
656
|
-
* if (
|
|
675
|
+
* if (isExplainedError(error)) return
|
|
676
|
+
* logger.error('Error during startup:', error)
|
|
657
677
|
* process.exitCode = 1
|
|
658
678
|
* })
|
|
659
679
|
* ```
|
|
@@ -682,10 +702,16 @@ const EXPLAINED = Symbol.for('meocord.explained');
|
|
|
682
702
|
}
|
|
683
703
|
|
|
684
704
|
const REFUSAL = Symbol.for('meocord.refusal');
|
|
685
|
-
/**
|
|
686
|
-
|
|
705
|
+
/**
|
|
706
|
+
* One per process for this meocord version, so the two builds report a refusal once: whether this process reports
|
|
707
|
+
* refusals yet (see `reportRefusals`), and the refusals someone else reports, and ends the process for, which it leaves
|
|
708
|
+
* to them.
|
|
709
|
+
*/ const refusals = shared('refusalReporting', ()=>({
|
|
710
|
+
reporting: false,
|
|
711
|
+
handedOff: new WeakSet()
|
|
712
|
+
}));
|
|
687
713
|
/** Leaves a refusal to whoever reports it instead, such as a shard's manager: going uncaught, it is neither logged nor exits. */ function handOffRefusal(error) {
|
|
688
|
-
handedOff.add(error);
|
|
714
|
+
refusals.handedOff.add(error);
|
|
689
715
|
}
|
|
690
716
|
/**
|
|
691
717
|
* Marks an error MeoCord raises for code it refuses as the application loads, such as a decorator given what it
|
|
@@ -696,18 +722,29 @@ const REFUSAL = Symbol.for('meocord.refusal');
|
|
|
696
722
|
enumerable: false
|
|
697
723
|
});
|
|
698
724
|
// Before the error is thrown, so a built application reports it however the throw ends
|
|
699
|
-
if (!reporting && isBuiltApplication()) reportRefusals();
|
|
725
|
+
if (!refusals.reporting && isBuiltApplication()) reportRefusals();
|
|
700
726
|
return error;
|
|
701
727
|
}
|
|
702
|
-
/**
|
|
728
|
+
/** The name of the class a member decorator is applied in: a static member's target is the class itself. */ function ownerName(target) {
|
|
729
|
+
return (typeof target === 'function' ? target : target.constructor).name;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* What a decorator is applied to, as a refusal names it: `Class.method` on a method, a static one included, `Class` on
|
|
733
|
+
* a class.
|
|
734
|
+
*/ function decoratedName(target, propertyKey) {
|
|
703
735
|
if (propertyKey === undefined) return target.name || 'a class';
|
|
704
|
-
return `${target
|
|
736
|
+
return `${ownerName(target)}.${String(propertyKey)}`;
|
|
705
737
|
}
|
|
706
738
|
/**
|
|
707
739
|
* Refuses a decorator that goes only on a method, or on a member, where it is applied to a class, directly or through
|
|
708
|
-
* `applyDecorators`, rather than store what nothing reads or fail on the method it lacks.
|
|
740
|
+
* `applyDecorators`, rather than store what nothing reads or fail on the method it lacks. On a static member, where
|
|
741
|
+
* nothing reads it either, it warns; the next major version (5.0) refuses it there too.
|
|
709
742
|
*/ function refuseOnClass(decorator, target, propertyKey, place = 'a method') {
|
|
710
743
|
if (propertyKey === undefined) throw refuse(new Error(`${decoratedName(target)}: ${decorator} goes on ${place}, not on a class.`));
|
|
744
|
+
if (typeof target === 'function') {
|
|
745
|
+
const where = place === 'a method' ? 'an instance method, not a static one; it never runs' : 'an instance member, not a static one, where nothing reads it';
|
|
746
|
+
warnOnce(new Logger(decorator.slice(1)), `${decoratedName(target, propertyKey)}: ${decorator} goes on ${where}. The next major version (5.0) refuses it.`);
|
|
747
|
+
}
|
|
711
748
|
}
|
|
712
749
|
/** Refuses a decorator that goes only on a class where it is applied to a method, directly or through `applyDecorators`. */ function refuseOnMethod(decorator, target, propertyKey) {
|
|
713
750
|
if (propertyKey !== undefined) throw refuse(new Error(`${decoratedName(target, propertyKey)}: ${decorator} goes on a class, not on a method.`));
|
|
@@ -890,9 +927,9 @@ function keepOrThrow(error, target, propertyKey) {
|
|
|
890
927
|
* report. Installed by the first refusal in a built application. Bun has no monitor event for a rejection, so there a
|
|
891
928
|
* listener reports a refused rejection and rejects any other again without itself, unless the app listens itself.
|
|
892
929
|
*/ function reportRefusals(log = (text)=>new Logger('MeoCord').error(text), exit = (code)=>process.exit(code), { bun = process.versions.bun !== undefined, reject = (reason)=>void Promise.reject(reason) } = {}) {
|
|
893
|
-
reporting = true;
|
|
930
|
+
refusals.reporting = true;
|
|
894
931
|
const report = (error)=>{
|
|
895
|
-
if (handedOff.has(error)) return;
|
|
932
|
+
if (refusals.handedOff.has(error)) return;
|
|
896
933
|
if (!isExplainedError(error)) log(describeRefusal(error, process.cwd()));
|
|
897
934
|
exit(1);
|
|
898
935
|
};
|
|
@@ -911,8 +948,8 @@ function keepOrThrow(error, target, propertyKey) {
|
|
|
911
948
|
process.on('unhandledRejection', onRejection);
|
|
912
949
|
}
|
|
913
950
|
|
|
914
|
-
/** Characters a log line shows escaped: C0 and C1 controls, DEL, line and paragraph separators, and bidi controls. */ const UNSAFE_IN_LOG = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\
|
|
915
|
-
/** The same, with the quote and the backslash, for text shown between quotes. */ const UNSAFE_IN_QUOTES = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\
|
|
951
|
+
/** Characters a log line shows escaped: C0 and C1 controls, DEL, line and paragraph separators, and bidi controls. */ const UNSAFE_IN_LOG = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\p{Bidi_Control}]/gu;
|
|
952
|
+
/** The same, with the quote and the backslash, for text shown between quotes. */ const UNSAFE_IN_QUOTES = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\p{Bidi_Control}"\\]/gu;
|
|
916
953
|
const SHORT_ESCAPES = {
|
|
917
954
|
'\n': '\\n',
|
|
918
955
|
'\r': '\\r',
|
|
@@ -936,8 +973,12 @@ const escapeChar = (char)=>SHORT_ESCAPES[char] ?? `\\u${char.charCodeAt(0).toStr
|
|
|
936
973
|
if (characters.length <= LOGGED_CHARACTERS) return quoted(text);
|
|
937
974
|
return `${quoted(`${characters.slice(0, LOGGED_CHARACTERS).join('')}…`)} (${characters.length} characters)`;
|
|
938
975
|
}
|
|
939
|
-
/**
|
|
940
|
-
|
|
976
|
+
/**
|
|
977
|
+
* Words a user wrote, as a reply quotes them: on one line, with their markdown, masked links included, shown as typed.
|
|
978
|
+
* Each run of whitespace reads as one space, and the spaces around the words are kept, since a word in quotes can
|
|
979
|
+
* carry them and be refused for them.
|
|
980
|
+
*/ function userWords(text) {
|
|
981
|
+
return discord_js.escapeMarkdown(text.replace(/\s+/g, ' '), {
|
|
941
982
|
maskedLink: true
|
|
942
983
|
});
|
|
943
984
|
}
|
|
@@ -992,7 +1033,7 @@ const describeGroup = (value, quote = asWritten)=>Array.isArray(value) ? 'an arr
|
|
|
992
1033
|
const prototype = Object.getPrototypeOf(value);
|
|
993
1034
|
return prototype === Object.prototype || prototype === null;
|
|
994
1035
|
};
|
|
995
|
-
const PLAIN = "give a plain object, such as {
|
|
1036
|
+
const PLAIN = "give a plain object: a database row's plain form, such as Mongoose's toObject() or Sequelize's get({ plain: true }), or an object literal of the values you set, since anything else would replace the whole";
|
|
996
1037
|
/** What a non-plain object is: by its class, or as one without a plain prototype when it has none named. */ const describeInstance = (value)=>{
|
|
997
1038
|
const name = value.constructor?.name;
|
|
998
1039
|
return typeof name === 'string' && name ? `a ${name}` : 'an object without a plain prototype';
|
|
@@ -1088,6 +1129,107 @@ const PLAIN = "give a plain object, such as { ...value }, or a row's toObject(),
|
|
|
1088
1129
|
throw refuse(new Error(`${where}: the theme has ${problems.length} problem${problems.length === 1 ? '' : 's'}:\n${problems.map((problem)=>` ${problem}`).join('\n')}`));
|
|
1089
1130
|
}
|
|
1090
1131
|
|
|
1132
|
+
/** The theme role each static stands for; `errorColor` is a fault in the bot, `danger`. */ const ROLES = {
|
|
1133
|
+
primaryColor: 'primary',
|
|
1134
|
+
successColor: 'success',
|
|
1135
|
+
infoColor: 'info',
|
|
1136
|
+
errorColor: 'danger',
|
|
1137
|
+
warningColor: 'warning'
|
|
1138
|
+
};
|
|
1139
|
+
const logger$6 = new Logger('Theme');
|
|
1140
|
+
// One per process for this meocord version, so a static set through one build keeps those set through the other
|
|
1141
|
+
const assigned = shared('legacyThemeColors', ()=>({}));
|
|
1142
|
+
/** Reads a role of the theme where it is read, warning once a run for each static read. */ function read(name) {
|
|
1143
|
+
warnDeprecated(logger$6, `Theme.${name}`, `useTheme().colors.${ROLES[name]}`);
|
|
1144
|
+
return useTheme().colors[ROLES[name]];
|
|
1145
|
+
}
|
|
1146
|
+
/** Sets a role beneath every theme an app sets; a value that is no colour is reported instead. */ function assign(name, value) {
|
|
1147
|
+
const role = ROLES[name];
|
|
1148
|
+
warnDeprecated(logger$6, `Assigning Theme.${name}`, `colors.${role} in @MeoCord({ theme })`);
|
|
1149
|
+
const [problem] = themeProblems({
|
|
1150
|
+
colors: {
|
|
1151
|
+
[role]: value
|
|
1152
|
+
}
|
|
1153
|
+
}, `Theme.${name}`);
|
|
1154
|
+
if (problem) {
|
|
1155
|
+
logger$6.warn(`${problem}. It was left unset.`);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
assigned[role] = value;
|
|
1159
|
+
setLegacyThemeLayer({
|
|
1160
|
+
colors: {
|
|
1161
|
+
...assigned
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Five of the theme's colours as static properties: primary, success, info, danger and warning.
|
|
1167
|
+
*
|
|
1168
|
+
* Each one reads the matching role of the theme where it is read, `useTheme().colors`, so code written against it
|
|
1169
|
+
* follows `@MeoCord({ theme })` and `@UseTheme` with no change. Assigning one recolours MeoCord's views, as a
|
|
1170
|
+
* role beneath every theme an app sets. Reading or assigning each one logs a warning once.
|
|
1171
|
+
*
|
|
1172
|
+
* @deprecated Since 4.1, and removed in the next major version (5.0). Use `useTheme().colors` instead. Themes
|
|
1173
|
+
* follow the app, a controller and a server; set the colours in `@MeoCord({ theme })`: `primaryColor` is
|
|
1174
|
+
* `colors.primary`, `successColor` `colors.success`, `infoColor` `colors.info`, `errorColor` `colors.danger` and
|
|
1175
|
+
* `warningColor` `colors.warning`.
|
|
1176
|
+
*
|
|
1177
|
+
* @example
|
|
1178
|
+
* ```ts
|
|
1179
|
+
* // Both colour the embed with the call's primary colour; write the second in new code
|
|
1180
|
+
* new EmbedBuilder().setColor(Theme.primaryColor)
|
|
1181
|
+
* new EmbedBuilder().setColor(useTheme().colors.primary)
|
|
1182
|
+
* ```
|
|
1183
|
+
*
|
|
1184
|
+
* @group Responses
|
|
1185
|
+
*/ class Theme {
|
|
1186
|
+
/**
|
|
1187
|
+
* @deprecated Since 4.1, and removed in the next major version (5.0). Use `useTheme().colors.primary` instead. It
|
|
1188
|
+
* reads the theme where it is read; set `colors.primary` in `@MeoCord({ theme })`.
|
|
1189
|
+
*/ static get primaryColor() {
|
|
1190
|
+
return read('primaryColor');
|
|
1191
|
+
}
|
|
1192
|
+
static set primaryColor(value) {
|
|
1193
|
+
assign('primaryColor', value);
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* @deprecated Since 4.1, and removed in the next major version (5.0). Use `useTheme().colors.success` instead. It
|
|
1197
|
+
* reads the theme where it is read; set `colors.success` in `@MeoCord({ theme })`.
|
|
1198
|
+
*/ static get successColor() {
|
|
1199
|
+
return read('successColor');
|
|
1200
|
+
}
|
|
1201
|
+
static set successColor(value) {
|
|
1202
|
+
assign('successColor', value);
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* @deprecated Since 4.1, and removed in the next major version (5.0). Use `useTheme().colors.info` instead. It
|
|
1206
|
+
* reads the theme where it is read; set `colors.info` in `@MeoCord({ theme })`.
|
|
1207
|
+
*/ static get infoColor() {
|
|
1208
|
+
return read('infoColor');
|
|
1209
|
+
}
|
|
1210
|
+
static set infoColor(value) {
|
|
1211
|
+
assign('infoColor', value);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* @deprecated Since 4.1, and removed in the next major version (5.0). Use `useTheme().colors.danger` instead. It
|
|
1215
|
+
* reads the theme where it is read; set `colors.danger` in `@MeoCord({ theme })`.
|
|
1216
|
+
*/ static get errorColor() {
|
|
1217
|
+
return read('errorColor');
|
|
1218
|
+
}
|
|
1219
|
+
static set errorColor(value) {
|
|
1220
|
+
assign('errorColor', value);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* @deprecated Since 4.1, and removed in the next major version (5.0). Use `useTheme().colors.warning` instead. It
|
|
1224
|
+
* reads the theme where it is read; set `colors.warning` in `@MeoCord({ theme })`.
|
|
1225
|
+
*/ static get warningColor() {
|
|
1226
|
+
return read('warningColor');
|
|
1227
|
+
}
|
|
1228
|
+
static set warningColor(value) {
|
|
1229
|
+
assign('warningColor', value);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1091
1233
|
/**
|
|
1092
1234
|
* Composes several class or method decorators into one.
|
|
1093
1235
|
*
|
|
@@ -1258,18 +1400,27 @@ const PLURAL_CATEGORIES = new Set([
|
|
|
1258
1400
|
'other'
|
|
1259
1401
|
]);
|
|
1260
1402
|
/** A plural's shape: a non-empty object whose every key is a plural category, as the types read one; any other is a group. */ const isPluralShape = (value)=>typeof value === 'object' && value !== null && Object.keys(value).length > 0 && Object.keys(value).every((key)=>PLURAL_CATEGORIES.has(key));
|
|
1261
|
-
/**
|
|
1262
|
-
* A message by its dotted key, or undefined when the catalog lacks it or the key names a group. A plural without an
|
|
1263
|
-
* `other` form reads as missing, so a translation that left it out falls back to the next catalog.
|
|
1264
|
-
*/ function lookup(catalog, key) {
|
|
1403
|
+
/** The value at a dotted key's path, or undefined where the path ends early. */ function valueAt(catalog, key) {
|
|
1265
1404
|
let current = catalog;
|
|
1266
1405
|
for (const part of key.split('.')){
|
|
1267
1406
|
if (typeof current !== 'object' || current === null) return undefined;
|
|
1268
1407
|
current = current[part];
|
|
1269
1408
|
}
|
|
1409
|
+
return current;
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* A message by its dotted key, or undefined when the catalog lacks it or the key names a group. `reference`, the
|
|
1413
|
+
* default catalog for a translation, decides where it has a group that an object is that group, so a translation that
|
|
1414
|
+
* keeps only a group's `other` message isn't read as a plural. Where it has a message, or lacks the key, the object's
|
|
1415
|
+
* own keys decide, so a translation may give a plural for a message its language needs one for. A plural without an
|
|
1416
|
+
* `other` form reads as missing, so a translation that left it out falls back to the next catalog.
|
|
1417
|
+
*/ function lookup(catalog, key, reference = catalog) {
|
|
1418
|
+
const current = valueAt(catalog, key);
|
|
1270
1419
|
if (typeof current === 'string') return current;
|
|
1271
|
-
if (isPluralShape(current)
|
|
1272
|
-
|
|
1420
|
+
if (!isPluralShape(current) || typeof current.other !== 'string') return undefined;
|
|
1421
|
+
const shape = reference === catalog ? current : valueAt(reference, key);
|
|
1422
|
+
const group = typeof shape === 'object' && shape !== null && !isPluralShape(shape);
|
|
1423
|
+
return group ? undefined : current;
|
|
1273
1424
|
}
|
|
1274
1425
|
const plurals = new Map();
|
|
1275
1426
|
/** The form of a found message to use: a plain message itself, or a plural's form for `count` in its locale's rules. */ function pluralForm({ message, locale }, count) {
|
|
@@ -1333,7 +1484,7 @@ class CatalogTranslator extends Translator {
|
|
|
1333
1484
|
}
|
|
1334
1485
|
translate(requested, key, params = {}) {
|
|
1335
1486
|
for (const locale of this[LOCALE_CHAIN](requested)){
|
|
1336
|
-
const message = lookup(this.catalogs[locale], key);
|
|
1487
|
+
const message = lookup(this.catalogs[locale], key, this.catalogs[this.defaultLocale]);
|
|
1337
1488
|
if (message === undefined) continue;
|
|
1338
1489
|
// An empty translation, as tools export an untranslated string, counts as missing: Discord refuses an empty reply
|
|
1339
1490
|
const text = pluralForm({
|
|
@@ -1369,7 +1520,7 @@ class CatalogTranslator extends Translator {
|
|
|
1369
1520
|
const localized = {};
|
|
1370
1521
|
for (const locale of this.locales){
|
|
1371
1522
|
if (locale === this.defaultLocale) continue;
|
|
1372
|
-
const message = lookup(this.catalogs[locale], key);
|
|
1523
|
+
const message = lookup(this.catalogs[locale], key, this.catalogs[this.defaultLocale]);
|
|
1373
1524
|
// A translation with a {param} is left out, as Discord would show it as written, and so is an empty one
|
|
1374
1525
|
if (typeof message === 'string' && message !== '' && placeholderNames(message).length === 0) {
|
|
1375
1526
|
localized[locale] = fillPlaceholders(message, (_name, whole)=>whole);
|
|
@@ -1543,8 +1694,9 @@ const ENGLISH = 'en-US';
|
|
|
1543
1694
|
/**
|
|
1544
1695
|
* The message a text is in for `locale`, and whether it is the app's: the app's catalogs down the locale's chain, with
|
|
1545
1696
|
* MeoCord's English as the English catalog. An English request reads it after the app's English catalogs and before a
|
|
1546
|
-
* default in another language; any other request reads it last.
|
|
1547
|
-
|
|
1697
|
+
* default in another language; any other request reads it last. A message whose form for `count` is empty, as tools
|
|
1698
|
+
* export an untranslated string, counts as missing, as it does for the app's own texts.
|
|
1699
|
+
*/ function findText(translator, locale, key, count) {
|
|
1548
1700
|
const english = ()=>{
|
|
1549
1701
|
const message = lookup(MEOCORD_MESSAGES, key);
|
|
1550
1702
|
return message === undefined ? undefined : {
|
|
@@ -1559,7 +1711,10 @@ const ENGLISH = 'en-US';
|
|
|
1559
1711
|
const beforeDefault = englishFirst && served === translator.defaultLocale ? english() : undefined;
|
|
1560
1712
|
if (beforeDefault) return beforeDefault;
|
|
1561
1713
|
const message = lookup(translator[CATALOGS][served], key);
|
|
1562
|
-
if (message !== undefined
|
|
1714
|
+
if (message !== undefined && pluralForm({
|
|
1715
|
+
message,
|
|
1716
|
+
locale: served
|
|
1717
|
+
}, count) !== '') return {
|
|
1563
1718
|
message,
|
|
1564
1719
|
locale: served,
|
|
1565
1720
|
own: true
|
|
@@ -1580,9 +1735,9 @@ const lists = new Map();
|
|
|
1580
1735
|
* that has it; any other list is joined with its `joiner`, a comma unless given, as Intl's `unit` lists join with
|
|
1581
1736
|
* nothing in zh-CN and with a space in ja.
|
|
1582
1737
|
*/ function renderText(translator, locale, text) {
|
|
1583
|
-
const found = findText(translator, locale, text.key);
|
|
1584
|
-
if (!found) return text.fallback ?? text.key;
|
|
1585
1738
|
const params = text.params ?? {};
|
|
1739
|
+
const found = findText(translator, locale, text.key, params.count);
|
|
1740
|
+
if (!found) return text.fallback ?? text.key;
|
|
1586
1741
|
return fillPlaceholders(pluralForm(found, params.count), (name, whole)=>{
|
|
1587
1742
|
if (!Object.hasOwn(params, name)) return whole;
|
|
1588
1743
|
const value = params[name];
|
|
@@ -1608,7 +1763,8 @@ const translators = shared('clientTranslators', ()=>new WeakMap());
|
|
|
1608
1763
|
/** A text for an interaction, in its user's language through its app's translator. */ function textFor(interaction, text) {
|
|
1609
1764
|
return renderText(translatorOfClient(interaction.client), interactionLocale(interaction), text);
|
|
1610
1765
|
}
|
|
1611
|
-
|
|
1766
|
+
// One per process for this meocord version, so an issue made by one build is translated by the other
|
|
1767
|
+
const issueTexts = shared('issueTexts', ()=>new WeakMap());
|
|
1612
1768
|
/** An issue of a usage error, whose message is the text in English, keeping the text to translate it later. */ function usageIssue(text, param) {
|
|
1613
1769
|
const issue = {
|
|
1614
1770
|
...param !== undefined && {
|
|
@@ -2017,15 +2173,26 @@ brand(CooldownStoreError, 'CooldownStoreError');
|
|
|
2017
2173
|
}
|
|
2018
2174
|
/** A class's name after its article, such as `an AutocompleteInteraction`; a `U` name reads as "you", so takes `a`. */ const withArticle = (name)=>`${/^[AEIO]/i.test(name) ? 'an' : 'a'} ${name}`;
|
|
2019
2175
|
/**
|
|
2020
|
-
* Sets `key` on `target` as an own, enumerable data property: unlike `target[key] = value`, a key
|
|
2021
|
-
* is a key like any other rather than the object's prototype
|
|
2176
|
+
* Sets `key` on a plain object `target` as an own, enumerable data property: unlike `target[key] = value`, a key
|
|
2177
|
+
* named `__proto__` is a key like any other rather than the object's prototype, and a key such as `toString` is set
|
|
2178
|
+
* even where `Object.prototype` is frozen.
|
|
2022
2179
|
*/ function setOwn(target, key, value) {
|
|
2023
|
-
Object.
|
|
2180
|
+
// An assignment would call __proto__'s setter, and throw for a member of a frozen Object.prototype. Any other key
|
|
2181
|
+
// assigned is an own data property already; defining each would cost V8 its fast object shape
|
|
2182
|
+
if (key in Object.prototype) Object.defineProperty(target, key, {
|
|
2024
2183
|
value,
|
|
2025
2184
|
enumerable: true,
|
|
2026
2185
|
writable: true,
|
|
2027
2186
|
configurable: true
|
|
2028
2187
|
});
|
|
2188
|
+
else target[key] = value;
|
|
2189
|
+
}
|
|
2190
|
+
/** `target`'s own value for `key`, never one it inherits, such as `Object.prototype.toString` for `toString`. */ const ownValue = (target, key)=>Object.hasOwn(target, key) ? target[key] : undefined;
|
|
2191
|
+
/**
|
|
2192
|
+
* Gives each of `keys`, names `Object.prototype` has such as `toString` or `constructor`, that `target` doesn't hold
|
|
2193
|
+
* an own `undefined`, so reading one left out gives `undefined` rather than the inherited member.
|
|
2194
|
+
*/ function shadowInherited(target, keys) {
|
|
2195
|
+
for (const key of keys)if (!Object.hasOwn(target, key)) setOwn(target, key, undefined);
|
|
2029
2196
|
}
|
|
2030
2197
|
|
|
2031
2198
|
/**
|
|
@@ -2193,37 +2360,48 @@ brand(CooldownStoreError, 'CooldownStoreError');
|
|
|
2193
2360
|
*
|
|
2194
2361
|
* @group Utilities
|
|
2195
2362
|
*/ function getInstallContext(interaction) {
|
|
2196
|
-
const
|
|
2197
|
-
const guildInstalled = owners[discord_js.ApplicationIntegrationType.GuildInstall] !== undefined;
|
|
2198
|
-
const userInstalled = owners[discord_js.ApplicationIntegrationType.UserInstall] !== undefined;
|
|
2363
|
+
const botInstalled = isBotInstalled(interaction);
|
|
2199
2364
|
switch(interaction.context){
|
|
2200
2365
|
case discord_js.InteractionContextType.BotDM:
|
|
2201
2366
|
return {
|
|
2202
2367
|
where: 'bot-dm',
|
|
2203
|
-
botInstalled
|
|
2368
|
+
botInstalled
|
|
2204
2369
|
};
|
|
2205
2370
|
case discord_js.InteractionContextType.PrivateChannel:
|
|
2206
2371
|
return {
|
|
2207
2372
|
where: 'private-channel',
|
|
2208
|
-
botInstalled
|
|
2373
|
+
botInstalled
|
|
2209
2374
|
};
|
|
2210
2375
|
case discord_js.InteractionContextType.Guild:
|
|
2211
|
-
// With no owners reported, the interaction came through the bot, as every interaction did before user installs.
|
|
2212
2376
|
return {
|
|
2213
2377
|
where: 'guild',
|
|
2214
|
-
botInstalled
|
|
2378
|
+
botInstalled
|
|
2215
2379
|
};
|
|
2216
2380
|
default:
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
botInstalled: true
|
|
2221
|
-
} : {
|
|
2222
|
-
where: 'bot-dm',
|
|
2223
|
-
botInstalled: true
|
|
2381
|
+
return {
|
|
2382
|
+
where: interaction.guildId ? 'guild' : 'bot-dm',
|
|
2383
|
+
botInstalled
|
|
2224
2384
|
};
|
|
2225
2385
|
}
|
|
2226
2386
|
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Whether the bot is present where an interaction happened, as {@link getInstallContext} reports it, read from Discord's
|
|
2389
|
+
* `context` and the integrations that authorized it alone: no `guildId`, which only `where` needs.
|
|
2390
|
+
*/ function isBotInstalled(interaction) {
|
|
2391
|
+
switch(interaction.context){
|
|
2392
|
+
case discord_js.InteractionContextType.PrivateChannel:
|
|
2393
|
+
return false;
|
|
2394
|
+
case discord_js.InteractionContextType.Guild:
|
|
2395
|
+
{
|
|
2396
|
+
const owners = interaction.authorizingIntegrationOwners ?? {};
|
|
2397
|
+
// With no owners reported, the interaction came through the bot, as every interaction did before user installs
|
|
2398
|
+
return owners[discord_js.ApplicationIntegrationType.GuildInstall] !== undefined || owners[discord_js.ApplicationIntegrationType.UserInstall] === undefined;
|
|
2399
|
+
}
|
|
2400
|
+
default:
|
|
2401
|
+
// A DM with the bot, or an interaction from before contexts existed, which reached only the bot
|
|
2402
|
+
return true;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2227
2405
|
|
|
2228
2406
|
const { Ephemeral, SuppressEmbeds, SuppressNotifications, IsComponentsV2 } = discord_js.MessageFlags;
|
|
2229
2407
|
/** The flags each call accepts; Discord rejects or ignores the rest. */ const ALLOWED = {
|
|
@@ -2256,6 +2434,22 @@ const { Ephemeral, SuppressEmbeds, SuppressNotifications, IsComponentsV2 } = dis
|
|
|
2256
2434
|
/** The names of the flags set in a value, for warnings. */ function flagNames(flags) {
|
|
2257
2435
|
return new discord_js.MessageFlagsBitField(flags).toArray();
|
|
2258
2436
|
}
|
|
2437
|
+
/** Each call as a warning names it, its article included. */ const STEP_NAMES = {
|
|
2438
|
+
deferReply: 'a deferred reply',
|
|
2439
|
+
reply: 'a reply',
|
|
2440
|
+
update: 'an update',
|
|
2441
|
+
edit: 'an edit',
|
|
2442
|
+
followUp: 'a follow-up'
|
|
2443
|
+
};
|
|
2444
|
+
/** What an update or an edit does with `Ephemeral` instead, and how to answer only the member. */ const PRIVATE_HINT = {
|
|
2445
|
+
update: 'An update changes the message the member clicked, for everyone; to answer only them, use respond(interaction).followUp() with MessageFlags.Ephemeral.',
|
|
2446
|
+
edit: 'An edit keeps the message as public or private as it was; to answer only the member, use respond(interaction).followUp() with MessageFlags.Ephemeral.'
|
|
2447
|
+
};
|
|
2448
|
+
/** The development warning for the flags a call dropped, with how to answer privately when `Ephemeral` is one of them. */ function droppedFlagsWarning(step, dropped) {
|
|
2449
|
+
const text = `Dropped flags ${flagNames(dropped).join(', ')}, which ${STEP_NAMES[step]} cannot take.`;
|
|
2450
|
+
const hint = hasEphemeral(dropped) ? PRIVATE_HINT[step] : undefined;
|
|
2451
|
+
return hint ? `${text} ${hint}` : text;
|
|
2452
|
+
}
|
|
2259
2453
|
|
|
2260
2454
|
const CDN_HOSTS = new Set([
|
|
2261
2455
|
'cdn.discordapp.com',
|
|
@@ -2404,8 +2598,12 @@ function rewriteComponent(component, kept) {
|
|
|
2404
2598
|
/** The id MeoCord gives the containers it renders, so a view left behind can be found again. */ const RENDERED_CONTAINER_ID = 0x4d43;
|
|
2405
2599
|
// Shared by both builds of this version, so either answers with the app's presenter
|
|
2406
2600
|
const presenters = shared('clientPresenters', ()=>new WeakMap());
|
|
2407
|
-
/** Sets the presenter for the interactions a client receives. */ function setPresenter(client, presenter) {
|
|
2408
|
-
presenters.set(client, presenter);
|
|
2601
|
+
/** Sets the presenter for the interactions a client receives; without one, they take the default. */ function setPresenter(client, presenter) {
|
|
2602
|
+
if (presenter) presenters.set(client, presenter);
|
|
2603
|
+
else presenters.delete(client);
|
|
2604
|
+
}
|
|
2605
|
+
/** The presenter set on a client, if any. */ function clientPresenter(client) {
|
|
2606
|
+
return presenters.get(client);
|
|
2409
2607
|
}
|
|
2410
2608
|
/** The presenter for an interaction's client, or the default one. */ function presenterFor(client) {
|
|
2411
2609
|
return client && presenters.get(client) || defaultPresenter;
|
|
@@ -2800,15 +2998,25 @@ const logger$4 = new Logger('Theme');
|
|
|
2800
2998
|
]).finally(()=>clearTimeout(timer));
|
|
2801
2999
|
}
|
|
2802
3000
|
/**
|
|
2803
|
-
* A result as a layer: copied, then checked; one with problems is left out, with a
|
|
2804
|
-
* `null`, as a database gives for a missing row, is no theme, as `undefined` is.
|
|
3001
|
+
* A result as a layer: copied, then checked; one with problems, or that throws as it is read, is left out, with a
|
|
3002
|
+
* warning once per id when `report`. `null`, as a database gives for a missing row, is no theme, as `undefined` is.
|
|
2805
3003
|
*/ accept(id, result, report) {
|
|
2806
3004
|
if (result === undefined || result === null) return undefined;
|
|
2807
|
-
const
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3005
|
+
const where = `themeFor.${this.kind} for ${this.kind} ${id}`;
|
|
3006
|
+
let layer;
|
|
3007
|
+
let problems;
|
|
3008
|
+
try {
|
|
3009
|
+
layer = copyLayer(result);
|
|
3010
|
+
// A value from the app's data, such as a user's saved choice, shown as a log line shows outside text
|
|
3011
|
+
problems = themeProblems(layer, where, {
|
|
3012
|
+
forLog: true
|
|
3013
|
+
});
|
|
3014
|
+
} catch (error) {
|
|
3015
|
+
// A cycle overflows the copy, and a getter may throw: the result is left out as one with problems is
|
|
3016
|
+
problems = [
|
|
3017
|
+
`${where}: theme cannot be read: ${quoteForLog(String(error?.message ?? error))}`
|
|
3018
|
+
];
|
|
3019
|
+
}
|
|
2812
3020
|
if (problems.length === 0) {
|
|
2813
3021
|
if (report) this.warned.delete(id);
|
|
2814
3022
|
return layer;
|
|
@@ -2937,13 +3145,14 @@ const logger$4 = new Logger('Theme');
|
|
|
2937
3145
|
const idOf = (value)=>typeof value === 'string' ? value : undefined;
|
|
2938
3146
|
/**
|
|
2939
3147
|
* The server and user a call comes from: an interaction's or a message's server and its user or author; a
|
|
2940
|
-
* reaction's message's server and the user who reacted, which a reaction handler gets second
|
|
2941
|
-
* server and user, when it has them.
|
|
3148
|
+
* reaction's message's server and the user who reacted, which a reaction handler gets second as `{ user }` and a
|
|
3149
|
+
* reaction event as the `User`; an event argument's server and user, when it has them.
|
|
2942
3150
|
*/ function themeTargets(args) {
|
|
2943
3151
|
const [first, second] = args;
|
|
2944
3152
|
if (!first || typeof first !== 'object') return {};
|
|
2945
3153
|
const guildId = idOf(first.guildId) ?? idOf(first.guild?.id) ?? idOf(first.message?.guildId);
|
|
2946
|
-
|
|
3154
|
+
// Only a User's own id: an event of an old and a new channel, role or message would give the new one's otherwise
|
|
3155
|
+
const userId = idOf(first.user?.id) ?? idOf(first.author?.id) ?? idOf(second?.user?.id) ?? (second instanceof discord_js.User ? idOf(second.id) : undefined);
|
|
2947
3156
|
return {
|
|
2948
3157
|
guildId,
|
|
2949
3158
|
userId
|
|
@@ -2970,7 +3179,8 @@ const idOf = (value)=>typeof value === 'string' ? value : undefined;
|
|
|
2970
3179
|
user
|
|
2971
3180
|
];
|
|
2972
3181
|
}
|
|
2973
|
-
|
|
3182
|
+
// One per process for this meocord version, as the rest of an app's theme state is
|
|
3183
|
+
const stores = shared('themeCaches', ()=>new WeakMap());
|
|
2974
3184
|
/**
|
|
2975
3185
|
* An app's cache of the themes `@MeoCord({ themeFor })` looked up, by server and by user.
|
|
2976
3186
|
*
|
|
@@ -3015,6 +3225,16 @@ const stores = new WeakMap();
|
|
|
3015
3225
|
else stores.delete(themeCache);
|
|
3016
3226
|
}
|
|
3017
3227
|
|
|
3228
|
+
function _class_private_field_loose_base(receiver, privateKey) {
|
|
3229
|
+
if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
|
|
3230
|
+
throw new TypeError("attempted to use private field on non-instance");
|
|
3231
|
+
}
|
|
3232
|
+
return receiver;
|
|
3233
|
+
}
|
|
3234
|
+
var id = 0;
|
|
3235
|
+
function _class_private_field_loose_key(name) {
|
|
3236
|
+
return "__private_" + id++ + "_" + name;
|
|
3237
|
+
}
|
|
3018
3238
|
const ALREADY_ACKNOWLEDGED = 40060;
|
|
3019
3239
|
/** Discord's code for an interaction it no longer knows, such as one past its three seconds unacknowledged. */ const UNKNOWN_INTERACTION = 10062;
|
|
3020
3240
|
const TOKEN_EXPIRED = new Set([
|
|
@@ -3112,6 +3332,7 @@ const messageLocks = shared('messageLocks', ()=>new Map());
|
|
|
3112
3332
|
function toJson(value) {
|
|
3113
3333
|
return typeof value?.toJSON === 'function' ? value.toJSON() : value;
|
|
3114
3334
|
}
|
|
3335
|
+
var _location = /*#__PURE__*/ _class_private_field_loose_key("_location");
|
|
3115
3336
|
/** The response state behind `respond()`, with what `@Defer` and the testing helpers use besides. */ class InteractionResponse {
|
|
3116
3337
|
get state() {
|
|
3117
3338
|
this.sync();
|
|
@@ -3150,7 +3371,7 @@ function toJson(value) {
|
|
|
3150
3371
|
flagsFor(step, requested, v2 = this.v2) {
|
|
3151
3372
|
const { flags, dropped } = resolveFlags(step, requested, v2);
|
|
3152
3373
|
if (dropped && development()) {
|
|
3153
|
-
logger$3.warn(
|
|
3374
|
+
logger$3.warn(droppedFlagsWarning(step, dropped));
|
|
3154
3375
|
}
|
|
3155
3376
|
return flags;
|
|
3156
3377
|
}
|
|
@@ -3674,7 +3895,7 @@ function toJson(value) {
|
|
|
3674
3895
|
} catch (error) {
|
|
3675
3896
|
const message = this.message;
|
|
3676
3897
|
const expired = Date.now() - this.interaction.createdTimestamp >= TOKEN_EXPIRED_AFTER_MS;
|
|
3677
|
-
if (!TOKEN_EXPIRED.has(errorCode(error)) || !expired || !this.
|
|
3898
|
+
if (!TOKEN_EXPIRED.has(errorCode(error)) || !expired || !isBotInstalled(this.interaction) || !message) throw error;
|
|
3678
3899
|
this.lastMessage = await this.call('message.edit', sent, ()=>message.edit(sent));
|
|
3679
3900
|
}
|
|
3680
3901
|
this.phase = 'replied';
|
|
@@ -3986,6 +4207,10 @@ function toJson(value) {
|
|
|
3986
4207
|
} : body;
|
|
3987
4208
|
}
|
|
3988
4209
|
constructor(interaction){
|
|
4210
|
+
Object.defineProperty(this, _location, {
|
|
4211
|
+
writable: true,
|
|
4212
|
+
value: void 0
|
|
4213
|
+
});
|
|
3989
4214
|
this.interaction = interaction;
|
|
3990
4215
|
this.phase = 'unanswered';
|
|
3991
4216
|
/** The answer steps asked for, run one after another; `pending` counts those not yet settled. */ this.queue = Promise.resolve();
|
|
@@ -4003,7 +4228,14 @@ function toJson(value) {
|
|
|
4003
4228
|
this.suppressNotifications = false;
|
|
4004
4229
|
// Set by @Defer's first step, so an error can name what acknowledged the interaction
|
|
4005
4230
|
this.underDefer = false;
|
|
4006
|
-
this
|
|
4231
|
+
Object.defineProperty(this, 'location', {
|
|
4232
|
+
get: ()=>{
|
|
4233
|
+
var _class_private_field_loose_base1, _location1;
|
|
4234
|
+
return (_class_private_field_loose_base1 = _class_private_field_loose_base(this, _location))[_location1 = _location] ?? (_class_private_field_loose_base1[_location1] = getInstallContext(interaction));
|
|
4235
|
+
},
|
|
4236
|
+
enumerable: true,
|
|
4237
|
+
configurable: true
|
|
4238
|
+
});
|
|
4007
4239
|
const message = 'message' in interaction ? interaction.message : undefined;
|
|
4008
4240
|
this.v2 = Boolean(message?.flags?.has(discord_js.MessageFlags.IsComponentsV2));
|
|
4009
4241
|
}
|
|
@@ -4085,8 +4317,10 @@ const states = shared('responseStates', ()=>new WeakMap());
|
|
|
4085
4317
|
/**
|
|
4086
4318
|
* The response state of the interaction being handled, the same one `respond(interaction)` returns,
|
|
4087
4319
|
* or `undefined` for anything that cannot be answered: a message, a reaction, an event or
|
|
4088
|
-
* autocomplete.
|
|
4320
|
+
* autocomplete. An `@On('interactionCreate')` handler's call has none either, as MeoCord answers that interaction
|
|
4321
|
+
* through the handler it dispatches it to.
|
|
4089
4322
|
*/ get response() {
|
|
4323
|
+
if (this.getType() === 'event') return undefined;
|
|
4090
4324
|
const interaction = this.getInteraction();
|
|
4091
4325
|
return interaction?.isRepliable() ? respond(interaction) : undefined;
|
|
4092
4326
|
}
|
|
@@ -4223,17 +4457,30 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4223
4457
|
}
|
|
4224
4458
|
}
|
|
4225
4459
|
|
|
4226
|
-
/**
|
|
4227
|
-
* How many calls a cooldown allows, and in how long a window.
|
|
4228
|
-
*
|
|
4229
|
-
* @group Types
|
|
4230
|
-
*/ /** `verdict` with `release` added as a non-enumerable property, as the built-in stores give it. */ function withRelease(verdict, release) {
|
|
4460
|
+
/** `verdict` with `release` added as a non-enumerable property, as the built-in stores give it. */ function withRelease(verdict, release) {
|
|
4231
4461
|
return Object.defineProperty(verdict, 'release', {
|
|
4232
4462
|
value: release,
|
|
4233
4463
|
enumerable: false,
|
|
4234
4464
|
configurable: true
|
|
4235
4465
|
});
|
|
4236
4466
|
}
|
|
4467
|
+
/**
|
|
4468
|
+
* `verdict` with `retryTimestamp` added as a non-enumerable property, unless it holds the field already, so a refusal
|
|
4469
|
+
* that gains it compares and serialises as it did.
|
|
4470
|
+
*/ function withRetryTimestamp(verdict, retryTimestamp) {
|
|
4471
|
+
if (retryTimestamp === undefined || Object.hasOwn(verdict, 'retryTimestamp')) return verdict;
|
|
4472
|
+
return Object.defineProperty(verdict, 'retryTimestamp', {
|
|
4473
|
+
value: retryTimestamp,
|
|
4474
|
+
enumerable: false,
|
|
4475
|
+
configurable: true
|
|
4476
|
+
});
|
|
4477
|
+
}
|
|
4478
|
+
/** A batch verdict for one key, as the built-in stores' `consume` gives it: `allowed`, `retryAfterMs`, and the wait's end. */ function singleVerdict({ allowed, retryAfterMs, retryTimestamp }) {
|
|
4479
|
+
return withRetryTimestamp({
|
|
4480
|
+
allowed,
|
|
4481
|
+
retryAfterMs
|
|
4482
|
+
}, retryTimestamp);
|
|
4483
|
+
}
|
|
4237
4484
|
/**
|
|
4238
4485
|
* Where `@Cooldown` counts calls.
|
|
4239
4486
|
*
|
|
@@ -4284,10 +4531,11 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4284
4531
|
*/ async consumeMany(entries) {
|
|
4285
4532
|
for (const [index, { key, limit }] of entries.entries()){
|
|
4286
4533
|
const verdict = await this.consume(key, limit);
|
|
4287
|
-
|
|
4534
|
+
// The wait's end by name, as a built-in store's consume gives it non-enumerable, which a spread leaves out
|
|
4535
|
+
if (!verdict.allowed) return withRetryTimestamp({
|
|
4288
4536
|
...verdict,
|
|
4289
4537
|
blocked: index
|
|
4290
|
-
};
|
|
4538
|
+
}, verdict.retryTimestamp);
|
|
4291
4539
|
}
|
|
4292
4540
|
return {
|
|
4293
4541
|
allowed: true,
|
|
@@ -4313,6 +4561,18 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4313
4561
|
});
|
|
4314
4562
|
}
|
|
4315
4563
|
}
|
|
4564
|
+
// CooldownStore's own consumeMany and peekMany carry a mark both builds share, so a check can tell a store's
|
|
4565
|
+
// override from either build's default, as a store that extends one build's class can be checked by the other's
|
|
4566
|
+
const DEFAULT_BATCH_METHOD = sharedKey('cooldown-store:default-batch-method');
|
|
4567
|
+
for (const method of [
|
|
4568
|
+
CooldownStore.prototype.consumeMany,
|
|
4569
|
+
CooldownStore.prototype.peekMany
|
|
4570
|
+
]){
|
|
4571
|
+
Object.defineProperty(method, DEFAULT_BATCH_METHOD, {
|
|
4572
|
+
value: true
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
/** Whether `method` is CooldownStore's own consumeMany or peekMany, from either build of this version. */ const isDefaultBatchMethod = (method)=>typeof method === 'function' && DEFAULT_BATCH_METHOD in method;
|
|
4316
4576
|
/**
|
|
4317
4577
|
* Of several refusals, the one a caller waits on: the call runs only once every entry allows it, so the
|
|
4318
4578
|
* longest wait. Undefined when nothing refused.
|
|
@@ -4403,16 +4663,12 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4403
4663
|
* @see {@link CooldownStore}
|
|
4404
4664
|
*/ class MemoryCooldownStore extends CooldownStore {
|
|
4405
4665
|
async consume(key, limit) {
|
|
4406
|
-
|
|
4666
|
+
return singleVerdict(await this.consumeMany([
|
|
4407
4667
|
{
|
|
4408
4668
|
key,
|
|
4409
4669
|
limit
|
|
4410
4670
|
}
|
|
4411
|
-
]);
|
|
4412
|
-
return {
|
|
4413
|
-
allowed,
|
|
4414
|
-
retryAfterMs
|
|
4415
|
-
};
|
|
4671
|
+
]));
|
|
4416
4672
|
}
|
|
4417
4673
|
/**
|
|
4418
4674
|
* Checks every entry, and records the call against all of them only if all allow it.
|
|
@@ -4635,16 +4891,12 @@ const DEFAULT_PREFIX = 'meocord:cooldown:';
|
|
|
4635
4891
|
* @param limit - The calls allowed, and the window they are counted over.
|
|
4636
4892
|
* @returns Whether this call was recorded, and if not, how long until one can be.
|
|
4637
4893
|
*/ async consume(key, limit) {
|
|
4638
|
-
|
|
4894
|
+
return singleVerdict(await this.consumeMany([
|
|
4639
4895
|
{
|
|
4640
4896
|
key,
|
|
4641
4897
|
limit
|
|
4642
4898
|
}
|
|
4643
|
-
]);
|
|
4644
|
-
return {
|
|
4645
|
-
allowed,
|
|
4646
|
-
retryAfterMs
|
|
4647
|
-
};
|
|
4899
|
+
]));
|
|
4648
4900
|
}
|
|
4649
4901
|
/**
|
|
4650
4902
|
* Records a call against every entry if all allow it, as one script: one round trip, however many
|
|
@@ -4833,34 +5085,70 @@ const UNIT_MS = lookupTable({
|
|
|
4833
5085
|
w: 604800000
|
|
4834
5086
|
});
|
|
4835
5087
|
/** A number written in full, `whole` for an integer: `undefined` for anything else. */ function number(word, whole) {
|
|
4836
|
-
|
|
5088
|
+
// Each run of digits splits only one way, so refusing a word takes time linear in its length
|
|
5089
|
+
if (!(whole ? /^[+-]?\d+$/ : /^[+-]?(\d+(?:\.\d*)?|\.\d+)(e[+-]?\d+)?$/i).test(word)) return undefined;
|
|
4837
5090
|
const value = Number(word);
|
|
4838
5091
|
return Number.isFinite(value) && (!whole || Number.isSafeInteger(value)) ? value : undefined;
|
|
4839
5092
|
}
|
|
4840
5093
|
/** `yes`, `no`, `true`, `false`, `on` or `off`, in any case, as a boolean. */ const bool = (word)=>BOOLEANS[word.toLowerCase()];
|
|
4841
|
-
/**
|
|
4842
|
-
|
|
5094
|
+
/**
|
|
5095
|
+
* A length of time written as amounts and units, such as `90s`, `2h30m` or `1.5d`, in milliseconds: `undefined` for one
|
|
5096
|
+
* too long to add up.
|
|
5097
|
+
*/ function duration(word) {
|
|
5098
|
+
// Sticky: each part starts where the last ended, so the scan stops at the first that doesn't and reads each part once
|
|
5099
|
+
const parts = word.toLowerCase().match(/(\d+(?:\.\d+)?)(ms|s|m|h|d|w)/gy);
|
|
4843
5100
|
if (!parts || parts.join('') !== word.toLowerCase()) return undefined;
|
|
4844
|
-
|
|
5101
|
+
const total = parts.reduce((sum, part)=>{
|
|
4845
5102
|
const [, amount, unit] = /^(\d+(?:\.\d+)?)(ms|s|m|h|d|w)$/.exec(part);
|
|
4846
|
-
return
|
|
5103
|
+
return sum + Number(amount) * UNIT_MS[unit];
|
|
4847
5104
|
}, 0);
|
|
5105
|
+
// Too long to add up, as number() refuses a value past the largest number
|
|
5106
|
+
return Number.isFinite(total) ? total : undefined;
|
|
4848
5107
|
}
|
|
4849
5108
|
/** The largest snowflake: Discord's IDs are unsigned 64-bit integers. */ const MAX_SNOWFLAKE = 2n ** 64n - 1n;
|
|
5109
|
+
/** The fewest and most digits a snowflake has, and the words of that many digits, with no leading zero. */ const SNOWFLAKE_DIGITS = {
|
|
5110
|
+
min: 17,
|
|
5111
|
+
max: 20
|
|
5112
|
+
};
|
|
5113
|
+
const SNOWFLAKE = new RegExp(`^[1-9]\\d{${SNOWFLAKE_DIGITS.min - 1},${SNOWFLAKE_DIGITS.max - 1}}$`);
|
|
4850
5114
|
/**
|
|
4851
5115
|
* A Discord ID, kept as its text: 17 to 20 digits, no leading zero, up to the largest 64-bit value. An ID's top 42 bits
|
|
4852
5116
|
* are milliseconds since 2015-01-01, so every ID made from 2015-01-28 on has 17 digits or more, and none is a safe
|
|
4853
5117
|
* integer.
|
|
4854
|
-
*/ const snowflake = (word)
|
|
4855
|
-
/** A UUID
|
|
5118
|
+
*/ const snowflake = (word)=>SNOWFLAKE.test(word) && BigInt(word) <= MAX_SNOWFLAKE ? word : undefined;
|
|
5119
|
+
/** A UUID's canonical 8-4-4-4-12 form, 36 characters with its dashes. */ const UUID = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
|
|
5120
|
+
const UUID_LENGTH = 36;
|
|
5121
|
+
/** A UUID in its canonical 8-4-4-4-12 form, in either case, kept as written. */ const uuid = (word)=>UUID.test(word) ? word : undefined;
|
|
4856
5122
|
/** The words a type such as `on|off` chooses from, or `undefined` for any other type. */ const choicesOf = (type)=>type.includes('|') ? type.split('|') : undefined;
|
|
4857
|
-
/** The
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
5123
|
+
/** The shortest segment of any type: one character. */ const SHORTEST_SEGMENT = 1;
|
|
5124
|
+
/**
|
|
5125
|
+
* The types a component's customId segment can take: each parsed from the segment alone, with no request, and the
|
|
5126
|
+
* fewest characters a segment of it has, which the check for a pattern no customId can match counts.
|
|
5127
|
+
*/ const SEGMENT_TYPES = lookupTable({
|
|
5128
|
+
string: {
|
|
5129
|
+
parse: (segment)=>segment,
|
|
5130
|
+
shortest: SHORTEST_SEGMENT
|
|
5131
|
+
},
|
|
5132
|
+
int: {
|
|
5133
|
+
parse: (segment)=>number(segment, true),
|
|
5134
|
+
shortest: SHORTEST_SEGMENT
|
|
5135
|
+
},
|
|
5136
|
+
number: {
|
|
5137
|
+
parse: (segment)=>number(segment, false),
|
|
5138
|
+
shortest: SHORTEST_SEGMENT
|
|
5139
|
+
},
|
|
5140
|
+
bool: {
|
|
5141
|
+
parse: bool,
|
|
5142
|
+
shortest: Math.min(...Object.keys(BOOLEANS).map((word)=>word.length))
|
|
5143
|
+
},
|
|
5144
|
+
snowflake: {
|
|
5145
|
+
parse: snowflake,
|
|
5146
|
+
shortest: SNOWFLAKE_DIGITS.min
|
|
5147
|
+
},
|
|
5148
|
+
uuid: {
|
|
5149
|
+
parse: uuid,
|
|
5150
|
+
shortest: UUID_LENGTH
|
|
5151
|
+
}
|
|
4864
5152
|
});
|
|
4865
5153
|
/**
|
|
4866
5154
|
* Whether `type` can type a customId segment: `string`, `int`, `number`, `bool`, `snowflake`, `uuid`, or words to
|
|
@@ -4869,7 +5157,12 @@ const UNIT_MS = lookupTable({
|
|
|
4869
5157
|
/** A customId segment as its type's value, words to choose from compared as written: `undefined` when it is not one. */ function parseSegment(type, segment) {
|
|
4870
5158
|
const choices = choicesOf(type);
|
|
4871
5159
|
if (choices) return choices.includes(segment) ? segment : undefined;
|
|
4872
|
-
return SEGMENT_TYPES[type](segment);
|
|
5160
|
+
return SEGMENT_TYPES[type].parse(segment);
|
|
5161
|
+
}
|
|
5162
|
+
/** The fewest characters a customId segment of `type` has: words to choose from count their shortest word. */ function shortestSegment(type) {
|
|
5163
|
+
const choices = choicesOf(type);
|
|
5164
|
+
if (choices) return Math.min(...choices.map((word)=>word.length));
|
|
5165
|
+
return SEGMENT_TYPES[type]?.shortest ?? SHORTEST_SEGMENT;
|
|
4873
5166
|
}
|
|
4874
5167
|
|
|
4875
5168
|
/**
|
|
@@ -4919,6 +5212,12 @@ const UNIT_MS = lookupTable({
|
|
|
4919
5212
|
return `; it was given ${describeValue(value)}`;
|
|
4920
5213
|
}
|
|
4921
5214
|
const logger = new Logger('Command');
|
|
5215
|
+
/** The interaction types a method's `@Command` routes declare, each once. */ function routeTypesOf(prototype, method) {
|
|
5216
|
+
const metas = Object.values(getCommandMap(prototype) ?? {}).flat();
|
|
5217
|
+
return [
|
|
5218
|
+
...new Set(metas.filter((meta)=>meta.methodName === method).map((meta)=>meta.type))
|
|
5219
|
+
];
|
|
5220
|
+
}
|
|
4922
5221
|
/**
|
|
4923
5222
|
* The class's own handler list, started from a copy of the inherited one, so a subclass's
|
|
4924
5223
|
* handlers never land in its base class's metadata.
|
|
@@ -4944,6 +5243,10 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
4944
5243
|
method,
|
|
4945
5244
|
label: `autocomplete of ${optionName === undefined ? 'every option' : `"${optionName}"`} in "${commandPath}"`
|
|
4946
5245
|
});
|
|
5246
|
+
/** An `@On` or `@Once` handler as a warning names its route; one event per method, as a subclass replaces it. */ const eventRoute = ({ method, event })=>({
|
|
5247
|
+
method,
|
|
5248
|
+
label: `event "${event}"`
|
|
5249
|
+
});
|
|
4947
5250
|
/** Records a route a class's own decorator declares for a method, which the startup check reads. */ function declareRoute(target, declared) {
|
|
4948
5251
|
const routes = Reflect.getOwnMetadata(META.declaredRoutes, target) ?? [];
|
|
4949
5252
|
routes.push(declared);
|
|
@@ -4971,13 +5274,22 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
4971
5274
|
else handlers[index] = entry;
|
|
4972
5275
|
Reflect.defineMetadata(key, handlers, target);
|
|
4973
5276
|
}
|
|
5277
|
+
/** The `@On` and `@Once` entries a class's own decorators declare, without those it inherits from its base. */ function ownEventHandlers(prototype) {
|
|
5278
|
+
const inherited = Reflect.getMetadata(META.eventHandlers, Object.getPrototypeOf(prototype)) ?? [];
|
|
5279
|
+
return ownHandlerList(META.eventHandlers, prototype).filter((entry)=>!inherited.includes(entry));
|
|
5280
|
+
}
|
|
4974
5281
|
/**
|
|
4975
5282
|
* Leaves each method the class's own decorators declare only those routes, of every kind, dropping the ones it inherits
|
|
4976
5283
|
* for it: what `@Controller({ inheritedRoutes: 'replace' })` does once the class's method decorators have run.
|
|
4977
5284
|
*/ function dropInheritedRoutes(prototype) {
|
|
4978
|
-
const redeclared = new Set(getDeclaredRoutes(prototype).map((route)=>route.method));
|
|
4979
|
-
if (redeclared.size === 0) return;
|
|
4980
5285
|
const base = Object.getPrototypeOf(prototype);
|
|
5286
|
+
// The class's own @On and @Once re-declare their methods too
|
|
5287
|
+
const ownEvents = ownEventHandlers(prototype);
|
|
5288
|
+
const redeclared = new Set([
|
|
5289
|
+
...getDeclaredRoutes(prototype).map((route)=>route.method),
|
|
5290
|
+
...ownEvents.map((entry)=>entry.method)
|
|
5291
|
+
]);
|
|
5292
|
+
if (redeclared.size === 0) return;
|
|
4981
5293
|
// An entry the class takes from its base, for a method it re-declares
|
|
4982
5294
|
const inheritedFor = (inherited, method)=>(entry)=>inherited.includes(entry) && redeclared.has(method(entry));
|
|
4983
5295
|
const commands = ownCommandMap(prototype);
|
|
@@ -5000,6 +5312,10 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
5000
5312
|
[
|
|
5001
5313
|
META.autocompleteHandlers,
|
|
5002
5314
|
(entry)=>entry.methodName
|
|
5315
|
+
],
|
|
5316
|
+
[
|
|
5317
|
+
META.eventHandlers,
|
|
5318
|
+
(entry)=>entry.method
|
|
5003
5319
|
]
|
|
5004
5320
|
];
|
|
5005
5321
|
for (const [key, method] of lists){
|
|
@@ -5026,13 +5342,14 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
5026
5342
|
function MessageHandler(pattern, options = {}) {
|
|
5027
5343
|
return declaring(function(target, propertyKey) {
|
|
5028
5344
|
refuseOnClass('@MessageHandler', target, propertyKey);
|
|
5029
|
-
// An empty pattern means every message, as no pattern does
|
|
5030
|
-
|
|
5031
|
-
|
|
5345
|
+
// An empty pattern, or one of spaces alone, means every message, as no pattern does
|
|
5346
|
+
const blank = pattern !== undefined && pattern.trim() === '';
|
|
5347
|
+
if (blank) {
|
|
5348
|
+
warnDeprecatedBehaviour(logger, `@MessageHandler('${pattern}') on ${decoratedName(target, propertyKey)}`, 'is refused', '@MessageHandler()');
|
|
5032
5349
|
}
|
|
5033
5350
|
const method = propertyKey.toString();
|
|
5034
5351
|
const declared = {
|
|
5035
|
-
pattern:
|
|
5352
|
+
pattern: blank ? undefined : pattern,
|
|
5036
5353
|
method,
|
|
5037
5354
|
options
|
|
5038
5355
|
};
|
|
@@ -5154,13 +5471,14 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5154
5471
|
params: params.length,
|
|
5155
5472
|
typed: Object.values(types).reduce((sum, type)=>sum + (choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1), 0)
|
|
5156
5473
|
});
|
|
5157
|
-
// Each param takes at least
|
|
5474
|
+
// Each param takes at least the shortest value of its type
|
|
5475
|
+
const shortest = literalLength + params.reduce((sum, param)=>sum + shortestSegment(types[param] ?? 'string'), 0);
|
|
5158
5476
|
return {
|
|
5159
5477
|
regex,
|
|
5160
5478
|
params,
|
|
5161
5479
|
types: lookupTable(types),
|
|
5162
5480
|
specificity,
|
|
5163
|
-
shortest
|
|
5481
|
+
shortest
|
|
5164
5482
|
};
|
|
5165
5483
|
}
|
|
5166
5484
|
/** Why a `{name:type}` cannot type a customId segment, which holds text the bot wrote, with no message to read. */ function segmentTypeProblem(param, type) {
|
|
@@ -5233,13 +5551,17 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5233
5551
|
refuseOnClass('@Command', target, propertyKey);
|
|
5234
5552
|
const originalMethod = _descriptor.value;
|
|
5235
5553
|
if (!originalMethod) {
|
|
5236
|
-
throw refuse(new Error(`${target
|
|
5554
|
+
throw refuse(new Error(`${decoratedName(target, propertyKey)}: @Command is on something with no implementation.`));
|
|
5237
5555
|
}
|
|
5238
5556
|
// Wrap original method for interaction type validation
|
|
5239
5557
|
const declaredAs = typeof builderOrType === 'function' ? builderOrType.name : `CommandType.${builderOrType}`;
|
|
5240
5558
|
const checked = function(interaction, params) {
|
|
5241
|
-
|
|
5242
|
-
|
|
5559
|
+
// Every type the method's routes declare, in the class the call is made on: each @Command wraps it, so one alone
|
|
5560
|
+
// would refuse the others' interactions, and an override that adds a route type reaches this one through super
|
|
5561
|
+
const called = typeof this === 'object' && this !== null && Object.prototype.isPrototypeOf.call(target, this) ? Object.getPrototypeOf(this) : target;
|
|
5562
|
+
const types = routeTypesOf(called, propertyKey);
|
|
5563
|
+
if (!types.some((type)=>matchesCommandType(type, interaction))) {
|
|
5564
|
+
throw new Error(types.length <= 1 ? `${decoratedName(target, propertyKey)}: @Command('${commandName}', ${declaredAs}) takes ` + `${interactionClassName(commandType)}${givenInstead(interaction)}.` : `${decoratedName(target, propertyKey)}: its @Command routes take ${types.map(interactionClassName).join(' or ')}` + `${givenInstead(interaction)}.`);
|
|
5243
5565
|
}
|
|
5244
5566
|
return originalMethod.apply(this, [
|
|
5245
5567
|
interaction,
|
|
@@ -5257,7 +5579,7 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5257
5579
|
let guilds;
|
|
5258
5580
|
// Determine command type and builder
|
|
5259
5581
|
if (typeof builderOrType === 'function') {
|
|
5260
|
-
const where =
|
|
5582
|
+
const where = decoratedName(target, propertyKey);
|
|
5261
5583
|
// A subcommand is part of its command's builder, which belongs on the command's own name
|
|
5262
5584
|
const subcommandPath = Reflect.getMetadata(META.commandType, builderOrType) === enum_index.CommandType.SLASH && commandName.includes(' ');
|
|
5263
5585
|
const command = commandName.split(' ')[0];
|
|
@@ -5304,12 +5626,12 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5304
5626
|
if (isCustomIdRouted(commandType)) {
|
|
5305
5627
|
// A route warned as it was made
|
|
5306
5628
|
const braces = typeof name === 'string' ? literalBraces(commandName) : undefined;
|
|
5307
|
-
if (braces) logger.warn(`${target
|
|
5629
|
+
if (braces) logger.warn(`${decoratedName(target, propertyKey)}: in the pattern "${commandName}", ${braces}`);
|
|
5308
5630
|
let pattern;
|
|
5309
5631
|
try {
|
|
5310
5632
|
pattern = createRegexFromPattern(commandName);
|
|
5311
5633
|
} catch (error) {
|
|
5312
|
-
throw refuse(new Error(`${target
|
|
5634
|
+
throw refuse(new Error(`${decoratedName(target, propertyKey)}: ${error.message}`, {
|
|
5313
5635
|
cause: error
|
|
5314
5636
|
}));
|
|
5315
5637
|
}
|
|
@@ -5319,7 +5641,7 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5319
5641
|
// Warned rather than refused, so a bot that carries such a handler still starts
|
|
5320
5642
|
if (pattern.shortest === 0 || pattern.shortest > MAX_CUSTOM_ID_LENGTH) {
|
|
5321
5643
|
const length = pattern.shortest === 0 ? 'the pattern is empty' : `its shortest is ${pattern.shortest} characters`;
|
|
5322
|
-
logger.warn(`${target
|
|
5644
|
+
logger.warn(`${decoratedName(target, propertyKey)}: the pattern "${commandName}" matches no customId: Discord's customIds ` + `are 1 to ${MAX_CUSTOM_ID_LENGTH} characters, and ${length}, so the handler never runs.`);
|
|
5323
5645
|
}
|
|
5324
5646
|
}
|
|
5325
5647
|
const declared = {
|
|
@@ -5371,7 +5693,7 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5371
5693
|
const registered = built?.type;
|
|
5372
5694
|
if (!declared || registered === undefined || declared.kind === registered) return;
|
|
5373
5695
|
const kind = registered === discord_js.ApplicationCommandType.User ? 'user' : 'message';
|
|
5374
|
-
throw refuse(new Error(`${target
|
|
5696
|
+
throw refuse(new Error(`${decoratedName(target, propertyKey)}: it takes a ${declared.name} context menu interaction, but ${builderName} ` + `registers "${commandName}" as a ${kind} context menu command. Declare the handler's interaction as the kind the ` + `builder's setType() names.`));
|
|
5375
5697
|
}
|
|
5376
5698
|
/**
|
|
5377
5699
|
* Retrieves the command map for a given controller.
|
|
@@ -5532,11 +5854,13 @@ exports.ReactionHandler = ReactionHandler;
|
|
|
5532
5854
|
exports.RedisCooldownStore = RedisCooldownStore;
|
|
5533
5855
|
exports.SetMetadata = SetMetadata;
|
|
5534
5856
|
exports.TEXT_LIMITS = TEXT_LIMITS;
|
|
5857
|
+
exports.Theme = Theme;
|
|
5535
5858
|
exports.ThemeCache = ThemeCache;
|
|
5536
5859
|
exports.Translator = Translator;
|
|
5537
5860
|
exports.UnroutedExecutionContext = UnroutedExecutionContext;
|
|
5538
5861
|
exports.UserError = UserError;
|
|
5539
5862
|
exports.ValidationError = ValidationError;
|
|
5863
|
+
exports.addOwnHandler = addOwnHandler;
|
|
5540
5864
|
exports.allDeclaredErrors = allDeclaredErrors;
|
|
5541
5865
|
exports.ambientThemeVersion = ambientThemeVersion;
|
|
5542
5866
|
exports.applyDecorators = applyDecorators;
|
|
@@ -5550,6 +5874,7 @@ exports.bundleEntry = bundleEntry;
|
|
|
5550
5874
|
exports.callOrder = callOrder;
|
|
5551
5875
|
exports.choicesOf = choicesOf;
|
|
5552
5876
|
exports.claimAmbientTheme = claimAmbientTheme;
|
|
5877
|
+
exports.clientPresenter = clientPresenter;
|
|
5553
5878
|
exports.collectStartupErrors = collectStartupErrors;
|
|
5554
5879
|
exports.commandsNamed = commandsNamed;
|
|
5555
5880
|
exports.compiledConfigMessage = compiledConfigMessage;
|
|
@@ -5571,6 +5896,7 @@ exports.describeValue = describeValue;
|
|
|
5571
5896
|
exports.dropInheritedRoutes = dropInheritedRoutes;
|
|
5572
5897
|
exports.duration = duration;
|
|
5573
5898
|
exports.escapeForLog = escapeForLog;
|
|
5899
|
+
exports.eventRoute = eventRoute;
|
|
5574
5900
|
exports.existingResponse = existingResponse;
|
|
5575
5901
|
exports.fitText = fitText;
|
|
5576
5902
|
exports.focusedOptionName = focusedOptionName;
|
|
@@ -5583,7 +5909,6 @@ exports.getInstallContext = getInstallContext;
|
|
|
5583
5909
|
exports.getMessageHandlers = getMessageHandlers;
|
|
5584
5910
|
exports.getReactionHandlers = getReactionHandlers;
|
|
5585
5911
|
exports.handOffRefusal = handOffRefusal;
|
|
5586
|
-
exports.hasAmbientTheme = hasAmbientTheme;
|
|
5587
5912
|
exports.hasCustomId = hasCustomId;
|
|
5588
5913
|
exports.hideInLogs = hideInLogs;
|
|
5589
5914
|
exports.inferContextType = inferContextType;
|
|
@@ -5592,6 +5917,7 @@ exports.isBuiltApplication = isBuiltApplication;
|
|
|
5592
5917
|
exports.isConstructible = isConstructible;
|
|
5593
5918
|
exports.isConstructor = isConstructor;
|
|
5594
5919
|
exports.isCustomIdRouted = isCustomIdRouted;
|
|
5920
|
+
exports.isDefaultBatchMethod = isDefaultBatchMethod;
|
|
5595
5921
|
exports.isExplainedError = isExplainedError;
|
|
5596
5922
|
exports.isPluralShape = isPluralShape;
|
|
5597
5923
|
exports.isRefusal = isRefusal;
|
|
@@ -5613,7 +5939,9 @@ exports.mergeTheme = mergeTheme;
|
|
|
5613
5939
|
exports.messageLocale = messageLocale;
|
|
5614
5940
|
exports.missingTranslatorError = missingTranslatorError;
|
|
5615
5941
|
exports.number = number;
|
|
5616
|
-
exports.
|
|
5942
|
+
exports.ownEventHandlers = ownEventHandlers;
|
|
5943
|
+
exports.ownValue = ownValue;
|
|
5944
|
+
exports.ownerName = ownerName;
|
|
5617
5945
|
exports.ownsAmbientTheme = ownsAmbientTheme;
|
|
5618
5946
|
exports.paramNarrowness = paramNarrowness;
|
|
5619
5947
|
exports.parseSegment = parseSegment;
|
|
@@ -5637,33 +5965,34 @@ exports.responseOf = responseOf;
|
|
|
5637
5965
|
exports.routeSpecificity = routeSpecificity;
|
|
5638
5966
|
exports.runInThemeScope = runInThemeScope;
|
|
5639
5967
|
exports.runWithTheme = runWithTheme;
|
|
5640
|
-
exports.setLegacyThemeLayer = setLegacyThemeLayer;
|
|
5641
5968
|
exports.setOwn = setOwn;
|
|
5642
5969
|
exports.setPresenter = setPresenter;
|
|
5643
5970
|
exports.setStartupErrorsMode = setStartupErrorsMode;
|
|
5644
5971
|
exports.setThemeOutsideCalls = setThemeOutsideCalls;
|
|
5972
|
+
exports.shadowInherited = shadowInherited;
|
|
5645
5973
|
exports.shared = shared;
|
|
5646
5974
|
exports.sharedCustomId = sharedCustomId;
|
|
5647
5975
|
exports.sharedName = sharedName;
|
|
5976
|
+
exports.singleVerdict = singleVerdict;
|
|
5648
5977
|
exports.stampCall = stampCall;
|
|
5649
5978
|
exports.startupError = startupError;
|
|
5979
|
+
exports.startupErrorsOf = startupErrorsOf;
|
|
5650
5980
|
exports.stopOnStartupErrors = stopOnStartupErrors;
|
|
5651
5981
|
exports.textOfIssue = textOfIssue;
|
|
5652
5982
|
exports.textRenderer = textRenderer;
|
|
5653
5983
|
exports.themeForInteraction = themeForInteraction;
|
|
5654
5984
|
exports.themeForProblem = themeForProblem;
|
|
5655
5985
|
exports.themeLayersVersion = themeLayersVersion;
|
|
5656
|
-
exports.themeProblems = themeProblems;
|
|
5657
5986
|
exports.themeResolverClass = themeResolverClass;
|
|
5658
5987
|
exports.translatorOfClient = translatorOfClient;
|
|
5659
5988
|
exports.usageHeading = usageHeading;
|
|
5660
5989
|
exports.usageIssue = usageIssue;
|
|
5661
5990
|
exports.useTheme = useTheme;
|
|
5662
5991
|
exports.userWords = userWords;
|
|
5663
|
-
exports.warnDeprecated = warnDeprecated;
|
|
5664
5992
|
exports.warnDeprecatedBehaviour = warnDeprecatedBehaviour;
|
|
5665
5993
|
exports.warnOnce = warnOnce;
|
|
5666
5994
|
exports.warnPlaceholder = warnPlaceholder;
|
|
5995
|
+
exports.withArticle = withArticle;
|
|
5667
5996
|
exports.withRelease = withRelease;
|
|
5668
5997
|
exports.withSendableFiles = withSendableFiles;
|
|
5669
5998
|
exports.withoutFiles = withoutFiles;
|