meocord 4.2.2 → 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +130 -0
- package/dist/cjs/_shared/{controller.decorator-U2NBWcoU.cjs → controller.decorator-hp5xdJU4.cjs} +512 -183
- package/dist/cjs/_shared/{startup-roots-lA5xkBFq.cjs → event-sources-DzqTODXg.cjs} +265 -80
- package/dist/cjs/_shared/{providers-B4MOFBrW.cjs → providers-BBvbnmLP.cjs} +780 -281
- package/dist/cjs/_shared/{sharded-cooldown-store-HTUb3Ii_.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-CbBHUu5P.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +432 -181
- package/dist/cjs/decorator/index.cjs +85 -120
- package/dist/cjs/testing/index.cjs +1664 -784
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/package.json.template +9 -9
- package/dist/esm/bin/app-template/src/main.ts.template +3 -2
- package/dist/esm/bin/generator.js +6 -4
- package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
- package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
- package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
- package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
- package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
- package/dist/esm/bin/helper/service-generator.helper.js +6 -4
- package/dist/esm/bin/meocord.js +106 -48
- package/dist/esm/build/native-addons.js +75 -19
- package/dist/esm/build/rsbuild-config.js +11 -3
- package/dist/esm/build/typed-externals.js +15 -2
- package/dist/esm/common/cooldown-store.js +38 -14
- package/dist/esm/common/deprecation.js +4 -1
- package/dist/esm/common/execution-context.js +3 -1
- package/dist/esm/common/explained-error.js +7 -3
- package/dist/esm/common/log-level.js +13 -9
- package/dist/esm/common/logger.js +4 -2
- package/dist/esm/common/meocord-text.js +11 -6
- package/dist/esm/common/redis-cooldown-store.js +3 -7
- package/dist/esm/common/response/flags.js +17 -1
- package/dist/esm/common/response/install-context.js +27 -16
- package/dist/esm/common/response/presenter.js +7 -3
- package/dist/esm/common/response/response-state.js +27 -5
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +17 -8
- package/dist/esm/core/autocomplete-routes.js +26 -0
- package/dist/esm/core/command-conflicts.js +15 -7
- package/dist/esm/core/command-registration.js +15 -1
- package/dist/esm/core/cooldown-runner.js +7 -9
- package/dist/esm/core/defer.js +2 -5
- package/dist/esm/core/dispatcher.js +83 -58
- package/dist/esm/core/event-requirements.js +67 -19
- package/dist/esm/core/event-sources.js +100 -0
- package/dist/esm/core/fallback.js +3 -1
- package/dist/esm/core/filter-runner.js +4 -4
- package/dist/esm/core/guard-runner.js +173 -52
- package/dist/esm/core/handler-input.js +24 -3
- package/dist/esm/core/handler-pipeline.js +44 -28
- package/dist/esm/core/handler-registry.js +51 -21
- package/dist/esm/core/input-runner.js +7 -8
- package/dist/esm/core/interceptor-runner.js +4 -5
- package/dist/esm/core/lifecycle-hooks.js +3 -8
- package/dist/esm/core/lifecycle-order.js +1 -15
- package/dist/esm/core/meocord-classes.js +14 -1
- package/dist/esm/core/meocord-factory.js +80 -53
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +5 -9
- package/dist/esm/core/message-params.js +126 -46
- package/dist/esm/core/message-routes.js +225 -45
- package/dist/esm/core/message-words.js +30 -2
- package/dist/esm/core/observer-runner.js +2 -1
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +28 -3
- package/dist/esm/core/scalar-types.js +57 -16
- package/dist/esm/core/shard-context.js +11 -10
- package/dist/esm/core/shard-exit.js +3 -3
- package/dist/esm/core/shard-manager.js +16 -6
- package/dist/esm/core/startup-roots.js +28 -8
- package/dist/esm/core/theme-resolvers.js +26 -11
- package/dist/esm/core/theme-runtime.js +23 -13
- package/dist/esm/core/theme-scope.js +27 -29
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +6 -1
- package/dist/esm/decorator/command-builder.decorator.js +14 -0
- package/dist/esm/decorator/controller-class.decorator.js +1 -1
- package/dist/esm/decorator/controller.decorator.js +48 -19
- package/dist/esm/decorator/defer.decorator.js +2 -2
- package/dist/esm/decorator/event.decorator.js +4 -5
- package/dist/esm/decorator/filter.decorator.js +2 -2
- package/dist/esm/decorator/guard.decorator.js +4 -61
- package/dist/esm/decorator/inject.decorator.js +4 -1
- package/dist/esm/decorator/interceptor.decorator.js +2 -2
- package/dist/esm/decorator/theme.decorator.js +4 -3
- package/dist/esm/decorator/validation.decorator.js +5 -5
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +29 -13
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -355
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +195 -77
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +639 -113
- package/dist/esm/testing/response-log.js +8 -0
- package/dist/esm/testing/response.js +7 -5
- package/dist/esm/testing/routing.js +48 -6
- package/dist/esm/testing/strict-mocks.js +1 -0
- package/dist/esm/testing/theme.js +3 -2
- package/dist/esm/util/common.util.js +30 -9
- package/dist/esm/util/generator-cli.util.js +6 -4
- package/dist/esm/util/inherited-env.util.js +72 -13
- package/dist/esm/util/json.util.js +10 -3
- package/dist/esm/util/meocord-cli.util.js +3 -2
- package/dist/esm/util/meocord-config-loader.util.js +14 -1
- package/dist/esm/util/meocord-config-validation.util.js +13 -3
- package/dist/esm/util/meocord-source-config.util.js +13 -3
- package/dist/esm/util/platform.util.js +3 -2
- package/dist/esm/util/refusal.util.js +29 -11
- package/dist/esm/util/registration-mode.util.js +14 -1
- package/dist/esm/util/timer-limit.util.js +4 -1
- package/dist/esm/util/tsconfig-paths.util.js +38 -7
- package/dist/esm/util/tsconfig.util.js +16 -58
- package/dist/esm/util/user-text.util.js +8 -4
- package/dist/esm/util/value.util.js +15 -4
- package/dist/types/common/index.d.cts +13 -9
- package/dist/types/common/index.d.ts +13 -9
- package/dist/types/core/index.d.cts +21 -11
- package/dist/types/core/index.d.ts +21 -11
- package/dist/types/decorator/index.d.cts +63 -63
- package/dist/types/decorator/index.d.ts +63 -63
- package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
- package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
- package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
- package/dist/types/testing/index.d.cts +137 -54
- package/dist/types/testing/index.d.ts +137 -54
- package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
- package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
- package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
- package/package.json +5 -2
|
@@ -5,8 +5,8 @@ import { UserError } from '../errors.js';
|
|
|
5
5
|
import { textFor } from '../meocord-text.js';
|
|
6
6
|
import { logFailedSend } from './send-failure.js';
|
|
7
7
|
import { describeInteraction } from '../../util/interaction.util.js';
|
|
8
|
-
import { getInstallContext } from './install-context.js';
|
|
9
|
-
import { resolveFlags,
|
|
8
|
+
import { isBotInstalled, getInstallContext } from './install-context.js';
|
|
9
|
+
import { resolveFlags, droppedFlagsWarning, hasEphemeral, hasComponentsV2 } from './flags.js';
|
|
10
10
|
import { rewriteAttachmentUrls, keptAttachmentNames } from './attachments.js';
|
|
11
11
|
import { renderEmbed, isTooLarge, REFUSED_AS_TOO_LARGE, withoutFiles, renderContainer, attachmentsOf, withSendableFiles, presenterFor, presentedMessage, TEXT_LIMITS, defaultPresenter, DEFAULT_ATTACHMENT_SIZE_LIMIT } from './presenter.js';
|
|
12
12
|
import { sameEmbed, withoutRenderedViews, lockComponents, countComponents, V2_COMPONENT_LIMIT, EMBED_LIMIT } from './components.js';
|
|
@@ -16,6 +16,16 @@ import { themeForInteraction } from '../../core/theme-scope.js';
|
|
|
16
16
|
import { LOADING_DRAW_TIMEOUT_MS } from '../../core/theme-resolvers.js';
|
|
17
17
|
import { shared } from '../../util/shared-state.util.js';
|
|
18
18
|
|
|
19
|
+
function _class_private_field_loose_base(receiver, privateKey) {
|
|
20
|
+
if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) {
|
|
21
|
+
throw new TypeError("attempted to use private field on non-instance");
|
|
22
|
+
}
|
|
23
|
+
return receiver;
|
|
24
|
+
}
|
|
25
|
+
var id = 0;
|
|
26
|
+
function _class_private_field_loose_key(name) {
|
|
27
|
+
return "__private_" + id++ + "_" + name;
|
|
28
|
+
}
|
|
19
29
|
const ALREADY_ACKNOWLEDGED = 40060;
|
|
20
30
|
/** Discord's code for an interaction it no longer knows, such as one past its three seconds unacknowledged. */ const UNKNOWN_INTERACTION = 10062;
|
|
21
31
|
const TOKEN_EXPIRED = new Set([
|
|
@@ -113,6 +123,7 @@ const messageLocks = shared('messageLocks', ()=>new Map());
|
|
|
113
123
|
function toJson(value) {
|
|
114
124
|
return typeof value?.toJSON === 'function' ? value.toJSON() : value;
|
|
115
125
|
}
|
|
126
|
+
var _location = /*#__PURE__*/ _class_private_field_loose_key("_location");
|
|
116
127
|
/** The response state behind `respond()`, with what `@Defer` and the testing helpers use besides. */ class InteractionResponse {
|
|
117
128
|
get state() {
|
|
118
129
|
this.sync();
|
|
@@ -151,7 +162,7 @@ function toJson(value) {
|
|
|
151
162
|
flagsFor(step, requested, v2 = this.v2) {
|
|
152
163
|
const { flags, dropped } = resolveFlags(step, requested, v2);
|
|
153
164
|
if (dropped && development()) {
|
|
154
|
-
logger.warn(
|
|
165
|
+
logger.warn(droppedFlagsWarning(step, dropped));
|
|
155
166
|
}
|
|
156
167
|
return flags;
|
|
157
168
|
}
|
|
@@ -675,7 +686,7 @@ function toJson(value) {
|
|
|
675
686
|
} catch (error) {
|
|
676
687
|
const message = this.message;
|
|
677
688
|
const expired = Date.now() - this.interaction.createdTimestamp >= TOKEN_EXPIRED_AFTER_MS;
|
|
678
|
-
if (!TOKEN_EXPIRED.has(errorCode(error)) || !expired || !this.
|
|
689
|
+
if (!TOKEN_EXPIRED.has(errorCode(error)) || !expired || !isBotInstalled(this.interaction) || !message) throw error;
|
|
679
690
|
this.lastMessage = await this.call('message.edit', sent, ()=>message.edit(sent));
|
|
680
691
|
}
|
|
681
692
|
this.phase = 'replied';
|
|
@@ -987,6 +998,10 @@ function toJson(value) {
|
|
|
987
998
|
} : body;
|
|
988
999
|
}
|
|
989
1000
|
constructor(interaction){
|
|
1001
|
+
Object.defineProperty(this, _location, {
|
|
1002
|
+
writable: true,
|
|
1003
|
+
value: void 0
|
|
1004
|
+
});
|
|
990
1005
|
this.interaction = interaction;
|
|
991
1006
|
this.phase = 'unanswered';
|
|
992
1007
|
/** The answer steps asked for, run one after another; `pending` counts those not yet settled. */ this.queue = Promise.resolve();
|
|
@@ -1004,7 +1019,14 @@ function toJson(value) {
|
|
|
1004
1019
|
this.suppressNotifications = false;
|
|
1005
1020
|
// Set by @Defer's first step, so an error can name what acknowledged the interaction
|
|
1006
1021
|
this.underDefer = false;
|
|
1007
|
-
this
|
|
1022
|
+
Object.defineProperty(this, 'location', {
|
|
1023
|
+
get: ()=>{
|
|
1024
|
+
var _class_private_field_loose_base1, _location1;
|
|
1025
|
+
return (_class_private_field_loose_base1 = _class_private_field_loose_base(this, _location))[_location1 = _location] ?? (_class_private_field_loose_base1[_location1] = getInstallContext(interaction));
|
|
1026
|
+
},
|
|
1027
|
+
enumerable: true,
|
|
1028
|
+
configurable: true
|
|
1029
|
+
});
|
|
1008
1030
|
const message = 'message' in interaction ? interaction.message : undefined;
|
|
1009
1031
|
this.v2 = Boolean(message?.flags?.has(MessageFlags.IsComponentsV2));
|
|
1010
1032
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { CooldownStore, withRelease, MemoryCooldownStore, forgetRecorded, recordedOf } from './cooldown-store.js';
|
|
2
|
+
import { CooldownStore, singleVerdict, withRelease, MemoryCooldownStore, forgetRecorded, recordedOf } from './cooldown-store.js';
|
|
3
3
|
import { isShardMessage } from '../core/shard-messages.js';
|
|
4
4
|
import { managerGone } from '../util/sharding-mode.util.js';
|
|
5
5
|
import { isShardProcess } from '../util/shard-process.util.js';
|
|
@@ -60,16 +60,12 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
60
60
|
* @param limit - The calls allowed, and the window they are counted over.
|
|
61
61
|
* @returns Whether this call was recorded, and if not, how long until one can be.
|
|
62
62
|
*/ async consume(key, limit) {
|
|
63
|
-
|
|
63
|
+
return singleVerdict(await this.consumeMany([
|
|
64
64
|
{
|
|
65
65
|
key,
|
|
66
66
|
limit
|
|
67
67
|
}
|
|
68
|
-
]);
|
|
69
|
-
return {
|
|
70
|
-
allowed,
|
|
71
|
-
retryAfterMs
|
|
72
|
-
};
|
|
68
|
+
]));
|
|
73
69
|
}
|
|
74
70
|
/**
|
|
75
71
|
* Records a call against every entry if all allow it, in one message to the manager.
|
|
@@ -108,11 +104,17 @@ import { isShardProcess } from '../util/shard-process.util.js';
|
|
|
108
104
|
this.pending.set(id, {
|
|
109
105
|
resolve: (verdict, recorded)=>{
|
|
110
106
|
settle();
|
|
111
|
-
// The manager's undo is the calls it recorded, sent back
|
|
112
|
-
|
|
107
|
+
// The manager's undo is the calls it recorded, sent back once: it finds a call by key and time, so a second
|
|
108
|
+
// undo would take another call recorded in the same millisecond
|
|
109
|
+
let released = false;
|
|
110
|
+
const release = async ()=>{
|
|
111
|
+
if (released) return;
|
|
112
|
+
released = true;
|
|
113
|
+
channel.send({
|
|
113
114
|
meocord: 'cooldown-release',
|
|
114
115
|
recorded: recorded ?? []
|
|
115
116
|
});
|
|
117
|
+
};
|
|
116
118
|
resolve(!peek && verdict.allowed && recorded ? withRelease(verdict, release) : verdict);
|
|
117
119
|
},
|
|
118
120
|
reject: (error)=>(settle(), reject(error))
|
package/dist/esm/common/theme.js
CHANGED
|
@@ -2,6 +2,7 @@ import { warnDeprecated } from './deprecation.js';
|
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
3
|
import { useTheme, setLegacyThemeLayer } from '../core/theme-scope.js';
|
|
4
4
|
import { themeProblems } from '../core/theme-validation.js';
|
|
5
|
+
import { shared } from '../util/shared-state.util.js';
|
|
5
6
|
|
|
6
7
|
/** The theme role each static stands for; `errorColor` is a fault in the bot, `danger`. */ const ROLES = {
|
|
7
8
|
primaryColor: 'primary',
|
|
@@ -11,7 +12,8 @@ import { themeProblems } from '../core/theme-validation.js';
|
|
|
11
12
|
warningColor: 'warning'
|
|
12
13
|
};
|
|
13
14
|
const logger = new Logger('Theme');
|
|
14
|
-
|
|
15
|
+
// One per process for this meocord version, so a static set through one build keeps those set through the other
|
|
16
|
+
const assigned = shared('legacyThemeColors', ()=>({}));
|
|
15
17
|
/** Reads a role of the theme where it is read, warning once a run for each static read. */ function read(name) {
|
|
16
18
|
warnDeprecated(logger, `Theme.${name}`, `useTheme().colors.${ROLES[name]}`);
|
|
17
19
|
return useTheme().colors[ROLES[name]];
|
|
@@ -78,18 +78,27 @@ const PLURAL_CATEGORIES = new Set([
|
|
|
78
78
|
'other'
|
|
79
79
|
]);
|
|
80
80
|
/** 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));
|
|
81
|
-
/**
|
|
82
|
-
* A message by its dotted key, or undefined when the catalog lacks it or the key names a group. A plural without an
|
|
83
|
-
* `other` form reads as missing, so a translation that left it out falls back to the next catalog.
|
|
84
|
-
*/ function lookup(catalog, key) {
|
|
81
|
+
/** The value at a dotted key's path, or undefined where the path ends early. */ function valueAt(catalog, key) {
|
|
85
82
|
let current = catalog;
|
|
86
83
|
for (const part of key.split('.')){
|
|
87
84
|
if (typeof current !== 'object' || current === null) return undefined;
|
|
88
85
|
current = current[part];
|
|
89
86
|
}
|
|
87
|
+
return current;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A message by its dotted key, or undefined when the catalog lacks it or the key names a group. `reference`, the
|
|
91
|
+
* default catalog for a translation, decides where it has a group that an object is that group, so a translation that
|
|
92
|
+
* 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
|
|
93
|
+
* own keys decide, so a translation may give a plural for a message its language needs one for. A plural without an
|
|
94
|
+
* `other` form reads as missing, so a translation that left it out falls back to the next catalog.
|
|
95
|
+
*/ function lookup(catalog, key, reference = catalog) {
|
|
96
|
+
const current = valueAt(catalog, key);
|
|
90
97
|
if (typeof current === 'string') return current;
|
|
91
|
-
if (isPluralShape(current)
|
|
92
|
-
|
|
98
|
+
if (!isPluralShape(current) || typeof current.other !== 'string') return undefined;
|
|
99
|
+
const shape = reference === catalog ? current : valueAt(reference, key);
|
|
100
|
+
const group = typeof shape === 'object' && shape !== null && !isPluralShape(shape);
|
|
101
|
+
return group ? undefined : current;
|
|
93
102
|
}
|
|
94
103
|
const plurals = new Map();
|
|
95
104
|
/** 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) {
|
|
@@ -153,7 +162,7 @@ class CatalogTranslator extends Translator {
|
|
|
153
162
|
}
|
|
154
163
|
translate(requested, key, params = {}) {
|
|
155
164
|
for (const locale of this[LOCALE_CHAIN](requested)){
|
|
156
|
-
const message = lookup(this.catalogs[locale], key);
|
|
165
|
+
const message = lookup(this.catalogs[locale], key, this.catalogs[this.defaultLocale]);
|
|
157
166
|
if (message === undefined) continue;
|
|
158
167
|
// An empty translation, as tools export an untranslated string, counts as missing: Discord refuses an empty reply
|
|
159
168
|
const text = pluralForm({
|
|
@@ -189,7 +198,7 @@ class CatalogTranslator extends Translator {
|
|
|
189
198
|
const localized = {};
|
|
190
199
|
for (const locale of this.locales){
|
|
191
200
|
if (locale === this.defaultLocale) continue;
|
|
192
|
-
const message = lookup(this.catalogs[locale], key);
|
|
201
|
+
const message = lookup(this.catalogs[locale], key, this.catalogs[this.defaultLocale]);
|
|
193
202
|
// A translation with a {param} is left out, as Discord would show it as written, and so is an empty one
|
|
194
203
|
if (typeof message === 'string' && message !== '' && placeholderNames(message).length === 0) {
|
|
195
204
|
localized[locale] = fillPlaceholders(message, (_name, whole)=>whole);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getAutocompleteHandlers } from '../decorator/controller.decorator.js';
|
|
2
|
+
import { focusedOptionName, resolveCommandPaths } from '../util/interaction.util.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Every `@Autocomplete` handler of the given controllers, read from their metadata, with the handlers of one option
|
|
6
|
+
* before those of a whole command, so an option's handler wins over its command's whatever order they are declared in.
|
|
7
|
+
*/ function buildAutocompleteRoutes(controllers) {
|
|
8
|
+
const routes = controllers.flatMap((controllerClass)=>getAutocompleteHandlers(controllerClass.prototype).map((meta)=>({
|
|
9
|
+
controllerClass,
|
|
10
|
+
meta
|
|
11
|
+
})));
|
|
12
|
+
return routes.sort((a, b)=>Number(Boolean(b.meta.optionName)) - Number(Boolean(a.meta.optionName)));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The handler an autocomplete reaches, as dispatch routes it: its full command path, then its command's name, and at
|
|
16
|
+
* each the first handler whose option is the focused one, or that names none.
|
|
17
|
+
*/ function matchAutocompleteRoute(routes, interaction) {
|
|
18
|
+
const focused = focusedOptionName(interaction);
|
|
19
|
+
for (const path of resolveCommandPaths(interaction)){
|
|
20
|
+
const route = routes.find(({ meta })=>meta.commandPath === path && (meta.optionName === undefined || meta.optionName === focused));
|
|
21
|
+
if (route) return route;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { buildAutocompleteRoutes, matchAutocompleteRoute };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApplicationCommandType, ApplicationCommandOptionType } from 'discord.js';
|
|
2
2
|
import { Logger } from '../common/logger.js';
|
|
3
|
-
import { getAutocompleteHandlers, getCommandMap, getDeclaredRoutes, getHandlerRoutes, getMessageHandlers, getReactionHandlers } from '../decorator/controller.decorator.js';
|
|
3
|
+
import { getAutocompleteHandlers, getCommandMap, getDeclaredRoutes, ownEventHandlers, eventRoute, getHandlerRoutes, getMessageHandlers, getReactionHandlers } from '../decorator/controller.decorator.js';
|
|
4
|
+
import { getEventHandlers } from '../decorator/event.decorator.js';
|
|
4
5
|
import { serialise, registrationKey, SENT_AS, commandNameOf } from './command-registration.js';
|
|
5
6
|
import { buildComponentRoutes, findComponentRouteConflicts } from './component-routes.js';
|
|
6
7
|
import { CommandType } from '../enum/controller.enum.js';
|
|
@@ -246,9 +247,9 @@ const quoted = (names)=>names.length < 2 ? names.map((name)=>`"${name}"`).join('
|
|
|
246
247
|
}
|
|
247
248
|
/**
|
|
248
249
|
* Warns about a handler a subclass re-declares on other routes while it still answers the ones it inherits, for
|
|
249
|
-
* `@Command`, `@MessageHandler`, `@ReactionHandler` and `@
|
|
250
|
-
* the inherited routes. Every class a listed controller extends is checked, a base that is not listed
|
|
251
|
-
* the methods the controller still inherits through it.
|
|
250
|
+
* `@Command`, `@MessageHandler`, `@ReactionHandler`, `@Autocomplete`, `@On` and `@Once` alike. The next major version
|
|
251
|
+
* (5.0) drops the inherited routes. Every class a listed controller extends is checked, a base that is not listed
|
|
252
|
+
* included, for the methods the controller still inherits through it.
|
|
252
253
|
*/ function warnInheritedRoutes(controllerClasses) {
|
|
253
254
|
// A set, so a class two listed controllers extend is named once, for the routes either of them still answers
|
|
254
255
|
const problems = new Set();
|
|
@@ -257,11 +258,18 @@ const quoted = (names)=>names.length < 2 ? names.map((name)=>`"${name}"`).join('
|
|
|
257
258
|
// nothing of them from above it, so a class above is not named for them
|
|
258
259
|
const cut = new Set();
|
|
259
260
|
for(let prototype = controllerClass.prototype; prototype && prototype !== Object.prototype; prototype = Object.getPrototypeOf(prototype)){
|
|
260
|
-
const declared = getDeclaredRoutes(prototype);
|
|
261
261
|
const base = Object.getPrototypeOf(prototype);
|
|
262
|
-
if (
|
|
262
|
+
if (!base) continue;
|
|
263
|
+
const declared = [
|
|
264
|
+
...getDeclaredRoutes(prototype),
|
|
265
|
+
...ownEventHandlers(prototype).map(eventRoute)
|
|
266
|
+
];
|
|
267
|
+
if (declared.length === 0) continue;
|
|
263
268
|
if (Reflect.getOwnMetadata(META.replacesInheritedRoutes, prototype)) for (const route of declared)cut.add(route.method);
|
|
264
|
-
const inherited =
|
|
269
|
+
const inherited = [
|
|
270
|
+
...getHandlerRoutes(base),
|
|
271
|
+
...getEventHandlers(base).map(eventRoute)
|
|
272
|
+
].filter((route)=>!cut.has(route.method));
|
|
265
273
|
for (const method of new Set(declared.map((route)=>route.method))){
|
|
266
274
|
const own = [
|
|
267
275
|
...new Set(declared.filter((route)=>route.method === method).map((route)=>route.label))
|
|
@@ -61,6 +61,7 @@ import { CommandType } from '../enum/controller.enum.js';
|
|
|
61
61
|
if (existing === undefined) {
|
|
62
62
|
const name = typeof body.name === 'string' ? body.name : commandName;
|
|
63
63
|
unlocalizable.push(...localizationProblems(name, body));
|
|
64
|
+
const builderName = builderClass?.name;
|
|
64
65
|
byKey.set(key, {
|
|
65
66
|
builderClass: builderClass ?? builder,
|
|
66
67
|
command: {
|
|
@@ -68,6 +69,9 @@ import { CommandType } from '../enum/controller.enum.js';
|
|
|
68
69
|
body,
|
|
69
70
|
...guilds && {
|
|
70
71
|
guilds
|
|
72
|
+
},
|
|
73
|
+
...typeof builderName === 'string' && builderName && {
|
|
74
|
+
builder: builderName
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
});
|
|
@@ -105,6 +109,14 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
105
109
|
}
|
|
106
110
|
/** Whether a command is an activity's primary entry point, which Discord takes only globally. */ const isEntryPoint = (command)=>command.body.type === ApplicationCommandType.PrimaryEntryPoint;
|
|
107
111
|
/** What a blank guild to register every command to says, as an unset variable gives one. */ const BLANK_GUILD = 'No commands were registered: the guild to register to is blank. Give a guild id, or leave it out to register where commands.* says.';
|
|
112
|
+
/**
|
|
113
|
+
* Names every command whose builder lists guilds but no guild id, each with its builder, and says the run registers
|
|
114
|
+
* none of them yet still succeeds.
|
|
115
|
+
*/ function blankGuildsWarning(commands) {
|
|
116
|
+
const one = commands.length === 1;
|
|
117
|
+
const lines = commands.map((command)=>` ${command.builder ? `${command.builder}: ` : ''}"${command.name}"`);
|
|
118
|
+
return `${commands.length} ${one ? 'command is' : 'commands are'} not registered: ${one ? 'its builder lists' : 'their builders list'} ` + 'no guild ids, as when the variable holding them is unset.\n' + `${lines.join('\n')}\n` + 'Set the guild ids; a builder without guilds registers where commands.* says. `meocord register` still exits 0 for ' + 'these; the next major version (5.0) exits 1.';
|
|
119
|
+
}
|
|
108
120
|
/**
|
|
109
121
|
* Where each command is sent: one bulk update per scope. A scope's update replaces everything the application has
|
|
110
122
|
* there, so the default scope is included even when it ends up empty, unless `commands.guilds` lists no guild id.
|
|
@@ -148,6 +160,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
148
160
|
guild
|
|
149
161
|
});
|
|
150
162
|
const unregistered = [];
|
|
163
|
+
const blankGuilds = [];
|
|
151
164
|
for (const command of commands){
|
|
152
165
|
if (isEntryPoint(command)) {
|
|
153
166
|
if (command.guilds) {
|
|
@@ -157,7 +170,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
157
170
|
} else if (command.guilds) {
|
|
158
171
|
const own = ids(command.guilds);
|
|
159
172
|
// Falling back to the default scope could publish a staff-only command everywhere.
|
|
160
|
-
if (own.length === 0)
|
|
173
|
+
if (own.length === 0) blankGuilds.push(command);
|
|
161
174
|
for (const guild of own)add({
|
|
162
175
|
guild
|
|
163
176
|
}, command);
|
|
@@ -171,6 +184,7 @@ const scopeKey = (scope)=>scope === 'global' ? 'global' : `guild-${scope.guild}`
|
|
|
171
184
|
}, command);
|
|
172
185
|
}
|
|
173
186
|
}
|
|
187
|
+
if (blankGuilds.length > 0) logger.warn(blankGuildsWarning(blankGuilds));
|
|
174
188
|
if (nowhere) {
|
|
175
189
|
const outcome = unregistered.length === 0 ? targets.has('global') ? 'only the entry point command is registered globally' : 'nothing is registered globally' : `${unregistered.join(', ')} ${unregistered.length === 1 ? 'is' : 'are'} not registered, rather than registered globally`;
|
|
176
190
|
logger.warn(`commands.guilds lists no guild id, as an unset environment variable leaves it, so ${outcome}. ` + `Set the guild ids, or remove commands.guilds to register globally.`);
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { BaseInteraction, Message } from 'discord.js';
|
|
3
|
-
import {
|
|
3
|
+
import { CooldownError, CooldownStoreError } from '../common/errors.js';
|
|
4
4
|
import { CooldownStore, MemoryCooldownStore } from '../common/cooldown-store.js';
|
|
5
5
|
import { Logger } from '../common/logger.js';
|
|
6
|
-
import { perHandler, sourcePrototype, stageClasses } from './guard-runner.js';
|
|
6
|
+
import { perHandler, methodStages, sourcePrototype, stageClasses } from './guard-runner.js';
|
|
7
7
|
import { META } from '../util/metadata-keys.js';
|
|
8
8
|
|
|
9
|
-
/** The cooldowns on a handler: each class's, base first, then the method's. */ const handlerCooldowns = perHandler((prototype, methodName)=>{
|
|
10
|
-
|
|
11
|
-
if (!source) return [];
|
|
9
|
+
/** The cooldowns on a handler: each class's, base first, then the method's and its overrides', base first. */ const handlerCooldowns = perHandler((prototype, methodName)=>{
|
|
10
|
+
if (!sourcePrototype(prototype, methodName)) return [];
|
|
12
11
|
return [
|
|
13
12
|
// Base first, as guards run
|
|
14
13
|
...stageClasses(prototype, methodName).flatMap((cls)=>Reflect.getOwnMetadata(META.classCooldowns, cls) ?? []),
|
|
15
|
-
...
|
|
14
|
+
...methodCooldowns(prototype, methodName)
|
|
16
15
|
];
|
|
17
16
|
});
|
|
18
17
|
/** The counted cooldowns of each list {@link handlerCooldowns} gives, which is the same for a handler. */ const countedCooldowns = new WeakMap();
|
|
@@ -53,9 +52,8 @@ import { META } from '../util/metadata-keys.js';
|
|
|
53
52
|
}
|
|
54
53
|
return counted;
|
|
55
54
|
}
|
|
56
|
-
/** The cooldowns declared on a method itself. */ function methodCooldowns(prototype, methodName) {
|
|
57
|
-
|
|
58
|
-
return source ? Reflect.getOwnMetadata(META.methodCooldowns, source, methodName) ?? [] : [];
|
|
55
|
+
/** The cooldowns declared on a method itself and on the methods it overrides, base first. */ function methodCooldowns(prototype, methodName) {
|
|
56
|
+
return methodStages(prototype, methodName, META.methodCooldowns);
|
|
59
57
|
}
|
|
60
58
|
/** Who and where a call came from: an interaction's or a message's user, server and channel. */ function caller(first) {
|
|
61
59
|
if (first instanceof BaseInteraction) return {
|
package/dist/esm/core/defer.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
-
import { perHandler,
|
|
2
|
+
import { perHandler, nearestMethodStage } from './guard-runner.js';
|
|
3
3
|
import { getMessageHandlers, getReactionHandlers, getAutocompleteHandlers } from '../decorator/controller.decorator.js';
|
|
4
4
|
import { getEventHandlers } from '../decorator/event.decorator.js';
|
|
5
5
|
import { META } from '../util/metadata-keys.js';
|
|
6
6
|
|
|
7
7
|
/** How long `@Defer({ mode: 'auto' })` waits for an answer by default. */ const AUTO_DEFER_AFTER_MS = 1500;
|
|
8
8
|
/** The latest `'auto'` acknowledges, counted from the interaction's creation: Discord allows three seconds. */ const AUTO_DEFER_LIMIT_MS = 2500;
|
|
9
|
-
/** The `@Defer` options of a handler,
|
|
10
|
-
const source = sourcePrototype(prototype, methodName);
|
|
11
|
-
return source ? Reflect.getOwnMetadata(META.deferOptions, source, methodName) : undefined;
|
|
12
|
-
});
|
|
9
|
+
/** The `@Defer` options of a handler: its own, or else those of the nearest method it overrides that has them. */ const handlerDefer = perHandler((prototype, methodName)=>nearestMethodStage(prototype, methodName, META.deferOptions));
|
|
13
10
|
/** The kind of non-interaction handler a method is, if any: `@Defer` cannot apply to one. */ function nonInteractionHandler(prototype, methodName) {
|
|
14
11
|
if (getMessageHandlers(prototype).some((handler)=>handler.method === methodName)) return 'message';
|
|
15
12
|
if (getReactionHandlers(prototype).some((handler)=>handler.method === methodName)) return 'reaction';
|