meocord 4.1.2 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +362 -0
- package/THIRD_PARTY_NOTICES.md +14 -13
- package/dist/cjs/_shared/{controller.decorator-BywwkU9O.cjs → controller.decorator-XKMlcUZz.cjs} +669 -185
- package/dist/cjs/_shared/{providers-BMMLp2SN.cjs → providers-BZgJyfZS.cjs} +218 -75
- package/dist/cjs/_shared/{sharded-cooldown-store-B65iaiAy.cjs → sharded-cooldown-store-CsJJyCZd.cjs} +10 -7
- package/dist/cjs/_shared/{startup-roots-lFc69AxC.cjs → startup-roots-CgfPQrNl.cjs} +374 -132
- package/dist/cjs/_shared/{translate-error-BnM8FbSH.cjs → translate-error-BUAR1WYG.cjs} +21 -7
- package/dist/cjs/common/index.cjs +4 -4
- package/dist/cjs/core/index.cjs +365 -310
- package/dist/cjs/decorator/index.cjs +85 -61
- package/dist/cjs/testing/index.cjs +2236 -496
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/README.md.template +3 -1
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/controllers/button/sample.button.controller.ts.template +2 -2
- package/dist/esm/bin/app-template/src/guards/owner.guard.spec.ts.template +8 -4
- package/dist/esm/bin/app-template/src/guards/owner.guard.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.config.ts.template +1 -1
- package/dist/esm/bin/app-template/vitest.setup.ts.template +7 -3
- package/dist/esm/bin/generator.js +4 -5
- package/dist/esm/bin/helper/filter-generator.helper.js +4 -5
- package/dist/esm/bin/helper/guard-generator.helper.js +4 -5
- package/dist/esm/bin/helper/interceptor-generator.helper.js +4 -5
- package/dist/esm/bin/helper/observer-generator.helper.js +4 -5
- package/dist/esm/bin/helper/pipe-generator.helper.js +4 -5
- package/dist/esm/bin/helper/service-generator.helper.js +4 -5
- package/dist/esm/bin/meocord.js +84 -25
- package/dist/esm/build/load-config.pre-entry.js +17 -4
- package/dist/esm/build/rsbuild-config.js +16 -12
- package/dist/esm/build/typed-externals.js +36 -0
- package/dist/esm/common/cooldown-store.js +10 -2
- package/dist/esm/common/decorator.js +6 -6
- package/dist/esm/common/deprecation.js +2 -2
- package/dist/esm/common/errors.js +27 -2
- package/dist/esm/common/execution-context.js +1 -1
- package/dist/esm/common/meocord-text.js +11 -8
- package/dist/esm/common/metadata.js +1 -1
- package/dist/esm/common/response/call-order.js +9 -4
- package/dist/esm/common/response/presenter.js +33 -6
- package/dist/esm/common/response/response-state.js +84 -10
- package/dist/esm/common/route.js +21 -5
- package/dist/esm/common/sharded-cooldown-store.js +1 -1
- package/dist/esm/common/translate-error.js +1 -1
- package/dist/esm/common/translator.js +37 -6
- package/dist/esm/core/command-conflicts.js +34 -34
- package/dist/esm/core/command-registration.js +93 -24
- package/dist/esm/core/command-routes.js +2 -2
- package/dist/esm/core/component-routes.js +74 -31
- package/dist/esm/core/cooldown-runner.js +29 -8
- package/dist/esm/core/dispatcher.js +51 -7
- package/dist/esm/core/fallback.js +20 -11
- package/dist/esm/core/filter-runner.js +2 -2
- package/dist/esm/core/guard-runner.js +16 -3
- package/dist/esm/core/handler-input.js +4 -3
- package/dist/esm/core/handler-pipeline.js +27 -14
- package/dist/esm/core/handler-registry.js +1 -1
- package/dist/esm/core/interceptor-runner.js +2 -2
- package/dist/esm/core/lifecycle-hooks.js +134 -47
- package/dist/esm/core/meocord-factory.js +241 -179
- package/dist/esm/core/meocord.app.js +116 -75
- package/dist/esm/core/message-routes.js +4 -3
- package/dist/esm/core/observer-runner.js +4 -3
- package/dist/esm/core/providers.js +45 -23
- package/dist/esm/core/route-specificity.js +4 -4
- package/dist/esm/core/running-calls.js +43 -0
- package/dist/esm/core/scalar-types.js +15 -3
- package/dist/esm/core/shard-context.js +6 -7
- package/dist/esm/core/shard-manager.js +16 -10
- package/dist/esm/core/startup-roots.js +23 -4
- package/dist/esm/core/theme-resolvers.js +4 -1
- package/dist/esm/core/theme-runtime.js +2 -2
- package/dist/esm/core/theme-scope.js +3 -1
- package/dist/esm/core/theme-validation.js +17 -10
- package/dist/esm/decorator/app.decorator.js +12 -7
- package/dist/esm/decorator/command-builder.decorator.js +4 -4
- package/dist/esm/decorator/controller-class.decorator.js +9 -4
- package/dist/esm/decorator/controller.decorator.js +162 -83
- package/dist/esm/decorator/cooldown.decorator.js +20 -3
- package/dist/esm/decorator/defer.decorator.js +4 -4
- package/dist/esm/decorator/event.decorator.js +5 -5
- package/dist/esm/decorator/filter.decorator.js +7 -7
- package/dist/esm/decorator/guard.decorator.js +11 -10
- package/dist/esm/decorator/inject.decorator.js +4 -4
- package/dist/esm/decorator/interceptor.decorator.js +7 -7
- package/dist/esm/decorator/observer.decorator.js +4 -4
- package/dist/esm/decorator/service.decorator.js +4 -4
- package/dist/esm/decorator/theme.decorator.js +3 -3
- package/dist/esm/decorator/validation.decorator.js +10 -10
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +27 -11
- package/dist/esm/testing/cooldown-store-suite.js +85 -15
- package/dist/esm/testing/discord-defaults.js +249 -21
- package/dist/esm/testing/index.js +4 -2
- package/dist/esm/testing/inspect-handler.js +20 -3
- package/dist/esm/testing/meocord-testing-module.js +352 -205
- package/dist/esm/testing/mock-fn.js +173 -22
- package/dist/esm/testing/mock-interaction.js +1214 -209
- package/dist/esm/testing/response.js +1 -1
- package/dist/esm/testing/routing.js +39 -14
- package/dist/esm/testing/snowflake.js +9 -2
- package/dist/esm/testing/startup-errors.js +26 -0
- package/dist/esm/testing/strict-mocks.js +65 -0
- package/dist/esm/testing/theme.js +1 -1
- package/dist/esm/util/bundle-entry.util.js +2 -1
- package/dist/esm/util/common.util.js +47 -19
- package/dist/esm/util/dev-runner.util.js +8 -8
- package/dist/esm/util/generator-cli.util.js +4 -5
- package/dist/esm/util/interaction.util.js +3 -3
- package/dist/esm/util/meocord-config-loader.util.js +10 -2
- package/dist/esm/util/meocord-config-validation.util.js +4 -5
- package/dist/esm/util/meocord-source-config.util.js +65 -20
- package/dist/esm/util/metadata-keys.js +1 -0
- package/dist/esm/util/method-wrapper.util.js +16 -0
- package/dist/esm/util/platform.util.js +10 -10
- package/dist/esm/util/refusal.util.js +148 -8
- package/dist/esm/util/shard-process.util.js +4 -1
- package/dist/esm/util/sharding-mode.util.js +5 -7
- package/dist/esm/util/shared-state.util.js +21 -0
- package/dist/esm/util/tsconfig-paths.util.js +29 -0
- package/dist/esm/util/tsconfig.util.js +10 -10
- package/dist/esm/util/value.util.js +12 -1
- package/dist/types/common/index.d.cts +8 -8
- package/dist/types/common/index.d.ts +8 -8
- package/dist/types/core/index.d.cts +10 -7
- package/dist/types/core/index.d.ts +10 -7
- package/dist/types/decorator/index.d.cts +31 -29
- package/dist/types/decorator/index.d.ts +31 -29
- package/dist/types/{index-D7n0FBMm.d.ts → index-DKJclIi-.d.ts} +107 -36
- package/dist/types/{index-DEP0NYrU.d.cts → index-Dlf0ZZtS.d.cts} +107 -36
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-C-aMI7Sl.d.cts → route-Bh1U13yy.d.cts} +8 -4
- package/dist/types/{route-C-aMI7Sl.d.ts → route-Bh1U13yy.d.ts} +8 -4
- package/dist/types/testing/index.d.cts +406 -70
- package/dist/types/testing/index.d.ts +406 -70
- package/dist/types/{theme-resolvers-COHdX5g6.d.cts → theme-resolvers-CyEz9pnv.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DedxyscT.d.ts → theme-resolvers-DA204O-q.d.ts} +1 -1
- package/dist/types/{translator-J6tIUfdp.d.ts → translator-Bg7hwxDF.d.cts} +21 -8
- package/dist/types/{translator-B4mc1lzJ.d.cts → translator-CeP4bCzp.d.ts} +21 -8
- package/package.json +4 -3
|
@@ -2,12 +2,13 @@ import 'reflect-metadata';
|
|
|
2
2
|
import { UserContextMenuCommandInteraction, ApplicationCommandType, MessageContextMenuCommandInteraction } from 'discord.js';
|
|
3
3
|
import { CommandType } from '../enum/controller.enum.js';
|
|
4
4
|
import { MetadataKey } from '../enum/metadata-key.enum.js';
|
|
5
|
-
import { matchesCommandType, interactionClassName
|
|
5
|
+
import { isCustomIdRouted, matchesCommandType, interactionClassName } from '../util/interaction.util.js';
|
|
6
|
+
import { wrapsMethod } from '../util/method-wrapper.util.js';
|
|
6
7
|
import { warnDeprecatedBehaviour } from '../common/deprecation.js';
|
|
7
8
|
import { Logger } from '../common/logger.js';
|
|
8
9
|
import { routeSpecificity } from '../core/route-specificity.js';
|
|
9
|
-
import { isSegmentType, choicesOf,
|
|
10
|
-
import {
|
|
10
|
+
import { lookupTable, isSegmentType, choicesOf, parseSegment } from '../core/scalar-types.js';
|
|
11
|
+
import { declaring, refuse, refuseOnClass } from '../util/refusal.util.js';
|
|
11
12
|
import { withArticle, describeValue } from '../util/value.util.js';
|
|
12
13
|
import { META } from '../util/metadata-keys.js';
|
|
13
14
|
|
|
@@ -69,19 +70,60 @@ const autocompleteRoute = (method, commandPath, optionName)=>({
|
|
|
69
70
|
else handlers[index] = entry;
|
|
70
71
|
Reflect.defineMetadata(key, handlers, target);
|
|
71
72
|
}
|
|
72
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Leaves each method the class's own decorators declare only those routes, of every kind, dropping the ones it inherits
|
|
75
|
+
* for it: what `@Controller({ inheritedRoutes: 'replace' })` does once the class's method decorators have run.
|
|
76
|
+
*/ function dropInheritedRoutes(prototype) {
|
|
77
|
+
const redeclared = new Set(getDeclaredRoutes(prototype).map((route)=>route.method));
|
|
78
|
+
if (redeclared.size === 0) return;
|
|
79
|
+
const base = Object.getPrototypeOf(prototype);
|
|
80
|
+
// An entry the class takes from its base, for a method it re-declares
|
|
81
|
+
const inheritedFor = (inherited, method)=>(entry)=>inherited.includes(entry) && redeclared.has(method(entry));
|
|
82
|
+
const commands = ownCommandMap(prototype);
|
|
83
|
+
const baseCommands = Reflect.getMetadata(META.commands, base) ?? Object.create(null);
|
|
84
|
+
for (const [name, metas] of Object.entries(commands)){
|
|
85
|
+
const kept = metas.filter((meta)=>!inheritedFor(commandsNamed(baseCommands, name), (entry)=>entry.methodName)(meta));
|
|
86
|
+
if (kept.length > 0) commands[name] = kept;
|
|
87
|
+
else delete commands[name];
|
|
88
|
+
}
|
|
89
|
+
Reflect.defineMetadata(META.commands, commands, prototype);
|
|
90
|
+
const lists = [
|
|
91
|
+
[
|
|
92
|
+
META.messageHandlers,
|
|
93
|
+
(entry)=>entry.method
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
META.reactionHandlers,
|
|
97
|
+
(entry)=>entry.method
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
META.autocompleteHandlers,
|
|
101
|
+
(entry)=>entry.methodName
|
|
102
|
+
]
|
|
103
|
+
];
|
|
104
|
+
for (const [key, method] of lists){
|
|
105
|
+
const inherited = Reflect.getMetadata(key, base) ?? [];
|
|
106
|
+
Reflect.defineMetadata(key, ownHandlerList(key, prototype).filter((entry)=>!inheritedFor(inherited, method)(entry)), prototype);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The class's own command map, started from a copy of the inherited one, for the same reason. Keyed by command name
|
|
111
|
+
* without a prototype, so a command named like an inherited key, such as `constructor`, is a key like any other.
|
|
112
|
+
*/ function ownCommandMap(target) {
|
|
73
113
|
const own = Reflect.getOwnMetadata(META.commands, target);
|
|
74
114
|
if (own) return own;
|
|
75
115
|
const inherited = Reflect.getMetadata(META.commands, target) ?? {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
116
|
+
const copy = Object.create(null);
|
|
117
|
+
for (const [name, metas] of Object.entries(inherited))copy[name] = [
|
|
118
|
+
...metas
|
|
119
|
+
];
|
|
120
|
+
return copy;
|
|
121
|
+
}
|
|
122
|
+
/** The handlers a command map holds for `name`: its own entry only, never one `Object.prototype` holds. */ function commandsNamed(map, name) {
|
|
123
|
+
return map && Object.hasOwn(map, name) ? map[name] : [];
|
|
82
124
|
}
|
|
83
125
|
function MessageHandler(pattern, options = {}) {
|
|
84
|
-
return function(target, propertyKey) {
|
|
126
|
+
return declaring(function(target, propertyKey) {
|
|
85
127
|
refuseOnClass('@MessageHandler', target, propertyKey);
|
|
86
128
|
// An empty pattern means every message, as no pattern does
|
|
87
129
|
if (pattern === '') {
|
|
@@ -95,7 +137,7 @@ function MessageHandler(pattern, options = {}) {
|
|
|
95
137
|
};
|
|
96
138
|
addOwnHandler(META.messageHandlers, target, declared, (other)=>other.method === method && other.pattern === declared.pattern);
|
|
97
139
|
declareRoute(target, messageRoute(method, declared.pattern));
|
|
98
|
-
};
|
|
140
|
+
});
|
|
99
141
|
}
|
|
100
142
|
function ReactionHandler(emojiOrSettings, settings = {}) {
|
|
101
143
|
const [emoji, own] = typeof emojiOrSettings === 'object' ? [
|
|
@@ -105,7 +147,7 @@ function ReactionHandler(emojiOrSettings, settings = {}) {
|
|
|
105
147
|
emojiOrSettings,
|
|
106
148
|
settings
|
|
107
149
|
];
|
|
108
|
-
return function(target, propertyKey) {
|
|
150
|
+
return declaring(function(target, propertyKey) {
|
|
109
151
|
refuseOnClass('@ReactionHandler', target, propertyKey);
|
|
110
152
|
const method = propertyKey.toString();
|
|
111
153
|
addOwnHandler(META.reactionHandlers, target, {
|
|
@@ -114,7 +156,7 @@ function ReactionHandler(emojiOrSettings, settings = {}) {
|
|
|
114
156
|
settings: own
|
|
115
157
|
}, (other)=>other.method === method && other.emoji === emoji);
|
|
116
158
|
declareRoute(target, reactionRoute(method, emoji));
|
|
117
|
-
};
|
|
159
|
+
});
|
|
118
160
|
}
|
|
119
161
|
/** A custom emoji as Discord writes it in a message: `<:name:id>`, or `<a:name:id>` for an animated one. */ const CUSTOM_EMOJI = /^<a?:\w+:(\d+)>$/;
|
|
120
162
|
/**
|
|
@@ -143,12 +185,24 @@ function ReactionHandler(emojiOrSettings, settings = {}) {
|
|
|
143
185
|
}
|
|
144
186
|
// `{name}`, or `{name:type}` with the type read as far as the brace, so a type no segment can hold is named
|
|
145
187
|
const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
188
|
+
// The whole of a brace pair that is a param, as PLACEHOLDER_PATTERN reads one
|
|
189
|
+
const WHOLE_PLACEHOLDER = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`);
|
|
190
|
+
/**
|
|
191
|
+
* Why a pattern's brace pairs that aren't params are matched as literal text, or undefined when it has none: a
|
|
192
|
+
* param's name is ASCII letters, digits and _, so `{café}` is text.
|
|
193
|
+
*/ function literalBraces(pattern) {
|
|
194
|
+
const braces = (pattern.match(/\{[^{}]*}/g) ?? []).filter((brace)=>!WHOLE_PLACEHOLDER.test(brace));
|
|
195
|
+
if (braces.length === 0) return undefined;
|
|
196
|
+
const one = braces.length === 1;
|
|
197
|
+
return `${braces.join(', ')} ${one ? "isn't a param" : "aren't params"}, since a param's name is ASCII letters, digits and _; ` + `${one ? 'it is' : 'they are'} matched as literal text.`;
|
|
198
|
+
}
|
|
146
199
|
/** The character a parameter will not cross, so one pattern segment maps to one value. */ const PARAM_SEPARATOR = '/';
|
|
200
|
+
/** The longest customId Discord accepts; the shortest is one character. */ const MAX_CUSTOM_ID_LENGTH = 100;
|
|
147
201
|
/** Escapes a literal stretch of a pattern so only placeholders stay meaningful. */ const escapeLiteral = (literal)=>literal.replace(/[/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
148
202
|
/**
|
|
149
|
-
* Compiles a pattern into a regex, its parameter names, their types
|
|
150
|
-
* whole `/`-separated segment, so a uuid is captured whole and
|
|
151
|
-
* `profile/{uuid}/{id}`; a param that shares a segment with literal text is refused.
|
|
203
|
+
* Compiles a pattern into a regex, its parameter names, their types, its 4.1 specificity and the length of the
|
|
204
|
+
* shortest customId it matches. A `{name}` takes a whole `/`-separated segment, so a uuid is captured whole and
|
|
205
|
+
* `profile/{uuid}` never overlaps `profile/{uuid}/{id}`; a param that shares a segment with literal text is refused.
|
|
152
206
|
*/ function createRegexFromPattern(pattern) {
|
|
153
207
|
const params = [];
|
|
154
208
|
// By the param's own name, so one named like an inherited key, such as `__proto__`, keeps its type
|
|
@@ -169,6 +223,13 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
169
223
|
if (literal !== '' && !literal.endsWith(PARAM_SEPARATOR) || after !== undefined && after !== PARAM_SEPARATOR) {
|
|
170
224
|
throw refuse(new Error(`Invalid pattern "${pattern}": {${param}} must occupy a whole segment, so it has to be ` + `preceded and followed by "${PARAM_SEPARATOR}" or by the ends of the pattern. ` + `Write "a${PARAM_SEPARATOR}{${param}}" rather than "a-{${param}}".`));
|
|
171
225
|
}
|
|
226
|
+
// A regex group's name, which the engine would refuse with the compiled regex in place of the pattern
|
|
227
|
+
if (params.includes(param)) {
|
|
228
|
+
throw refuse(new Error(`Invalid pattern "${pattern}": {${param}} appears twice; give each param its own name.`));
|
|
229
|
+
}
|
|
230
|
+
if (/^\d/.test(param)) {
|
|
231
|
+
throw refuse(new Error(`Invalid pattern "${pattern}": {${param}} starts with a digit; start a param's name with a letter or _.`));
|
|
232
|
+
}
|
|
172
233
|
if (type !== undefined) {
|
|
173
234
|
if (!isSegmentType(type)) throw refuse(new Error(`Invalid pattern "${pattern}": ${segmentTypeProblem(param, type)}`));
|
|
174
235
|
if (choicesOf(type)?.includes('')) {
|
|
@@ -186,25 +247,23 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
186
247
|
literalLength += trailing.length;
|
|
187
248
|
regexPattern += escapeLiteral(trailing);
|
|
188
249
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
189
|
-
//
|
|
190
|
-
// more exactly than one leaving it to a parameter. Fewer parameters breaks a tie
|
|
191
|
-
// between equal-length patterns, then narrower typed params; the order the controllers
|
|
192
|
-
// and handlers are listed in settles what is left, and the startup warning names the pair.
|
|
250
|
+
// What 4.1 ranked by, kept for code that reads CommandMeta.specificity; routing ranks segment by segment
|
|
193
251
|
const specificity = routeSpecificity({
|
|
194
252
|
literals: literalLength,
|
|
195
253
|
params: params.length,
|
|
196
|
-
typed: Object.values(types).reduce((sum, type)=>sum +
|
|
254
|
+
typed: Object.values(types).reduce((sum, type)=>sum + (choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1), 0)
|
|
197
255
|
});
|
|
256
|
+
// Each param takes at least one character
|
|
198
257
|
return {
|
|
199
258
|
regex,
|
|
200
259
|
params,
|
|
201
260
|
types: lookupTable(types),
|
|
202
|
-
specificity
|
|
261
|
+
specificity,
|
|
262
|
+
shortest: literalLength + params.length
|
|
203
263
|
};
|
|
204
264
|
}
|
|
205
|
-
/** How few values a segment type takes, so a narrower type ranks first: words to choose from, then bool, int, number. */ const narrowness = (type)=>choicesOf(type) ? 4 : type === 'bool' ? 3 : type === 'int' ? 2 : 1;
|
|
206
265
|
/** Why a `{name:type}` cannot type a customId segment, which holds text the bot wrote, with no message to read. */ function segmentTypeProblem(param, type) {
|
|
207
|
-
const kinds = 'string, int, number, bool, or words to choose from such as {mode:on|off}';
|
|
266
|
+
const kinds = 'string, int, number, bool, snowflake, uuid, or words to choose from such as {mode:on|off}';
|
|
208
267
|
if ([
|
|
209
268
|
'member',
|
|
210
269
|
'user',
|
|
@@ -228,10 +287,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
228
287
|
* the keys the handler's params require are checked against the pattern or route, a typed segment's value against
|
|
229
288
|
* its type, and a select menu's choices, such as `values: string[]`, against what discord.js gives; a key a pipe
|
|
230
289
|
* produces is declared `Piped<T>`, and left to the pipe. Two component handlers of one type whose patterns match
|
|
231
|
-
* exactly the same ids stop the bot at startup
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
290
|
+
* exactly the same ids stop the bot at startup. Patterns that both match an id rank segment by segment, left to
|
|
291
|
+
* right: at the first segment one spells out as literal text and the other leaves to a param, the literal one runs,
|
|
292
|
+
* so `profile/me/{section}` takes `profile/me/edit` from `profile/{userId}/edit`; between patterns that leaves tied,
|
|
293
|
+
* the narrower type at the first param where they differ. Two still tied are warned about as the bot starts, and the
|
|
294
|
+
* one listed first runs. A context menu handler receives the kind its builder's `setType()` names, and one
|
|
235
295
|
* declaring the other kind fails to compile; when the compiler cannot tell the kind, `@Command` checks the parameter
|
|
236
296
|
* type it emits as it applies. A subclass that re-declares an inherited handler on the same name or pattern takes its
|
|
237
297
|
* own builder and options; on another it still answers the inherited one too, which the bot warns about as it
|
|
@@ -264,12 +324,11 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
264
324
|
* @category Handlers
|
|
265
325
|
* @see {@link CommandBuilder}
|
|
266
326
|
* @see {@link route}
|
|
267
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
268
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
327
|
+
* @see {@link https://meocord.dev/docs/4.2/slash-commands | Slash commands}
|
|
328
|
+
* @see {@link https://meocord.dev/docs/4.2/components | Buttons, selects and modals}
|
|
269
329
|
*/ function Command(name, builderOrType) {
|
|
270
330
|
const commandName = typeof name === 'string' ? name : name.pattern;
|
|
271
|
-
return function(target, propertyKey, _descriptor) {
|
|
272
|
-
var _commands, _commandName;
|
|
331
|
+
return declaring(function(target, propertyKey, _descriptor) {
|
|
273
332
|
refuseOnClass('@Command', target, propertyKey);
|
|
274
333
|
const originalMethod = _descriptor.value;
|
|
275
334
|
if (!originalMethod) {
|
|
@@ -277,7 +336,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
277
336
|
}
|
|
278
337
|
// Wrap original method for interaction type validation
|
|
279
338
|
const declaredAs = typeof builderOrType === 'function' ? builderOrType.name : `CommandType.${builderOrType}`;
|
|
280
|
-
|
|
339
|
+
const checked = function(interaction, params) {
|
|
281
340
|
if (!matchesCommandType(commandType, interaction)) {
|
|
282
341
|
throw new Error(`${target.constructor.name}.${propertyKey}: @Command('${commandName}', ${declaredAs}) takes ` + `${interactionClassName(commandType)}${givenInstead(interaction)}.`);
|
|
283
342
|
}
|
|
@@ -286,6 +345,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
286
345
|
params
|
|
287
346
|
]);
|
|
288
347
|
};
|
|
348
|
+
_descriptor.value = wrapsMethod(checked, originalMethod);
|
|
289
349
|
// This class's own map, inherited routes included
|
|
290
350
|
const commands = ownCommandMap(target);
|
|
291
351
|
let builderInstance;
|
|
@@ -321,7 +381,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
321
381
|
}));
|
|
322
382
|
}
|
|
323
383
|
// discord.js builders validate as they are set, and their errors name neither the command nor the field.
|
|
324
|
-
throw refuse(new Error(`${where}: ${builderOrType.name} could not build "${commandName}": ${detail}. Check its names, descriptions and ` +
|
|
384
|
+
throw refuse(new Error(`${where}: ${builderOrType.name} could not build "${commandName}": ${detail}. Check its names, descriptions and ` + 'localizations, which Discord limits to 32 characters for a name and 100 for a description. A name is letters, ' + 'numbers, `-` and `_`, lowercase where the script has case, and without spaces, in every locale.', {
|
|
325
385
|
cause: error
|
|
326
386
|
}));
|
|
327
387
|
}
|
|
@@ -341,6 +401,9 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
341
401
|
commandType = builderOrType;
|
|
342
402
|
}
|
|
343
403
|
if (isCustomIdRouted(commandType)) {
|
|
404
|
+
// A route warned as it was made
|
|
405
|
+
const braces = typeof name === 'string' ? literalBraces(commandName) : undefined;
|
|
406
|
+
if (braces) logger.warn(`${target.constructor.name}.${propertyKey}: in the pattern "${commandName}", ${braces}`);
|
|
344
407
|
let pattern;
|
|
345
408
|
try {
|
|
346
409
|
pattern = createRegexFromPattern(commandName);
|
|
@@ -349,10 +412,14 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
349
412
|
cause: error
|
|
350
413
|
}));
|
|
351
414
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
415
|
+
regex = pattern.regex;
|
|
416
|
+
dynamicParams = pattern.params;
|
|
417
|
+
specificity = pattern.specificity;
|
|
418
|
+
// Warned rather than refused, so a bot that carries such a handler still starts
|
|
419
|
+
if (pattern.shortest === 0 || pattern.shortest > MAX_CUSTOM_ID_LENGTH) {
|
|
420
|
+
const length = pattern.shortest === 0 ? 'the pattern is empty' : `its shortest is ${pattern.shortest} characters`;
|
|
421
|
+
logger.warn(`${target.constructor.name}.${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.`);
|
|
422
|
+
}
|
|
356
423
|
}
|
|
357
424
|
const declared = {
|
|
358
425
|
methodName: propertyKey,
|
|
@@ -369,14 +436,15 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
369
436
|
}
|
|
370
437
|
};
|
|
371
438
|
// One the class inherits for this method and route is replaced where it stands, so this one's options apply
|
|
372
|
-
|
|
373
|
-
const
|
|
439
|
+
if (!Object.hasOwn(commands, commandName)) commands[commandName] = [];
|
|
440
|
+
const metas = commands[commandName];
|
|
441
|
+
const inherited = commandsNamed(getCommandMap(Object.getPrototypeOf(target)), commandName);
|
|
374
442
|
const replaced = metas.findIndex((meta)=>inherited.includes(meta) && meta.methodName === propertyKey && meta.type === commandType);
|
|
375
443
|
if (replaced === -1) metas.push(declared);
|
|
376
444
|
else metas[replaced] = declared;
|
|
377
445
|
Reflect.defineMetadata(META.commands, commands, target);
|
|
378
446
|
declareRoute(target, commandRoute(propertyKey, commandType, commandName));
|
|
379
|
-
};
|
|
447
|
+
});
|
|
380
448
|
}
|
|
381
449
|
/** The interaction class Discord sends for each context menu kind. */ const CONTEXT_MENU_INTERACTIONS = new Map([
|
|
382
450
|
[
|
|
@@ -448,9 +516,9 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
448
516
|
* @group Decorators
|
|
449
517
|
* @category Handlers
|
|
450
518
|
* @see {@link Command}
|
|
451
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
519
|
+
* @see {@link https://meocord.dev/docs/4.2/autocomplete | Autocomplete}
|
|
452
520
|
*/ function Autocomplete(commandPath, optionName) {
|
|
453
|
-
return function(target, propertyKey, _descriptor) {
|
|
521
|
+
return declaring(function(target, propertyKey, _descriptor) {
|
|
454
522
|
refuseOnClass('@Autocomplete', target, propertyKey);
|
|
455
523
|
const methodName = propertyKey.toString();
|
|
456
524
|
addOwnHandler(META.autocompleteHandlers, target, {
|
|
@@ -459,7 +527,7 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
459
527
|
methodName
|
|
460
528
|
}, (other)=>other.methodName === methodName && other.commandPath === commandPath && other.optionName === optionName);
|
|
461
529
|
declareRoute(target, autocompleteRoute(methodName, commandPath, optionName));
|
|
462
|
-
};
|
|
530
|
+
});
|
|
463
531
|
}
|
|
464
532
|
/**
|
|
465
533
|
* Returns a controller's autocomplete handlers, option-specific ones first.
|
|
@@ -481,52 +549,63 @@ const PLACEHOLDER_PATTERN = /\{(\w+)(?::([^}/]*))?}/g;
|
|
|
481
549
|
const match = new RegExp(`^${PLACEHOLDER_PATTERN.source}$`).exec(segment);
|
|
482
550
|
return match ? typeKey(match[2]) : undefined;
|
|
483
551
|
}
|
|
484
|
-
/**
|
|
552
|
+
/**
|
|
553
|
+
* How few values a segment type takes, so a narrower type ranks first: words to choose from, then bool, uuid,
|
|
554
|
+
* snowflake, int, number, and text last. A snowflake is narrower than a number, which takes all its values.
|
|
555
|
+
*/ const NARROWNESS = lookupTable({
|
|
556
|
+
bool: 5,
|
|
557
|
+
uuid: 4,
|
|
558
|
+
snowflake: 3,
|
|
559
|
+
int: 2,
|
|
560
|
+
number: 1,
|
|
561
|
+
'': 0
|
|
562
|
+
});
|
|
563
|
+
const narrowness = (type)=>choicesOf(type) ? 6 : NARROWNESS[type];
|
|
564
|
+
/**
|
|
565
|
+
* How narrow a pattern segment that is a param, `{name}` or `{name:type}`, is: from `0` for text to `4` for words to
|
|
566
|
+
* choose from. `undefined` for a segment of literal text.
|
|
567
|
+
*/ function paramNarrowness(segment) {
|
|
568
|
+
const type = segmentParamType(segment);
|
|
569
|
+
return type === undefined ? undefined : narrowness(type);
|
|
570
|
+
}
|
|
571
|
+
/** A value of each scalar segment type: an int, a bool, a snowflake and a uuid; text takes any. */ const SAMPLE_VALUES = [
|
|
572
|
+
'1',
|
|
573
|
+
'true',
|
|
574
|
+
'10000000000000000',
|
|
575
|
+
'00000000-0000-0000-0000-000000000000'
|
|
576
|
+
];
|
|
577
|
+
/** A value both readings of a segment take: a literal, text, or a typed param; `undefined` when they share none. */ function sharedValue(left, right) {
|
|
485
578
|
const [a, b] = [
|
|
486
579
|
segmentParamType(left),
|
|
487
580
|
segmentParamType(right)
|
|
488
581
|
];
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
582
|
+
const takes = (type, segment, value)=>type === undefined ? segment === value : type === '' || parseSegment(type, value) !== undefined;
|
|
583
|
+
// Every type takes one of these when it takes anything another reading takes: a literal, a listed word, or a value
|
|
584
|
+
// of each scalar type
|
|
585
|
+
const candidates = [
|
|
586
|
+
...a === undefined ? [
|
|
493
587
|
left
|
|
494
|
-
] : [
|
|
495
|
-
|
|
588
|
+
] : [],
|
|
589
|
+
...b === undefined ? [
|
|
496
590
|
right
|
|
497
|
-
]
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
// Words to choose from overlap another type when one of them is a value of it
|
|
502
|
-
const [wordsA, wordsB] = [
|
|
503
|
-
choicesOf(a),
|
|
504
|
-
choicesOf(b)
|
|
591
|
+
] : [],
|
|
592
|
+
...choicesOf(a ?? '') ?? [],
|
|
593
|
+
...choicesOf(b ?? '') ?? [],
|
|
594
|
+
...SAMPLE_VALUES
|
|
505
595
|
];
|
|
506
|
-
|
|
507
|
-
if (wordsB) return wordsB.some((word)=>parseSegment(a, word) !== undefined);
|
|
508
|
-
// Of the scalar types, only a whole number and a number share values
|
|
509
|
-
return a === 'int' && b === 'number' || a === 'number' && b === 'int';
|
|
596
|
+
return candidates.find((value)=>takes(a, left, value) && takes(b, right, value));
|
|
510
597
|
}
|
|
511
598
|
/**
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*/ function
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const disjoint = left.some((segment, index)=>!segmentsOverlap(segment, right[index]));
|
|
523
|
-
if (!disjoint) collisions.push([
|
|
524
|
-
patterns[i],
|
|
525
|
-
patterns[j]
|
|
526
|
-
]);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return collisions;
|
|
599
|
+
* A customId both patterns match, such as `ticket/1/close` for `ticket/{id}/close` and `ticket/{n:int}/close`;
|
|
600
|
+
* `undefined` when no id matches both.
|
|
601
|
+
*/ function sharedCustomId(left, right) {
|
|
602
|
+
const [a, b] = [
|
|
603
|
+
left.split(PARAM_SEPARATOR),
|
|
604
|
+
right.split(PARAM_SEPARATOR)
|
|
605
|
+
];
|
|
606
|
+
if (a.length !== b.length) return undefined;
|
|
607
|
+
const values = a.map((segment, index)=>sharedValue(segment, b[index]));
|
|
608
|
+
return values.every((value)=>value !== undefined) ? values.join(PARAM_SEPARATOR) : undefined;
|
|
530
609
|
}
|
|
531
610
|
|
|
532
|
-
export { Autocomplete, Command, MessageHandler, PARAM_SEPARATOR, ReactionHandler, createRegexFromPattern,
|
|
611
|
+
export { Autocomplete, Command, MAX_CUSTOM_ID_LENGTH, MessageHandler, PARAM_SEPARATOR, ReactionHandler, commandsNamed, createRegexFromPattern, dropInheritedRoutes, getAutocompleteHandlers, getCommandMap, getDeclaredRoutes, getHandlerRoutes, getMessageHandlers, getReactionHandlers, literalBraces, matchesEmoji, ownHandlerList, paramNarrowness, patternShape, sharedCustomId };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import {
|
|
2
|
+
import { Logger } from '../common/logger.js';
|
|
3
|
+
import { declaring, refuse, decoratedName } from '../util/refusal.util.js';
|
|
4
|
+
import { describeValue } from '../util/value.util.js';
|
|
3
5
|
import { META } from '../util/metadata-keys.js';
|
|
4
6
|
|
|
5
7
|
function Cooldown(options) {
|
|
@@ -10,10 +12,11 @@ function Cooldown(options) {
|
|
|
10
12
|
per,
|
|
11
13
|
windowMs: Math.round(seconds * 1000)
|
|
12
14
|
};
|
|
13
|
-
return function(target, propertyKey) {
|
|
15
|
+
return declaring(function(target, propertyKey) {
|
|
14
16
|
// Checked where it applies, so the refusal names the handler or controller
|
|
15
17
|
const problem = cooldownProblem(seconds, uses, per, by);
|
|
16
18
|
if (problem) throw refuse(new Error(`${decoratedName(target, propertyKey)}: ${problem}`));
|
|
19
|
+
for (const warning of cooldownWarnings(options))logger.warn(`${decoratedName(target, propertyKey)}: ${warning}`);
|
|
17
20
|
// Decorators apply bottom-up; prepending keeps them in the order they read.
|
|
18
21
|
if (propertyKey === undefined) {
|
|
19
22
|
const existing = Reflect.getOwnMetadata(META.classCooldowns, target) ?? [];
|
|
@@ -28,12 +31,26 @@ function Cooldown(options) {
|
|
|
28
31
|
...existing
|
|
29
32
|
], target, propertyKey);
|
|
30
33
|
}
|
|
31
|
-
};
|
|
34
|
+
});
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
37
|
* The longest window, in seconds: half the furthest time a `Date` holds, 8.64e15 ms, so the moment a wait ends, now
|
|
35
38
|
* plus the window, is a real date for a refusal's `retryAt` and the time it shows.
|
|
36
39
|
*/ const MAX_SECONDS = 4320000000000;
|
|
40
|
+
const logger = new Logger('Cooldown');
|
|
41
|
+
/**
|
|
42
|
+
* What a cooldown's options get wrong that the types refuse, yet still counts: a `bypass` that is not a function, which
|
|
43
|
+
* fails every call, and a `seconds` that is not a number, read as one. A falsy `bypass` means none.
|
|
44
|
+
*/ function cooldownWarnings({ seconds, bypass }) {
|
|
45
|
+
const warnings = [];
|
|
46
|
+
if (bypass && typeof bypass !== 'function') {
|
|
47
|
+
warnings.push(`@Cooldown takes bypass as a function of the call, returning whether the call skips the cooldown, not ${describeValue(bypass)}; ` + 'every call it counts fails with it. In the next major version (5.0) it is refused.');
|
|
48
|
+
}
|
|
49
|
+
if (typeof seconds !== 'number') {
|
|
50
|
+
warnings.push(`@Cooldown takes seconds as a number, not ${describeValue(seconds)}, which it counts as a window of ` + `${Math.round(seconds * 1000)} ms. In the next major version (5.0) it is refused.`);
|
|
51
|
+
}
|
|
52
|
+
return warnings;
|
|
53
|
+
}
|
|
37
54
|
/** Why a cooldown's options cannot be counted, or undefined when they can. */ function cooldownProblem(seconds, uses, per, by) {
|
|
38
55
|
if (!(seconds >= 0.001 && seconds <= MAX_SECONDS)) return `@Cooldown needs a number of seconds from 0.001 to ${MAX_SECONDS}, not ${seconds}.`;
|
|
39
56
|
if (!Number.isInteger(uses) || uses < 1) return `@Cooldown needs a whole number of uses of at least 1, not ${uses}.`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { nonInteractionHandler, deferMisuseError } from '../core/defer.js';
|
|
3
|
-
import { refuseOnClass, refuse } from '../util/refusal.util.js';
|
|
3
|
+
import { declaring, refuseOnClass, refuse } from '../util/refusal.util.js';
|
|
4
4
|
import { META } from '../util/metadata-keys.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -37,9 +37,9 @@ import { META } from '../util/metadata-keys.js';
|
|
|
37
37
|
* @category Pipeline stages
|
|
38
38
|
* @see {@link DeferOptions}
|
|
39
39
|
* @see {@link respond}
|
|
40
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
40
|
+
* @see {@link https://meocord.dev/docs/4.2/defer | Deferring}
|
|
41
41
|
*/ function Defer(options = {}) {
|
|
42
|
-
return (target, propertyKey)=>{
|
|
42
|
+
return declaring((target, propertyKey)=>{
|
|
43
43
|
refuseOnClass('@Defer', target, propertyKey);
|
|
44
44
|
const methodName = String(propertyKey);
|
|
45
45
|
const kind = nonInteractionHandler(target, methodName);
|
|
@@ -47,7 +47,7 @@ import { META } from '../util/metadata-keys.js';
|
|
|
47
47
|
Reflect.defineMetadata(META.deferOptions, {
|
|
48
48
|
...options
|
|
49
49
|
}, target, methodName);
|
|
50
|
-
};
|
|
50
|
+
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export { Defer };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { ownHandlerList } from './controller.decorator.js';
|
|
3
3
|
import { META } from '../util/metadata-keys.js';
|
|
4
|
-
import { refuseOnClass } from '../util/refusal.util.js';
|
|
4
|
+
import { declaring, refuseOnClass } from '../util/refusal.util.js';
|
|
5
5
|
|
|
6
6
|
function eventDecorator(event, once) {
|
|
7
7
|
// Generic over the method, so it is checked against the event's arguments rather than required to match exactly
|
|
8
|
-
return function(target, propertyKey, _descriptor) {
|
|
8
|
+
return declaring(function(target, propertyKey, _descriptor) {
|
|
9
9
|
refuseOnClass(once ? '@Once' : '@On', target, propertyKey);
|
|
10
10
|
const handlers = ownHandlerList(META.eventHandlers, target);
|
|
11
11
|
handlers.push({
|
|
@@ -14,7 +14,7 @@ function eventDecorator(event, once) {
|
|
|
14
14
|
once
|
|
15
15
|
});
|
|
16
16
|
Reflect.defineMetadata(META.eventHandlers, handlers, target);
|
|
17
|
-
};
|
|
17
|
+
});
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* Handles a discord.js client event every time it is emitted, on a controller or a service.
|
|
@@ -42,7 +42,7 @@ function eventDecorator(event, once) {
|
|
|
42
42
|
* @group Decorators
|
|
43
43
|
* @category Handlers
|
|
44
44
|
* @see {@link Once}
|
|
45
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
45
|
+
* @see {@link https://meocord.dev/docs/4.2/gateway-events | Gateway events}
|
|
46
46
|
*/ function On(event) {
|
|
47
47
|
return eventDecorator(event, false);
|
|
48
48
|
}
|
|
@@ -69,7 +69,7 @@ function eventDecorator(event, once) {
|
|
|
69
69
|
* @group Decorators
|
|
70
70
|
* @category Handlers
|
|
71
71
|
* @see {@link On}
|
|
72
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
72
|
+
* @see {@link https://meocord.dev/docs/4.2/gateway-events | Gateway events}
|
|
73
73
|
*/ function Once(event) {
|
|
74
74
|
return eventDecorator(event, true);
|
|
75
75
|
}
|
|
@@ -5,7 +5,7 @@ import { Logger } from '../common/logger.js';
|
|
|
5
5
|
import { warnDeprecatedBehaviour } from '../common/deprecation.js';
|
|
6
6
|
import { isConstructor, describeValue } from '../util/value.util.js';
|
|
7
7
|
import { META } from '../util/metadata-keys.js';
|
|
8
|
-
import { refuseOnMethod } from '../util/refusal.util.js';
|
|
8
|
+
import { declaring, refuseOnMethod } from '../util/refusal.util.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Marks a class as an exception filter for the given error types.
|
|
@@ -34,9 +34,9 @@ import { refuseOnMethod } from '../util/refusal.util.js';
|
|
|
34
34
|
* @group Decorators
|
|
35
35
|
* @category Pipeline stages
|
|
36
36
|
* @see {@link UseFilter}
|
|
37
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
37
|
+
* @see {@link https://meocord.dev/docs/4.2/exception-filters | Exception filters}
|
|
38
38
|
*/ function Catch(...errorTypes) {
|
|
39
|
-
return function(target, propertyKey) {
|
|
39
|
+
return declaring(function(target, propertyKey) {
|
|
40
40
|
refuseOnMethod('@Catch', target, propertyKey);
|
|
41
41
|
makeInjectable(target);
|
|
42
42
|
errorTypes.forEach((type, index)=>{
|
|
@@ -45,7 +45,7 @@ import { refuseOnMethod } from '../util/refusal.util.js';
|
|
|
45
45
|
warnDeprecatedBehaviour(logger, `${target.name}: @Catch's ${entry}, ${describeValue(type)}, which matches no error,`, 'is refused', 'an error class, such as @Catch(CooldownError),');
|
|
46
46
|
});
|
|
47
47
|
Reflect.defineMetadata(META.catchTypes, errorTypes, target);
|
|
48
|
-
};
|
|
48
|
+
});
|
|
49
49
|
}
|
|
50
50
|
const logger = new Logger('Catch');
|
|
51
51
|
const ORDINALS = [
|
|
@@ -85,9 +85,9 @@ const ORDINALS = [
|
|
|
85
85
|
* @group Decorators
|
|
86
86
|
* @category Pipeline stages
|
|
87
87
|
* @see {@link Catch}
|
|
88
|
-
* @see {@link https://meocord.dev/docs/4.
|
|
88
|
+
* @see {@link https://meocord.dev/docs/4.2/exception-filters | Exception filters}
|
|
89
89
|
*/ function UseFilter(...filters) {
|
|
90
|
-
return function(target, propertyKey) {
|
|
90
|
+
return declaring(function(target, propertyKey) {
|
|
91
91
|
const where = propertyKey === undefined ? target.name : `${target.constructor.name}.${String(propertyKey)}`;
|
|
92
92
|
assertStageEntries('@UseFilter', 'filter', where, filters);
|
|
93
93
|
// Decorators apply bottom-up, so a higher decorator's filters are tried first.
|
|
@@ -104,7 +104,7 @@ const ORDINALS = [
|
|
|
104
104
|
...existing
|
|
105
105
|
], target, propertyKey);
|
|
106
106
|
}
|
|
107
|
-
};
|
|
107
|
+
});
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
export { Catch, UseFilter };
|