meocord 4.2.1 → 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 +188 -0
- package/dist/cjs/_shared/{controller.decorator-XKMlcUZz.cjs → controller.decorator-hp5xdJU4.cjs} +550 -189
- package/dist/cjs/_shared/{startup-roots-CgfPQrNl.cjs → event-sources-DzqTODXg.cjs} +274 -88
- package/dist/cjs/_shared/{providers-BZgJyfZS.cjs → providers-BBvbnmLP.cjs} +837 -295
- package/dist/cjs/_shared/{sharded-cooldown-store-CsJJyCZd.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-BUAR1WYG.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +435 -182
- package/dist/cjs/decorator/index.cjs +116 -121
- package/dist/cjs/testing/index.cjs +1923 -808
- 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 +32 -10
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +27 -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 +92 -66
- 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 +6 -6
- 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 +54 -32
- 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 +83 -54
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +37 -15
- 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 +4 -2
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +39 -4
- 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/stage-scope.js +2 -1
- 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 +31 -30
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +37 -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 +4 -4
- 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 +32 -25
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -335
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +217 -91
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +864 -124
- 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 +8 -4
- 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 +33 -5
- package/dist/types/common/index.d.cts +17 -10
- package/dist/types/common/index.d.ts +17 -10
- 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 +158 -61
- package/dist/types/testing/index.d.ts +158 -61
- 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-XKMlcUZz.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.1";
|
|
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.
|
|
@@ -595,6 +611,9 @@ let themeOutsideCalls;
|
|
|
595
611
|
* Such a listener runs in its emitter's context, so without it a `useTheme()` inside reads the theme outside any call.
|
|
596
612
|
* Timers and promises the handler starts keep its theme without it.
|
|
597
613
|
*
|
|
614
|
+
* The theme is the binding call's whole theme, its server's and user's themes included, so every later call, whoever
|
|
615
|
+
* it is for, is answered in the starting member's theme.
|
|
616
|
+
*
|
|
598
617
|
* @param fn - The function to run in the call's theme; `this` and its arguments are passed through.
|
|
599
618
|
* @returns A function that runs `fn` in the theme `useTheme()` returns where `bindTheme` is called.
|
|
600
619
|
*
|
|
@@ -604,7 +623,7 @@ let themeOutsideCalls;
|
|
|
604
623
|
* async poll(message: Message) {
|
|
605
624
|
* const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button, time: 60_000 })
|
|
606
625
|
* collector.on('collect', bindTheme(async (click: ButtonInteraction) => {
|
|
607
|
-
* const { emojis } = useTheme() // the
|
|
626
|
+
* const { emojis } = useTheme() // the starting member's theme, @UseTheme included
|
|
608
627
|
* await click.reply(`${emojis.success} Picked`)
|
|
609
628
|
* }))
|
|
610
629
|
* }
|
|
@@ -632,7 +651,10 @@ const EXPLAINED = Symbol.for('meocord.explained');
|
|
|
632
651
|
*
|
|
633
652
|
* Use it where a bot logs a startup failure, so an error MeoCord explained is not logged twice: an app
|
|
634
653
|
* `MeoCordFactory.create()` refuses, such as two handlers for one command, or Discord refusing the bot token or a
|
|
635
|
-
* 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.
|
|
636
658
|
*
|
|
637
659
|
* @param error - An error `MeoCordFactory.create()` threw or `app.start()` rejected with.
|
|
638
660
|
* @returns `true` when MeoCord explained the error.
|
|
@@ -648,9 +670,10 @@ const EXPLAINED = Symbol.for('meocord.explained');
|
|
|
648
670
|
* await app.start()
|
|
649
671
|
* }
|
|
650
672
|
*
|
|
651
|
-
* // 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
|
|
652
674
|
* bootstrap().catch(error => {
|
|
653
|
-
* if (
|
|
675
|
+
* if (isExplainedError(error)) return
|
|
676
|
+
* logger.error('Error during startup:', error)
|
|
654
677
|
* process.exitCode = 1
|
|
655
678
|
* })
|
|
656
679
|
* ```
|
|
@@ -679,10 +702,16 @@ const EXPLAINED = Symbol.for('meocord.explained');
|
|
|
679
702
|
}
|
|
680
703
|
|
|
681
704
|
const REFUSAL = Symbol.for('meocord.refusal');
|
|
682
|
-
/**
|
|
683
|
-
|
|
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
|
+
}));
|
|
684
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) {
|
|
685
|
-
handedOff.add(error);
|
|
714
|
+
refusals.handedOff.add(error);
|
|
686
715
|
}
|
|
687
716
|
/**
|
|
688
717
|
* Marks an error MeoCord raises for code it refuses as the application loads, such as a decorator given what it
|
|
@@ -693,18 +722,29 @@ const REFUSAL = Symbol.for('meocord.refusal');
|
|
|
693
722
|
enumerable: false
|
|
694
723
|
});
|
|
695
724
|
// Before the error is thrown, so a built application reports it however the throw ends
|
|
696
|
-
if (!reporting && isBuiltApplication()) reportRefusals();
|
|
725
|
+
if (!refusals.reporting && isBuiltApplication()) reportRefusals();
|
|
697
726
|
return error;
|
|
698
727
|
}
|
|
699
|
-
/**
|
|
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) {
|
|
700
735
|
if (propertyKey === undefined) return target.name || 'a class';
|
|
701
|
-
return `${target
|
|
736
|
+
return `${ownerName(target)}.${String(propertyKey)}`;
|
|
702
737
|
}
|
|
703
738
|
/**
|
|
704
739
|
* Refuses a decorator that goes only on a method, or on a member, where it is applied to a class, directly or through
|
|
705
|
-
* `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.
|
|
706
742
|
*/ function refuseOnClass(decorator, target, propertyKey, place = 'a method') {
|
|
707
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
|
+
}
|
|
708
748
|
}
|
|
709
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) {
|
|
710
750
|
if (propertyKey !== undefined) throw refuse(new Error(`${decoratedName(target, propertyKey)}: ${decorator} goes on a class, not on a method.`));
|
|
@@ -887,9 +927,9 @@ function keepOrThrow(error, target, propertyKey) {
|
|
|
887
927
|
* report. Installed by the first refusal in a built application. Bun has no monitor event for a rejection, so there a
|
|
888
928
|
* listener reports a refused rejection and rejects any other again without itself, unless the app listens itself.
|
|
889
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) } = {}) {
|
|
890
|
-
reporting = true;
|
|
930
|
+
refusals.reporting = true;
|
|
891
931
|
const report = (error)=>{
|
|
892
|
-
if (handedOff.has(error)) return;
|
|
932
|
+
if (refusals.handedOff.has(error)) return;
|
|
893
933
|
if (!isExplainedError(error)) log(describeRefusal(error, process.cwd()));
|
|
894
934
|
exit(1);
|
|
895
935
|
};
|
|
@@ -908,8 +948,8 @@ function keepOrThrow(error, target, propertyKey) {
|
|
|
908
948
|
process.on('unhandledRejection', onRejection);
|
|
909
949
|
}
|
|
910
950
|
|
|
911
|
-
/** 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\
|
|
912
|
-
/** 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;
|
|
913
953
|
const SHORT_ESCAPES = {
|
|
914
954
|
'\n': '\\n',
|
|
915
955
|
'\r': '\\r',
|
|
@@ -933,8 +973,12 @@ const escapeChar = (char)=>SHORT_ESCAPES[char] ?? `\\u${char.charCodeAt(0).toStr
|
|
|
933
973
|
if (characters.length <= LOGGED_CHARACTERS) return quoted(text);
|
|
934
974
|
return `${quoted(`${characters.slice(0, LOGGED_CHARACTERS).join('')}…`)} (${characters.length} characters)`;
|
|
935
975
|
}
|
|
936
|
-
/**
|
|
937
|
-
|
|
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, ' '), {
|
|
938
982
|
maskedLink: true
|
|
939
983
|
});
|
|
940
984
|
}
|
|
@@ -989,7 +1033,7 @@ const describeGroup = (value, quote = asWritten)=>Array.isArray(value) ? 'an arr
|
|
|
989
1033
|
const prototype = Object.getPrototypeOf(value);
|
|
990
1034
|
return prototype === Object.prototype || prototype === null;
|
|
991
1035
|
};
|
|
992
|
-
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";
|
|
993
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)=>{
|
|
994
1038
|
const name = value.constructor?.name;
|
|
995
1039
|
return typeof name === 'string' && name ? `a ${name}` : 'an object without a plain prototype';
|
|
@@ -1085,6 +1129,107 @@ const PLAIN = "give a plain object, such as { ...value }, or a row's toObject(),
|
|
|
1085
1129
|
throw refuse(new Error(`${where}: the theme has ${problems.length} problem${problems.length === 1 ? '' : 's'}:\n${problems.map((problem)=>` ${problem}`).join('\n')}`));
|
|
1086
1130
|
}
|
|
1087
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
|
+
|
|
1088
1233
|
/**
|
|
1089
1234
|
* Composes several class or method decorators into one.
|
|
1090
1235
|
*
|
|
@@ -1246,17 +1391,36 @@ const logger$5 = new Logger('SetMetadata');
|
|
|
1246
1391
|
}
|
|
1247
1392
|
const DISCORD_LOCALES = new Set(Object.values(discord_js.Locale));
|
|
1248
1393
|
const languageOf = (locale)=>locale.split('-')[0];
|
|
1249
|
-
|
|
1394
|
+
const PLURAL_CATEGORIES = new Set([
|
|
1395
|
+
'zero',
|
|
1396
|
+
'one',
|
|
1397
|
+
'two',
|
|
1398
|
+
'few',
|
|
1399
|
+
'many',
|
|
1400
|
+
'other'
|
|
1401
|
+
]);
|
|
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));
|
|
1403
|
+
/** The value at a dotted key's path, or undefined where the path ends early. */ function valueAt(catalog, key) {
|
|
1250
1404
|
let current = catalog;
|
|
1251
1405
|
for (const part of key.split('.')){
|
|
1252
1406
|
if (typeof current !== 'object' || current === null) return undefined;
|
|
1253
1407
|
current = current[part];
|
|
1254
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);
|
|
1255
1419
|
if (typeof current === 'string') return current;
|
|
1256
|
-
if (
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
return undefined;
|
|
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;
|
|
1260
1424
|
}
|
|
1261
1425
|
const plurals = new Map();
|
|
1262
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) {
|
|
@@ -1320,7 +1484,7 @@ class CatalogTranslator extends Translator {
|
|
|
1320
1484
|
}
|
|
1321
1485
|
translate(requested, key, params = {}) {
|
|
1322
1486
|
for (const locale of this[LOCALE_CHAIN](requested)){
|
|
1323
|
-
const message = lookup(this.catalogs[locale], key);
|
|
1487
|
+
const message = lookup(this.catalogs[locale], key, this.catalogs[this.defaultLocale]);
|
|
1324
1488
|
if (message === undefined) continue;
|
|
1325
1489
|
// An empty translation, as tools export an untranslated string, counts as missing: Discord refuses an empty reply
|
|
1326
1490
|
const text = pluralForm({
|
|
@@ -1356,7 +1520,7 @@ class CatalogTranslator extends Translator {
|
|
|
1356
1520
|
const localized = {};
|
|
1357
1521
|
for (const locale of this.locales){
|
|
1358
1522
|
if (locale === this.defaultLocale) continue;
|
|
1359
|
-
const message = lookup(this.catalogs[locale], key);
|
|
1523
|
+
const message = lookup(this.catalogs[locale], key, this.catalogs[this.defaultLocale]);
|
|
1360
1524
|
// A translation with a {param} is left out, as Discord would show it as written, and so is an empty one
|
|
1361
1525
|
if (typeof message === 'string' && message !== '' && placeholderNames(message).length === 0) {
|
|
1362
1526
|
localized[locale] = fillPlaceholders(message, (_name, whole)=>whole);
|
|
@@ -1530,8 +1694,9 @@ const ENGLISH = 'en-US';
|
|
|
1530
1694
|
/**
|
|
1531
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
|
|
1532
1696
|
* MeoCord's English as the English catalog. An English request reads it after the app's English catalogs and before a
|
|
1533
|
-
* default in another language; any other request reads it last.
|
|
1534
|
-
|
|
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) {
|
|
1535
1700
|
const english = ()=>{
|
|
1536
1701
|
const message = lookup(MEOCORD_MESSAGES, key);
|
|
1537
1702
|
return message === undefined ? undefined : {
|
|
@@ -1546,7 +1711,10 @@ const ENGLISH = 'en-US';
|
|
|
1546
1711
|
const beforeDefault = englishFirst && served === translator.defaultLocale ? english() : undefined;
|
|
1547
1712
|
if (beforeDefault) return beforeDefault;
|
|
1548
1713
|
const message = lookup(translator[CATALOGS][served], key);
|
|
1549
|
-
if (message !== undefined
|
|
1714
|
+
if (message !== undefined && pluralForm({
|
|
1715
|
+
message,
|
|
1716
|
+
locale: served
|
|
1717
|
+
}, count) !== '') return {
|
|
1550
1718
|
message,
|
|
1551
1719
|
locale: served,
|
|
1552
1720
|
own: true
|
|
@@ -1567,9 +1735,9 @@ const lists = new Map();
|
|
|
1567
1735
|
* that has it; any other list is joined with its `joiner`, a comma unless given, as Intl's `unit` lists join with
|
|
1568
1736
|
* nothing in zh-CN and with a space in ja.
|
|
1569
1737
|
*/ function renderText(translator, locale, text) {
|
|
1570
|
-
const found = findText(translator, locale, text.key);
|
|
1571
|
-
if (!found) return text.fallback ?? text.key;
|
|
1572
1738
|
const params = text.params ?? {};
|
|
1739
|
+
const found = findText(translator, locale, text.key, params.count);
|
|
1740
|
+
if (!found) return text.fallback ?? text.key;
|
|
1573
1741
|
return fillPlaceholders(pluralForm(found, params.count), (name, whole)=>{
|
|
1574
1742
|
if (!Object.hasOwn(params, name)) return whole;
|
|
1575
1743
|
const value = params[name];
|
|
@@ -1595,7 +1763,8 @@ const translators = shared('clientTranslators', ()=>new WeakMap());
|
|
|
1595
1763
|
/** A text for an interaction, in its user's language through its app's translator. */ function textFor(interaction, text) {
|
|
1596
1764
|
return renderText(translatorOfClient(interaction.client), interactionLocale(interaction), text);
|
|
1597
1765
|
}
|
|
1598
|
-
|
|
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());
|
|
1599
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) {
|
|
1600
1769
|
const issue = {
|
|
1601
1770
|
...param !== undefined && {
|
|
@@ -1978,7 +2147,24 @@ brand(CooldownStoreError, 'CooldownStoreError');
|
|
|
1978
2147
|
else logger.error(`Could not ${what}:`, error);
|
|
1979
2148
|
}
|
|
1980
2149
|
|
|
1981
|
-
/** Whether a value
|
|
2150
|
+
/** Whether `new` takes a value, as `instanceof` needs of its right side, such as a `@Catch` type. */ function isConstructible(value) {
|
|
2151
|
+
if (typeof value !== 'function') return false;
|
|
2152
|
+
try {
|
|
2153
|
+
// Fails unless value can be new.target, which only a constructor can; none of value's constructor code runs
|
|
2154
|
+
Reflect.construct(String, [], value);
|
|
2155
|
+
return true;
|
|
2156
|
+
} catch {
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Whether a value is a class, which a stage entry or an app option must be: constructible, with an object prototype for
|
|
2162
|
+
* the metadata MeoCord reads. An arrow function, a method, an async or a generator function is none.
|
|
2163
|
+
*/ function isConstructor(value) {
|
|
2164
|
+
if (!isConstructible(value)) return false;
|
|
2165
|
+
const prototype = value.prototype;
|
|
2166
|
+
return typeof prototype === 'object' && prototype !== null;
|
|
2167
|
+
}
|
|
1982
2168
|
/** A value as a refusal or a warning names it: a string quoted, `null` and `undefined` as written, else its kind. */ function describeValue(value) {
|
|
1983
2169
|
if (value === null || value === undefined) return String(value);
|
|
1984
2170
|
if (typeof value === 'string') return quoteForLog(value);
|
|
@@ -1987,15 +2173,26 @@ brand(CooldownStoreError, 'CooldownStoreError');
|
|
|
1987
2173
|
}
|
|
1988
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}`;
|
|
1989
2175
|
/**
|
|
1990
|
-
* Sets `key` on `target` as an own, enumerable data property: unlike `target[key] = value`, a key
|
|
1991
|
-
* 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.
|
|
1992
2179
|
*/ function setOwn(target, key, value) {
|
|
1993
|
-
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, {
|
|
1994
2183
|
value,
|
|
1995
2184
|
enumerable: true,
|
|
1996
2185
|
writable: true,
|
|
1997
2186
|
configurable: true
|
|
1998
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);
|
|
1999
2196
|
}
|
|
2000
2197
|
|
|
2001
2198
|
/**
|
|
@@ -2163,37 +2360,48 @@ brand(CooldownStoreError, 'CooldownStoreError');
|
|
|
2163
2360
|
*
|
|
2164
2361
|
* @group Utilities
|
|
2165
2362
|
*/ function getInstallContext(interaction) {
|
|
2166
|
-
const
|
|
2167
|
-
const guildInstalled = owners[discord_js.ApplicationIntegrationType.GuildInstall] !== undefined;
|
|
2168
|
-
const userInstalled = owners[discord_js.ApplicationIntegrationType.UserInstall] !== undefined;
|
|
2363
|
+
const botInstalled = isBotInstalled(interaction);
|
|
2169
2364
|
switch(interaction.context){
|
|
2170
2365
|
case discord_js.InteractionContextType.BotDM:
|
|
2171
2366
|
return {
|
|
2172
2367
|
where: 'bot-dm',
|
|
2173
|
-
botInstalled
|
|
2368
|
+
botInstalled
|
|
2174
2369
|
};
|
|
2175
2370
|
case discord_js.InteractionContextType.PrivateChannel:
|
|
2176
2371
|
return {
|
|
2177
2372
|
where: 'private-channel',
|
|
2178
|
-
botInstalled
|
|
2373
|
+
botInstalled
|
|
2179
2374
|
};
|
|
2180
2375
|
case discord_js.InteractionContextType.Guild:
|
|
2181
|
-
// With no owners reported, the interaction came through the bot, as every interaction did before user installs.
|
|
2182
2376
|
return {
|
|
2183
2377
|
where: 'guild',
|
|
2184
|
-
botInstalled
|
|
2378
|
+
botInstalled
|
|
2185
2379
|
};
|
|
2186
2380
|
default:
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
botInstalled: true
|
|
2191
|
-
} : {
|
|
2192
|
-
where: 'bot-dm',
|
|
2193
|
-
botInstalled: true
|
|
2381
|
+
return {
|
|
2382
|
+
where: interaction.guildId ? 'guild' : 'bot-dm',
|
|
2383
|
+
botInstalled
|
|
2194
2384
|
};
|
|
2195
2385
|
}
|
|
2196
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
|
+
}
|
|
2197
2405
|
|
|
2198
2406
|
const { Ephemeral, SuppressEmbeds, SuppressNotifications, IsComponentsV2 } = discord_js.MessageFlags;
|
|
2199
2407
|
/** The flags each call accepts; Discord rejects or ignores the rest. */ const ALLOWED = {
|
|
@@ -2226,6 +2434,22 @@ const { Ephemeral, SuppressEmbeds, SuppressNotifications, IsComponentsV2 } = dis
|
|
|
2226
2434
|
/** The names of the flags set in a value, for warnings. */ function flagNames(flags) {
|
|
2227
2435
|
return new discord_js.MessageFlagsBitField(flags).toArray();
|
|
2228
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
|
+
}
|
|
2229
2453
|
|
|
2230
2454
|
const CDN_HOSTS = new Set([
|
|
2231
2455
|
'cdn.discordapp.com',
|
|
@@ -2374,8 +2598,12 @@ function rewriteComponent(component, kept) {
|
|
|
2374
2598
|
/** The id MeoCord gives the containers it renders, so a view left behind can be found again. */ const RENDERED_CONTAINER_ID = 0x4d43;
|
|
2375
2599
|
// Shared by both builds of this version, so either answers with the app's presenter
|
|
2376
2600
|
const presenters = shared('clientPresenters', ()=>new WeakMap());
|
|
2377
|
-
/** Sets the presenter for the interactions a client receives. */ function setPresenter(client, presenter) {
|
|
2378
|
-
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);
|
|
2379
2607
|
}
|
|
2380
2608
|
/** The presenter for an interaction's client, or the default one. */ function presenterFor(client) {
|
|
2381
2609
|
return client && presenters.get(client) || defaultPresenter;
|
|
@@ -2770,15 +2998,25 @@ const logger$4 = new Logger('Theme');
|
|
|
2770
2998
|
]).finally(()=>clearTimeout(timer));
|
|
2771
2999
|
}
|
|
2772
3000
|
/**
|
|
2773
|
-
* A result as a layer: copied, then checked; one with problems is left out, with a
|
|
2774
|
-
* `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.
|
|
2775
3003
|
*/ accept(id, result, report) {
|
|
2776
3004
|
if (result === undefined || result === null) return undefined;
|
|
2777
|
-
const
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
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
|
+
}
|
|
2782
3020
|
if (problems.length === 0) {
|
|
2783
3021
|
if (report) this.warned.delete(id);
|
|
2784
3022
|
return layer;
|
|
@@ -2907,13 +3145,14 @@ const logger$4 = new Logger('Theme');
|
|
|
2907
3145
|
const idOf = (value)=>typeof value === 'string' ? value : undefined;
|
|
2908
3146
|
/**
|
|
2909
3147
|
* The server and user a call comes from: an interaction's or a message's server and its user or author; a
|
|
2910
|
-
* reaction's message's server and the user who reacted, which a reaction handler gets second
|
|
2911
|
-
* 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.
|
|
2912
3150
|
*/ function themeTargets(args) {
|
|
2913
3151
|
const [first, second] = args;
|
|
2914
3152
|
if (!first || typeof first !== 'object') return {};
|
|
2915
3153
|
const guildId = idOf(first.guildId) ?? idOf(first.guild?.id) ?? idOf(first.message?.guildId);
|
|
2916
|
-
|
|
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);
|
|
2917
3156
|
return {
|
|
2918
3157
|
guildId,
|
|
2919
3158
|
userId
|
|
@@ -2940,7 +3179,8 @@ const idOf = (value)=>typeof value === 'string' ? value : undefined;
|
|
|
2940
3179
|
user
|
|
2941
3180
|
];
|
|
2942
3181
|
}
|
|
2943
|
-
|
|
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());
|
|
2944
3184
|
/**
|
|
2945
3185
|
* An app's cache of the themes `@MeoCord({ themeFor })` looked up, by server and by user.
|
|
2946
3186
|
*
|
|
@@ -2985,6 +3225,16 @@ const stores = new WeakMap();
|
|
|
2985
3225
|
else stores.delete(themeCache);
|
|
2986
3226
|
}
|
|
2987
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
|
+
}
|
|
2988
3238
|
const ALREADY_ACKNOWLEDGED = 40060;
|
|
2989
3239
|
/** Discord's code for an interaction it no longer knows, such as one past its three seconds unacknowledged. */ const UNKNOWN_INTERACTION = 10062;
|
|
2990
3240
|
const TOKEN_EXPIRED = new Set([
|
|
@@ -3082,6 +3332,7 @@ const messageLocks = shared('messageLocks', ()=>new Map());
|
|
|
3082
3332
|
function toJson(value) {
|
|
3083
3333
|
return typeof value?.toJSON === 'function' ? value.toJSON() : value;
|
|
3084
3334
|
}
|
|
3335
|
+
var _location = /*#__PURE__*/ _class_private_field_loose_key("_location");
|
|
3085
3336
|
/** The response state behind `respond()`, with what `@Defer` and the testing helpers use besides. */ class InteractionResponse {
|
|
3086
3337
|
get state() {
|
|
3087
3338
|
this.sync();
|
|
@@ -3120,7 +3371,7 @@ function toJson(value) {
|
|
|
3120
3371
|
flagsFor(step, requested, v2 = this.v2) {
|
|
3121
3372
|
const { flags, dropped } = resolveFlags(step, requested, v2);
|
|
3122
3373
|
if (dropped && development()) {
|
|
3123
|
-
logger$3.warn(
|
|
3374
|
+
logger$3.warn(droppedFlagsWarning(step, dropped));
|
|
3124
3375
|
}
|
|
3125
3376
|
return flags;
|
|
3126
3377
|
}
|
|
@@ -3383,15 +3634,15 @@ function toJson(value) {
|
|
|
3383
3634
|
});
|
|
3384
3635
|
}
|
|
3385
3636
|
/**
|
|
3386
|
-
*
|
|
3387
|
-
*
|
|
3388
|
-
* nothing. Never throws.
|
|
3637
|
+
* Ends a call a guard denied silently. A component still unanswered is acknowledged invisibly, with or without
|
|
3638
|
+
* `@Defer`, since Discord tells the user an unanswered call failed; so is a command `@Defer`'s 'auto' timer was
|
|
3639
|
+
* waiting on, whose deferred reply is then deleted, as `@Defer`'s is while nothing was sent into it. Never throws.
|
|
3389
3640
|
*/ abandon() {
|
|
3390
3641
|
const waiting = this.timer !== undefined;
|
|
3391
3642
|
this.cancelScheduled();
|
|
3392
3643
|
return this.queued(async ()=>{
|
|
3393
|
-
//
|
|
3394
|
-
if (waiting) await this.acknowledgeUnanswered({
|
|
3644
|
+
// A command's acknowledgement shows "thinking…", so one is made only for the timer that would have made it
|
|
3645
|
+
if (waiting || !answersWithOwnMessage(this.interaction)) await this.acknowledgeUnanswered({
|
|
3395
3646
|
ephemeral: true
|
|
3396
3647
|
});
|
|
3397
3648
|
this.sync();
|
|
@@ -3644,7 +3895,7 @@ function toJson(value) {
|
|
|
3644
3895
|
} catch (error) {
|
|
3645
3896
|
const message = this.message;
|
|
3646
3897
|
const expired = Date.now() - this.interaction.createdTimestamp >= TOKEN_EXPIRED_AFTER_MS;
|
|
3647
|
-
if (!TOKEN_EXPIRED.has(errorCode(error)) || !expired || !this.
|
|
3898
|
+
if (!TOKEN_EXPIRED.has(errorCode(error)) || !expired || !isBotInstalled(this.interaction) || !message) throw error;
|
|
3648
3899
|
this.lastMessage = await this.call('message.edit', sent, ()=>message.edit(sent));
|
|
3649
3900
|
}
|
|
3650
3901
|
this.phase = 'replied';
|
|
@@ -3956,6 +4207,10 @@ function toJson(value) {
|
|
|
3956
4207
|
} : body;
|
|
3957
4208
|
}
|
|
3958
4209
|
constructor(interaction){
|
|
4210
|
+
Object.defineProperty(this, _location, {
|
|
4211
|
+
writable: true,
|
|
4212
|
+
value: void 0
|
|
4213
|
+
});
|
|
3959
4214
|
this.interaction = interaction;
|
|
3960
4215
|
this.phase = 'unanswered';
|
|
3961
4216
|
/** The answer steps asked for, run one after another; `pending` counts those not yet settled. */ this.queue = Promise.resolve();
|
|
@@ -3973,7 +4228,14 @@ function toJson(value) {
|
|
|
3973
4228
|
this.suppressNotifications = false;
|
|
3974
4229
|
// Set by @Defer's first step, so an error can name what acknowledged the interaction
|
|
3975
4230
|
this.underDefer = false;
|
|
3976
|
-
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
|
+
});
|
|
3977
4239
|
const message = 'message' in interaction ? interaction.message : undefined;
|
|
3978
4240
|
this.v2 = Boolean(message?.flags?.has(discord_js.MessageFlags.IsComponentsV2));
|
|
3979
4241
|
}
|
|
@@ -4055,8 +4317,10 @@ const states = shared('responseStates', ()=>new WeakMap());
|
|
|
4055
4317
|
/**
|
|
4056
4318
|
* The response state of the interaction being handled, the same one `respond(interaction)` returns,
|
|
4057
4319
|
* or `undefined` for anything that cannot be answered: a message, a reaction, an event or
|
|
4058
|
-
* 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.
|
|
4059
4322
|
*/ get response() {
|
|
4323
|
+
if (this.getType() === 'event') return undefined;
|
|
4060
4324
|
const interaction = this.getInteraction();
|
|
4061
4325
|
return interaction?.isRepliable() ? respond(interaction) : undefined;
|
|
4062
4326
|
}
|
|
@@ -4193,17 +4457,30 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4193
4457
|
}
|
|
4194
4458
|
}
|
|
4195
4459
|
|
|
4196
|
-
/**
|
|
4197
|
-
* How many calls a cooldown allows, and in how long a window.
|
|
4198
|
-
*
|
|
4199
|
-
* @group Types
|
|
4200
|
-
*/ /** `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) {
|
|
4201
4461
|
return Object.defineProperty(verdict, 'release', {
|
|
4202
4462
|
value: release,
|
|
4203
4463
|
enumerable: false,
|
|
4204
4464
|
configurable: true
|
|
4205
4465
|
});
|
|
4206
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
|
+
}
|
|
4207
4484
|
/**
|
|
4208
4485
|
* Where `@Cooldown` counts calls.
|
|
4209
4486
|
*
|
|
@@ -4254,10 +4531,11 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4254
4531
|
*/ async consumeMany(entries) {
|
|
4255
4532
|
for (const [index, { key, limit }] of entries.entries()){
|
|
4256
4533
|
const verdict = await this.consume(key, limit);
|
|
4257
|
-
|
|
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({
|
|
4258
4536
|
...verdict,
|
|
4259
4537
|
blocked: index
|
|
4260
|
-
};
|
|
4538
|
+
}, verdict.retryTimestamp);
|
|
4261
4539
|
}
|
|
4262
4540
|
return {
|
|
4263
4541
|
allowed: true,
|
|
@@ -4283,6 +4561,18 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4283
4561
|
});
|
|
4284
4562
|
}
|
|
4285
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;
|
|
4286
4576
|
/**
|
|
4287
4577
|
* Of several refusals, the one a caller waits on: the call runs only once every entry allows it, so the
|
|
4288
4578
|
* longest wait. Undefined when nothing refused.
|
|
@@ -4373,16 +4663,12 @@ const logger$2 = new Logger('ExecutionContext');
|
|
|
4373
4663
|
* @see {@link CooldownStore}
|
|
4374
4664
|
*/ class MemoryCooldownStore extends CooldownStore {
|
|
4375
4665
|
async consume(key, limit) {
|
|
4376
|
-
|
|
4666
|
+
return singleVerdict(await this.consumeMany([
|
|
4377
4667
|
{
|
|
4378
4668
|
key,
|
|
4379
4669
|
limit
|
|
4380
4670
|
}
|
|
4381
|
-
]);
|
|
4382
|
-
return {
|
|
4383
|
-
allowed,
|
|
4384
|
-
retryAfterMs
|
|
4385
|
-
};
|
|
4671
|
+
]));
|
|
4386
4672
|
}
|
|
4387
4673
|
/**
|
|
4388
4674
|
* Checks every entry, and records the call against all of them only if all allow it.
|
|
@@ -4605,16 +4891,12 @@ const DEFAULT_PREFIX = 'meocord:cooldown:';
|
|
|
4605
4891
|
* @param limit - The calls allowed, and the window they are counted over.
|
|
4606
4892
|
* @returns Whether this call was recorded, and if not, how long until one can be.
|
|
4607
4893
|
*/ async consume(key, limit) {
|
|
4608
|
-
|
|
4894
|
+
return singleVerdict(await this.consumeMany([
|
|
4609
4895
|
{
|
|
4610
4896
|
key,
|
|
4611
4897
|
limit
|
|
4612
4898
|
}
|
|
4613
|
-
]);
|
|
4614
|
-
return {
|
|
4615
|
-
allowed,
|
|
4616
|
-
retryAfterMs
|
|
4617
|
-
};
|
|
4899
|
+
]));
|
|
4618
4900
|
}
|
|
4619
4901
|
/**
|
|
4620
4902
|
* Records a call against every entry if all allow it, as one script: one round trip, however many
|
|
@@ -4803,34 +5085,70 @@ const UNIT_MS = lookupTable({
|
|
|
4803
5085
|
w: 604800000
|
|
4804
5086
|
});
|
|
4805
5087
|
/** A number written in full, `whole` for an integer: `undefined` for anything else. */ function number(word, whole) {
|
|
4806
|
-
|
|
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;
|
|
4807
5090
|
const value = Number(word);
|
|
4808
5091
|
return Number.isFinite(value) && (!whole || Number.isSafeInteger(value)) ? value : undefined;
|
|
4809
5092
|
}
|
|
4810
5093
|
/** `yes`, `no`, `true`, `false`, `on` or `off`, in any case, as a boolean. */ const bool = (word)=>BOOLEANS[word.toLowerCase()];
|
|
4811
|
-
/**
|
|
4812
|
-
|
|
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);
|
|
4813
5100
|
if (!parts || parts.join('') !== word.toLowerCase()) return undefined;
|
|
4814
|
-
|
|
5101
|
+
const total = parts.reduce((sum, part)=>{
|
|
4815
5102
|
const [, amount, unit] = /^(\d+(?:\.\d+)?)(ms|s|m|h|d|w)$/.exec(part);
|
|
4816
|
-
return
|
|
5103
|
+
return sum + Number(amount) * UNIT_MS[unit];
|
|
4817
5104
|
}, 0);
|
|
5105
|
+
// Too long to add up, as number() refuses a value past the largest number
|
|
5106
|
+
return Number.isFinite(total) ? total : undefined;
|
|
4818
5107
|
}
|
|
4819
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}}$`);
|
|
4820
5114
|
/**
|
|
4821
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
|
|
4822
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
|
|
4823
5117
|
* integer.
|
|
4824
|
-
*/ const snowflake = (word)
|
|
4825
|
-
/** 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;
|
|
4826
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;
|
|
4827
|
-
/** The
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
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
|
+
}
|
|
4834
5152
|
});
|
|
4835
5153
|
/**
|
|
4836
5154
|
* Whether `type` can type a customId segment: `string`, `int`, `number`, `bool`, `snowflake`, `uuid`, or words to
|
|
@@ -4839,7 +5157,12 @@ const UNIT_MS = lookupTable({
|
|
|
4839
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) {
|
|
4840
5158
|
const choices = choicesOf(type);
|
|
4841
5159
|
if (choices) return choices.includes(segment) ? segment : undefined;
|
|
4842
|
-
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;
|
|
4843
5166
|
}
|
|
4844
5167
|
|
|
4845
5168
|
/**
|
|
@@ -4889,6 +5212,12 @@ const UNIT_MS = lookupTable({
|
|
|
4889
5212
|
return `; it was given ${describeValue(value)}`;
|
|
4890
5213
|
}
|
|
4891
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
|
+
}
|
|
4892
5221
|
/**
|
|
4893
5222
|
* The class's own handler list, started from a copy of the inherited one, so a subclass's
|
|
4894
5223
|
* handlers never land in its base class's metadata.
|
|
@@ -4914,6 +5243,10 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
4914
5243
|
method,
|
|
4915
5244
|
label: `autocomplete of ${optionName === undefined ? 'every option' : `"${optionName}"`} in "${commandPath}"`
|
|
4916
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
|
+
});
|
|
4917
5250
|
/** Records a route a class's own decorator declares for a method, which the startup check reads. */ function declareRoute(target, declared) {
|
|
4918
5251
|
const routes = Reflect.getOwnMetadata(META.declaredRoutes, target) ?? [];
|
|
4919
5252
|
routes.push(declared);
|
|
@@ -4941,13 +5274,22 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
4941
5274
|
else handlers[index] = entry;
|
|
4942
5275
|
Reflect.defineMetadata(key, handlers, target);
|
|
4943
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
|
+
}
|
|
4944
5281
|
/**
|
|
4945
5282
|
* Leaves each method the class's own decorators declare only those routes, of every kind, dropping the ones it inherits
|
|
4946
5283
|
* for it: what `@Controller({ inheritedRoutes: 'replace' })` does once the class's method decorators have run.
|
|
4947
5284
|
*/ function dropInheritedRoutes(prototype) {
|
|
4948
|
-
const redeclared = new Set(getDeclaredRoutes(prototype).map((route)=>route.method));
|
|
4949
|
-
if (redeclared.size === 0) return;
|
|
4950
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;
|
|
4951
5293
|
// An entry the class takes from its base, for a method it re-declares
|
|
4952
5294
|
const inheritedFor = (inherited, method)=>(entry)=>inherited.includes(entry) && redeclared.has(method(entry));
|
|
4953
5295
|
const commands = ownCommandMap(prototype);
|
|
@@ -4970,6 +5312,10 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
4970
5312
|
[
|
|
4971
5313
|
META.autocompleteHandlers,
|
|
4972
5314
|
(entry)=>entry.methodName
|
|
5315
|
+
],
|
|
5316
|
+
[
|
|
5317
|
+
META.eventHandlers,
|
|
5318
|
+
(entry)=>entry.method
|
|
4973
5319
|
]
|
|
4974
5320
|
];
|
|
4975
5321
|
for (const [key, method] of lists){
|
|
@@ -4996,13 +5342,14 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
4996
5342
|
function MessageHandler(pattern, options = {}) {
|
|
4997
5343
|
return declaring(function(target, propertyKey) {
|
|
4998
5344
|
refuseOnClass('@MessageHandler', target, propertyKey);
|
|
4999
|
-
// An empty pattern means every message, as no pattern does
|
|
5000
|
-
|
|
5001
|
-
|
|
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()');
|
|
5002
5349
|
}
|
|
5003
5350
|
const method = propertyKey.toString();
|
|
5004
5351
|
const declared = {
|
|
5005
|
-
pattern:
|
|
5352
|
+
pattern: blank ? undefined : pattern,
|
|
5006
5353
|
method,
|
|
5007
5354
|
options
|
|
5008
5355
|
};
|
|
@@ -5124,13 +5471,14 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5124
5471
|
params: params.length,
|
|
5125
5472
|
typed: Object.values(types).reduce((sum, type)=>sum + (choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1), 0)
|
|
5126
5473
|
});
|
|
5127
|
-
// 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);
|
|
5128
5476
|
return {
|
|
5129
5477
|
regex,
|
|
5130
5478
|
params,
|
|
5131
5479
|
types: lookupTable(types),
|
|
5132
5480
|
specificity,
|
|
5133
|
-
shortest
|
|
5481
|
+
shortest
|
|
5134
5482
|
};
|
|
5135
5483
|
}
|
|
5136
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) {
|
|
@@ -5203,13 +5551,17 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5203
5551
|
refuseOnClass('@Command', target, propertyKey);
|
|
5204
5552
|
const originalMethod = _descriptor.value;
|
|
5205
5553
|
if (!originalMethod) {
|
|
5206
|
-
throw refuse(new Error(`${target
|
|
5554
|
+
throw refuse(new Error(`${decoratedName(target, propertyKey)}: @Command is on something with no implementation.`));
|
|
5207
5555
|
}
|
|
5208
5556
|
// Wrap original method for interaction type validation
|
|
5209
5557
|
const declaredAs = typeof builderOrType === 'function' ? builderOrType.name : `CommandType.${builderOrType}`;
|
|
5210
5558
|
const checked = function(interaction, params) {
|
|
5211
|
-
|
|
5212
|
-
|
|
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)}.`);
|
|
5213
5565
|
}
|
|
5214
5566
|
return originalMethod.apply(this, [
|
|
5215
5567
|
interaction,
|
|
@@ -5227,7 +5579,7 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5227
5579
|
let guilds;
|
|
5228
5580
|
// Determine command type and builder
|
|
5229
5581
|
if (typeof builderOrType === 'function') {
|
|
5230
|
-
const where =
|
|
5582
|
+
const where = decoratedName(target, propertyKey);
|
|
5231
5583
|
// A subcommand is part of its command's builder, which belongs on the command's own name
|
|
5232
5584
|
const subcommandPath = Reflect.getMetadata(META.commandType, builderOrType) === enum_index.CommandType.SLASH && commandName.includes(' ');
|
|
5233
5585
|
const command = commandName.split(' ')[0];
|
|
@@ -5274,12 +5626,12 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5274
5626
|
if (isCustomIdRouted(commandType)) {
|
|
5275
5627
|
// A route warned as it was made
|
|
5276
5628
|
const braces = typeof name === 'string' ? literalBraces(commandName) : undefined;
|
|
5277
|
-
if (braces) logger.warn(`${target
|
|
5629
|
+
if (braces) logger.warn(`${decoratedName(target, propertyKey)}: in the pattern "${commandName}", ${braces}`);
|
|
5278
5630
|
let pattern;
|
|
5279
5631
|
try {
|
|
5280
5632
|
pattern = createRegexFromPattern(commandName);
|
|
5281
5633
|
} catch (error) {
|
|
5282
|
-
throw refuse(new Error(`${target
|
|
5634
|
+
throw refuse(new Error(`${decoratedName(target, propertyKey)}: ${error.message}`, {
|
|
5283
5635
|
cause: error
|
|
5284
5636
|
}));
|
|
5285
5637
|
}
|
|
@@ -5289,7 +5641,7 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5289
5641
|
// Warned rather than refused, so a bot that carries such a handler still starts
|
|
5290
5642
|
if (pattern.shortest === 0 || pattern.shortest > MAX_CUSTOM_ID_LENGTH) {
|
|
5291
5643
|
const length = pattern.shortest === 0 ? 'the pattern is empty' : `its shortest is ${pattern.shortest} characters`;
|
|
5292
|
-
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.`);
|
|
5293
5645
|
}
|
|
5294
5646
|
}
|
|
5295
5647
|
const declared = {
|
|
@@ -5341,7 +5693,7 @@ const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
|
5341
5693
|
const registered = built?.type;
|
|
5342
5694
|
if (!declared || registered === undefined || declared.kind === registered) return;
|
|
5343
5695
|
const kind = registered === discord_js.ApplicationCommandType.User ? 'user' : 'message';
|
|
5344
|
-
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.`));
|
|
5345
5697
|
}
|
|
5346
5698
|
/**
|
|
5347
5699
|
* Retrieves the command map for a given controller.
|
|
@@ -5502,11 +5854,13 @@ exports.ReactionHandler = ReactionHandler;
|
|
|
5502
5854
|
exports.RedisCooldownStore = RedisCooldownStore;
|
|
5503
5855
|
exports.SetMetadata = SetMetadata;
|
|
5504
5856
|
exports.TEXT_LIMITS = TEXT_LIMITS;
|
|
5857
|
+
exports.Theme = Theme;
|
|
5505
5858
|
exports.ThemeCache = ThemeCache;
|
|
5506
5859
|
exports.Translator = Translator;
|
|
5507
5860
|
exports.UnroutedExecutionContext = UnroutedExecutionContext;
|
|
5508
5861
|
exports.UserError = UserError;
|
|
5509
5862
|
exports.ValidationError = ValidationError;
|
|
5863
|
+
exports.addOwnHandler = addOwnHandler;
|
|
5510
5864
|
exports.allDeclaredErrors = allDeclaredErrors;
|
|
5511
5865
|
exports.ambientThemeVersion = ambientThemeVersion;
|
|
5512
5866
|
exports.applyDecorators = applyDecorators;
|
|
@@ -5520,6 +5874,7 @@ exports.bundleEntry = bundleEntry;
|
|
|
5520
5874
|
exports.callOrder = callOrder;
|
|
5521
5875
|
exports.choicesOf = choicesOf;
|
|
5522
5876
|
exports.claimAmbientTheme = claimAmbientTheme;
|
|
5877
|
+
exports.clientPresenter = clientPresenter;
|
|
5523
5878
|
exports.collectStartupErrors = collectStartupErrors;
|
|
5524
5879
|
exports.commandsNamed = commandsNamed;
|
|
5525
5880
|
exports.compiledConfigMessage = compiledConfigMessage;
|
|
@@ -5541,6 +5896,7 @@ exports.describeValue = describeValue;
|
|
|
5541
5896
|
exports.dropInheritedRoutes = dropInheritedRoutes;
|
|
5542
5897
|
exports.duration = duration;
|
|
5543
5898
|
exports.escapeForLog = escapeForLog;
|
|
5899
|
+
exports.eventRoute = eventRoute;
|
|
5544
5900
|
exports.existingResponse = existingResponse;
|
|
5545
5901
|
exports.fitText = fitText;
|
|
5546
5902
|
exports.focusedOptionName = focusedOptionName;
|
|
@@ -5553,15 +5909,17 @@ exports.getInstallContext = getInstallContext;
|
|
|
5553
5909
|
exports.getMessageHandlers = getMessageHandlers;
|
|
5554
5910
|
exports.getReactionHandlers = getReactionHandlers;
|
|
5555
5911
|
exports.handOffRefusal = handOffRefusal;
|
|
5556
|
-
exports.hasAmbientTheme = hasAmbientTheme;
|
|
5557
5912
|
exports.hasCustomId = hasCustomId;
|
|
5558
5913
|
exports.hideInLogs = hideInLogs;
|
|
5559
5914
|
exports.inferContextType = inferContextType;
|
|
5560
5915
|
exports.interactionLocale = interactionLocale;
|
|
5561
5916
|
exports.isBuiltApplication = isBuiltApplication;
|
|
5917
|
+
exports.isConstructible = isConstructible;
|
|
5562
5918
|
exports.isConstructor = isConstructor;
|
|
5563
5919
|
exports.isCustomIdRouted = isCustomIdRouted;
|
|
5920
|
+
exports.isDefaultBatchMethod = isDefaultBatchMethod;
|
|
5564
5921
|
exports.isExplainedError = isExplainedError;
|
|
5922
|
+
exports.isPluralShape = isPluralShape;
|
|
5565
5923
|
exports.isRefusal = isRefusal;
|
|
5566
5924
|
exports.isTooLarge = isTooLarge;
|
|
5567
5925
|
exports.isUserOutcome = isUserOutcome;
|
|
@@ -5581,7 +5939,9 @@ exports.mergeTheme = mergeTheme;
|
|
|
5581
5939
|
exports.messageLocale = messageLocale;
|
|
5582
5940
|
exports.missingTranslatorError = missingTranslatorError;
|
|
5583
5941
|
exports.number = number;
|
|
5584
|
-
exports.
|
|
5942
|
+
exports.ownEventHandlers = ownEventHandlers;
|
|
5943
|
+
exports.ownValue = ownValue;
|
|
5944
|
+
exports.ownerName = ownerName;
|
|
5585
5945
|
exports.ownsAmbientTheme = ownsAmbientTheme;
|
|
5586
5946
|
exports.paramNarrowness = paramNarrowness;
|
|
5587
5947
|
exports.parseSegment = parseSegment;
|
|
@@ -5605,33 +5965,34 @@ exports.responseOf = responseOf;
|
|
|
5605
5965
|
exports.routeSpecificity = routeSpecificity;
|
|
5606
5966
|
exports.runInThemeScope = runInThemeScope;
|
|
5607
5967
|
exports.runWithTheme = runWithTheme;
|
|
5608
|
-
exports.setLegacyThemeLayer = setLegacyThemeLayer;
|
|
5609
5968
|
exports.setOwn = setOwn;
|
|
5610
5969
|
exports.setPresenter = setPresenter;
|
|
5611
5970
|
exports.setStartupErrorsMode = setStartupErrorsMode;
|
|
5612
5971
|
exports.setThemeOutsideCalls = setThemeOutsideCalls;
|
|
5972
|
+
exports.shadowInherited = shadowInherited;
|
|
5613
5973
|
exports.shared = shared;
|
|
5614
5974
|
exports.sharedCustomId = sharedCustomId;
|
|
5615
5975
|
exports.sharedName = sharedName;
|
|
5976
|
+
exports.singleVerdict = singleVerdict;
|
|
5616
5977
|
exports.stampCall = stampCall;
|
|
5617
5978
|
exports.startupError = startupError;
|
|
5979
|
+
exports.startupErrorsOf = startupErrorsOf;
|
|
5618
5980
|
exports.stopOnStartupErrors = stopOnStartupErrors;
|
|
5619
5981
|
exports.textOfIssue = textOfIssue;
|
|
5620
5982
|
exports.textRenderer = textRenderer;
|
|
5621
5983
|
exports.themeForInteraction = themeForInteraction;
|
|
5622
5984
|
exports.themeForProblem = themeForProblem;
|
|
5623
5985
|
exports.themeLayersVersion = themeLayersVersion;
|
|
5624
|
-
exports.themeProblems = themeProblems;
|
|
5625
5986
|
exports.themeResolverClass = themeResolverClass;
|
|
5626
5987
|
exports.translatorOfClient = translatorOfClient;
|
|
5627
5988
|
exports.usageHeading = usageHeading;
|
|
5628
5989
|
exports.usageIssue = usageIssue;
|
|
5629
5990
|
exports.useTheme = useTheme;
|
|
5630
5991
|
exports.userWords = userWords;
|
|
5631
|
-
exports.warnDeprecated = warnDeprecated;
|
|
5632
5992
|
exports.warnDeprecatedBehaviour = warnDeprecatedBehaviour;
|
|
5633
5993
|
exports.warnOnce = warnOnce;
|
|
5634
5994
|
exports.warnPlaceholder = warnPlaceholder;
|
|
5995
|
+
exports.withArticle = withArticle;
|
|
5635
5996
|
exports.withRelease = withRelease;
|
|
5636
5997
|
exports.withSendableFiles = withSendableFiles;
|
|
5637
5998
|
exports.withoutFiles = withoutFiles;
|