meocord 4.2.2 → 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +130 -0
- package/dist/cjs/_shared/{controller.decorator-U2NBWcoU.cjs → controller.decorator-hp5xdJU4.cjs} +512 -183
- package/dist/cjs/_shared/{startup-roots-lA5xkBFq.cjs → event-sources-DzqTODXg.cjs} +265 -80
- package/dist/cjs/_shared/{providers-B4MOFBrW.cjs → providers-BBvbnmLP.cjs} +780 -281
- package/dist/cjs/_shared/{sharded-cooldown-store-HTUb3Ii_.cjs → sharded-cooldown-store-B62g5O88.cjs} +11 -9
- package/dist/cjs/_shared/{translate-error-CbBHUu5P.cjs → translate-error-wsgfsUET.cjs} +1 -1
- package/dist/cjs/common/index.cjs +4 -104
- package/dist/cjs/core/index.cjs +432 -181
- package/dist/cjs/decorator/index.cjs +85 -120
- package/dist/cjs/testing/index.cjs +1664 -784
- package/dist/cli.json +1 -1
- package/dist/esm/bin/app-template/package.json.template +9 -9
- package/dist/esm/bin/app-template/src/main.ts.template +3 -2
- package/dist/esm/bin/generator.js +6 -4
- package/dist/esm/bin/helper/filter-generator.helper.js +6 -4
- package/dist/esm/bin/helper/guard-generator.helper.js +6 -4
- package/dist/esm/bin/helper/interceptor-generator.helper.js +6 -4
- package/dist/esm/bin/helper/observer-generator.helper.js +6 -4
- package/dist/esm/bin/helper/pipe-generator.helper.js +6 -4
- package/dist/esm/bin/helper/service-generator.helper.js +6 -4
- package/dist/esm/bin/meocord.js +106 -48
- package/dist/esm/build/native-addons.js +75 -19
- package/dist/esm/build/rsbuild-config.js +11 -3
- package/dist/esm/build/typed-externals.js +15 -2
- package/dist/esm/common/cooldown-store.js +38 -14
- package/dist/esm/common/deprecation.js +4 -1
- package/dist/esm/common/execution-context.js +3 -1
- package/dist/esm/common/explained-error.js +7 -3
- package/dist/esm/common/log-level.js +13 -9
- package/dist/esm/common/logger.js +4 -2
- package/dist/esm/common/meocord-text.js +11 -6
- package/dist/esm/common/redis-cooldown-store.js +3 -7
- package/dist/esm/common/response/flags.js +17 -1
- package/dist/esm/common/response/install-context.js +27 -16
- package/dist/esm/common/response/presenter.js +7 -3
- package/dist/esm/common/response/response-state.js +27 -5
- package/dist/esm/common/sharded-cooldown-store.js +11 -9
- package/dist/esm/common/theme.js +3 -1
- package/dist/esm/common/translator.js +17 -8
- package/dist/esm/core/autocomplete-routes.js +26 -0
- package/dist/esm/core/command-conflicts.js +15 -7
- package/dist/esm/core/command-registration.js +15 -1
- package/dist/esm/core/cooldown-runner.js +7 -9
- package/dist/esm/core/defer.js +2 -5
- package/dist/esm/core/dispatcher.js +83 -58
- package/dist/esm/core/event-requirements.js +67 -19
- package/dist/esm/core/event-sources.js +100 -0
- package/dist/esm/core/fallback.js +3 -1
- package/dist/esm/core/filter-runner.js +4 -4
- package/dist/esm/core/guard-runner.js +173 -52
- package/dist/esm/core/handler-input.js +24 -3
- package/dist/esm/core/handler-pipeline.js +44 -28
- package/dist/esm/core/handler-registry.js +51 -21
- package/dist/esm/core/input-runner.js +7 -8
- package/dist/esm/core/interceptor-runner.js +4 -5
- package/dist/esm/core/lifecycle-hooks.js +3 -8
- package/dist/esm/core/lifecycle-order.js +1 -15
- package/dist/esm/core/meocord-classes.js +14 -1
- package/dist/esm/core/meocord-factory.js +80 -53
- package/dist/esm/core/meocord.app.js +174 -89
- package/dist/esm/core/message-help.js +5 -9
- package/dist/esm/core/message-params.js +126 -46
- package/dist/esm/core/message-routes.js +225 -45
- package/dist/esm/core/message-words.js +30 -2
- package/dist/esm/core/observer-runner.js +2 -1
- package/dist/esm/core/process-state.js +19 -0
- package/dist/esm/core/providers.js +28 -3
- package/dist/esm/core/scalar-types.js +57 -16
- package/dist/esm/core/shard-context.js +11 -10
- package/dist/esm/core/shard-exit.js +3 -3
- package/dist/esm/core/shard-manager.js +16 -6
- package/dist/esm/core/startup-roots.js +28 -8
- package/dist/esm/core/theme-resolvers.js +26 -11
- package/dist/esm/core/theme-runtime.js +23 -13
- package/dist/esm/core/theme-scope.js +27 -29
- package/dist/esm/core/theme-validation.js +1 -1
- package/dist/esm/decorator/app.decorator.js +6 -1
- package/dist/esm/decorator/command-builder.decorator.js +14 -0
- package/dist/esm/decorator/controller-class.decorator.js +1 -1
- package/dist/esm/decorator/controller.decorator.js +48 -19
- package/dist/esm/decorator/defer.decorator.js +2 -2
- package/dist/esm/decorator/event.decorator.js +4 -5
- package/dist/esm/decorator/filter.decorator.js +2 -2
- package/dist/esm/decorator/guard.decorator.js +4 -61
- package/dist/esm/decorator/inject.decorator.js +4 -1
- package/dist/esm/decorator/interceptor.decorator.js +2 -2
- package/dist/esm/decorator/theme.decorator.js +4 -3
- package/dist/esm/decorator/validation.decorator.js +5 -5
- package/dist/esm/package.json.js +1 -1
- package/dist/esm/testing/catalog.js +29 -13
- package/dist/esm/testing/cooldown-store-suite.js +3 -3
- package/dist/esm/testing/discord-defaults.js +449 -355
- package/dist/esm/testing/inspect-handler.js +91 -10
- package/dist/esm/testing/meocord-testing-module.js +195 -77
- package/dist/esm/testing/mock-fn.js +8 -2
- package/dist/esm/testing/mock-interaction.js +639 -113
- package/dist/esm/testing/response-log.js +8 -0
- package/dist/esm/testing/response.js +7 -5
- package/dist/esm/testing/routing.js +48 -6
- package/dist/esm/testing/strict-mocks.js +1 -0
- package/dist/esm/testing/theme.js +3 -2
- package/dist/esm/util/common.util.js +30 -9
- package/dist/esm/util/generator-cli.util.js +6 -4
- package/dist/esm/util/inherited-env.util.js +72 -13
- package/dist/esm/util/json.util.js +10 -3
- package/dist/esm/util/meocord-cli.util.js +3 -2
- package/dist/esm/util/meocord-config-loader.util.js +14 -1
- package/dist/esm/util/meocord-config-validation.util.js +13 -3
- package/dist/esm/util/meocord-source-config.util.js +13 -3
- package/dist/esm/util/platform.util.js +3 -2
- package/dist/esm/util/refusal.util.js +29 -11
- package/dist/esm/util/registration-mode.util.js +14 -1
- package/dist/esm/util/timer-limit.util.js +4 -1
- package/dist/esm/util/tsconfig-paths.util.js +38 -7
- package/dist/esm/util/tsconfig.util.js +16 -58
- package/dist/esm/util/user-text.util.js +8 -4
- package/dist/esm/util/value.util.js +15 -4
- package/dist/types/common/index.d.cts +13 -9
- package/dist/types/common/index.d.ts +13 -9
- package/dist/types/core/index.d.cts +21 -11
- package/dist/types/core/index.d.ts +21 -11
- package/dist/types/decorator/index.d.cts +63 -63
- package/dist/types/decorator/index.d.ts +63 -63
- package/dist/types/{index-DKJclIi-.d.ts → index-Brkd6QGL.d.ts} +81 -35
- package/dist/types/{index-Dlf0ZZtS.d.cts → index-CqXImwF_.d.cts} +81 -35
- package/dist/types/interface/index.d.cts +1 -1
- package/dist/types/interface/index.d.ts +1 -1
- package/dist/types/{route-Bh1U13yy.d.ts → route-IQrK-MN2.d.cts} +11 -3
- package/dist/types/{route-Bh1U13yy.d.cts → route-bI-okq7g.d.ts} +11 -3
- package/dist/types/testing/index.d.cts +137 -54
- package/dist/types/testing/index.d.ts +137 -54
- package/dist/types/{theme-resolvers-CyEz9pnv.d.cts → theme-resolvers-AtgeaYqF.d.cts} +1 -1
- package/dist/types/{theme-resolvers-DA204O-q.d.ts → theme-resolvers-BgqysV8d.d.ts} +1 -1
- package/dist/types/{translator-Bg7hwxDF.d.cts → translator-D8aydlCE.d.ts} +12 -11
- package/dist/types/{translator-CeP4bCzp.d.ts → translator-iJhDlgwL.d.cts} +12 -11
- package/package.json +5 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { MessageUsageError } from '../common/errors.js';
|
|
2
2
|
import { usageIssue } from '../common/meocord-text.js';
|
|
3
3
|
import { MessageEntityRef, resolveRefs } from './message-entities.js';
|
|
4
|
-
import { splitWords, splitFlagWords } from './message-words.js';
|
|
4
|
+
import { caseKey, isLiteralFlag, splitWords, splitFlagWords, literalFlags } from './message-words.js';
|
|
5
5
|
import { lookupTable, duration, bool, number, choicesOf } from './scalar-types.js';
|
|
6
6
|
import { userWords } from '../util/user-text.util.js';
|
|
7
|
+
import { setOwn, ownValue, shadowInherited } from '../util/value.util.js';
|
|
7
8
|
|
|
8
9
|
/** The ID a mention or a bare snowflake names, for the mention's kind. */ function idOf(word, mention) {
|
|
9
10
|
return (mention.exec(word) ?? /^(\d{17,20})$/.exec(word))?.[1];
|
|
@@ -11,6 +12,10 @@ import { userWords } from '../util/user-text.util.js';
|
|
|
11
12
|
const USER_MENTION = /^<@!?(\d{17,20})>$/;
|
|
12
13
|
const ROLE_MENTION = /^<@&(\d{17,20})>$/;
|
|
13
14
|
const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
15
|
+
/** The server's role a word names: its mention or ID, else its name in any case. */ function roleNamed(guild, word) {
|
|
16
|
+
const id = idOf(word, ROLE_MENTION);
|
|
17
|
+
return id ? guild.roles.cache.get(id) : guild.roles.cache.find((candidate)=>candidate.name.toLowerCase() === word.toLowerCase());
|
|
18
|
+
}
|
|
14
19
|
/** The scalar types, each turning a word into its value or `undefined`, and the Discord ones, which resolve below. */ const BUILT_IN_TYPES = lookupTable({
|
|
15
20
|
string: (word)=>word,
|
|
16
21
|
int: (word)=>number(word, true),
|
|
@@ -41,7 +46,18 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
41
46
|
'channel'
|
|
42
47
|
]);
|
|
43
48
|
/** Whether a param type is found only in a server. */ const isGuildType = (type)=>GUILD_TYPES.has(type);
|
|
44
|
-
/** Whether a route's pattern takes a
|
|
49
|
+
/** Whether a route's pattern takes a `member`, `role` or `channel` param, optional or not. */ const takesGuildParam = (route)=>route.tokens.some((token)=>'param' in token && token.type !== undefined && GUILD_TYPES.has(token.type));
|
|
50
|
+
/**
|
|
51
|
+
* Whether a route works only in a server, as dispatch decides it: a `member`, `role` or `channel` param, optional or
|
|
52
|
+
* not, or a required flag of one of those types. An optional flag of those types refuses a DM only when it is given.
|
|
53
|
+
*/ const needsGuild = (route)=>takesGuildParam(route) || route.flags.some((flag)=>!flag.optional && flag.type !== undefined && GUILD_TYPES.has(flag.type));
|
|
54
|
+
/**
|
|
55
|
+
* Where a route works: its scope, narrowed to servers by a `member`, `role` or `channel` param, or required flag, as
|
|
56
|
+
* dispatch narrows it.
|
|
57
|
+
*/ function effectiveScope(route) {
|
|
58
|
+
if (route.scope !== 'any') return route.scope;
|
|
59
|
+
return needsGuild(route) ? 'guild' : 'any';
|
|
60
|
+
}
|
|
45
61
|
/** Whether a pattern's `{name:type}` names a type: built in, words to choose from, or one the app adds. */ function isKnownParamType(type, types) {
|
|
46
62
|
return type in BUILT_IN_TYPES || Boolean(choicesOf(type)) || appType(types, type) !== undefined;
|
|
47
63
|
}
|
|
@@ -121,14 +137,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
121
137
|
*/ function assertMessageScope(route, message, start) {
|
|
122
138
|
const inGuild = message.guildId !== null && message.guildId !== undefined;
|
|
123
139
|
if (route.scope === 'guild' && !inGuild) {
|
|
124
|
-
throw
|
|
125
|
-
usageIssue({
|
|
126
|
-
key: 'meocord.usage.serverOnly'
|
|
127
|
-
})
|
|
128
|
-
], {
|
|
129
|
-
serverOnly: true,
|
|
130
|
-
quiet: start === ''
|
|
131
|
-
});
|
|
140
|
+
throw serverOnly(usageOf(route, start), start === '');
|
|
132
141
|
}
|
|
133
142
|
if (route.scope === 'dm' && inGuild) {
|
|
134
143
|
throw new MessageUsageError(usageOf(route, start), [
|
|
@@ -141,6 +150,15 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
141
150
|
});
|
|
142
151
|
}
|
|
143
152
|
}
|
|
153
|
+
/** The usage error for a message sent outside a server to a command, or with a flag, that only a server has. */ const serverOnly = (usage, quiet)=>new MessageUsageError(usage, [
|
|
154
|
+
usageIssue({
|
|
155
|
+
key: 'meocord.usage.serverOnly'
|
|
156
|
+
})
|
|
157
|
+
], {
|
|
158
|
+
serverOnly: true,
|
|
159
|
+
quiet
|
|
160
|
+
});
|
|
161
|
+
/** Whether a message gives one of a route's flags of a type only a server has, such as `--in=#general`. */ const givesGuildFlag = (route, flags)=>flags.some((given)=>route.flags.some((flag)=>flag.type !== undefined && GUILD_TYPES.has(flag.type) && caseKey(flag.flag, route.caseSensitive) === caseKey(given.name, route.caseSensitive)));
|
|
144
162
|
/** Whether a value an app's `parse` returned is a ref to resolve after the guards. */ const isRef = (value)=>typeof value === 'object' && value !== null && 'cached' in value && typeof value.resolve === 'function';
|
|
145
163
|
/**
|
|
146
164
|
* The params a matched route's guards see, read from the words with no request to Discord: typed params and lists as
|
|
@@ -163,18 +181,20 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
163
181
|
const issues = [];
|
|
164
182
|
const items = [];
|
|
165
183
|
for (const token of route.tokens){
|
|
166
|
-
|
|
184
|
+
// Only a word the message gave is typed: an own one, never what Object.prototype has under the param's name
|
|
185
|
+
const word = 'param' in token ? ownValue(raw, token.param) : undefined;
|
|
186
|
+
if (!('param' in token) || token.type === undefined || word === undefined) continue;
|
|
167
187
|
if (!token.rest) {
|
|
168
188
|
items.push({
|
|
169
189
|
key: token.param,
|
|
170
190
|
label: token.param,
|
|
171
191
|
type: token.type,
|
|
172
|
-
word
|
|
192
|
+
word
|
|
173
193
|
});
|
|
174
194
|
continue;
|
|
175
195
|
}
|
|
176
|
-
const words = splitWords(
|
|
177
|
-
params
|
|
196
|
+
const words = splitWords(word);
|
|
197
|
+
setOwn(params, token.param, new Array(words.length));
|
|
178
198
|
words.forEach(({ value }, index)=>items.push({
|
|
179
199
|
key: token.param,
|
|
180
200
|
label: token.param,
|
|
@@ -184,19 +204,12 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
184
204
|
}));
|
|
185
205
|
}
|
|
186
206
|
const flagIssues = route.flags.length > 0 ? readFlags(route, message, start, params, items) : [];
|
|
207
|
+
// readFlags takes out each flag's key, so one left out whose name Object.prototype has reads as left out again
|
|
208
|
+
if (route.inheritedNames.length > 0) shadowInherited(params, route.inheritedNames);
|
|
187
209
|
const guild = message.guild;
|
|
188
|
-
if (!guild && (needsGuild(route) || items.some((item)=>GUILD_TYPES.has(item.type))))
|
|
189
|
-
throw new MessageUsageError(usage, [
|
|
190
|
-
usageIssue({
|
|
191
|
-
key: 'meocord.usage.serverOnly'
|
|
192
|
-
})
|
|
193
|
-
], {
|
|
194
|
-
serverOnly: true,
|
|
195
|
-
quiet
|
|
196
|
-
});
|
|
197
|
-
}
|
|
210
|
+
if (!guild && (needsGuild(route) || items.some((item)=>GUILD_TYPES.has(item.type)))) throw serverOnly(usage, quiet);
|
|
198
211
|
const put = (item, value)=>{
|
|
199
|
-
if (item.index === undefined) params
|
|
212
|
+
if (item.index === undefined) setOwn(params, item.key, value);
|
|
200
213
|
else params[item.key][item.index] = value;
|
|
201
214
|
};
|
|
202
215
|
const refs = [];
|
|
@@ -233,8 +246,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
233
246
|
}
|
|
234
247
|
} else if (type === 'role') {
|
|
235
248
|
// Roles are cached with the Guilds intent, so a role is known here or not at all
|
|
236
|
-
const
|
|
237
|
-
const role = id ? guild.roles.cache.get(id) : guild.roles.cache.find((candidate)=>candidate.name.toLowerCase() === word.toLowerCase());
|
|
249
|
+
const role = roleNamed(guild, word);
|
|
238
250
|
if (role) {
|
|
239
251
|
refer(item, 'role', new MessageEntityRef('role', role.id, message.client, guild));
|
|
240
252
|
continue;
|
|
@@ -274,9 +286,9 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
274
286
|
const params = {
|
|
275
287
|
...parsed.params
|
|
276
288
|
};
|
|
277
|
-
for (const [key, value] of Object.entries(params))if (Array.isArray(value)) params
|
|
289
|
+
for (const [key, value] of Object.entries(params))if (Array.isArray(value)) setOwn(params, key, [
|
|
278
290
|
...value
|
|
279
|
-
];
|
|
291
|
+
]);
|
|
280
292
|
const issues = [];
|
|
281
293
|
for (const slot of refs){
|
|
282
294
|
const value = slot.ref instanceof MessageEntityRef ? found.get(slot.ref) : await slot.ref.resolve();
|
|
@@ -284,7 +296,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
284
296
|
issues.push(missingEntity(slot));
|
|
285
297
|
continue;
|
|
286
298
|
}
|
|
287
|
-
if (slot.index === undefined) params
|
|
299
|
+
if (slot.index === undefined) setOwn(params, slot.key, value);
|
|
288
300
|
else params[slot.key][slot.index] = value;
|
|
289
301
|
}
|
|
290
302
|
if (issues.length > 0) throw new MessageUsageError(usage, issues, {
|
|
@@ -350,7 +362,11 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
350
362
|
return {
|
|
351
363
|
parseArgs: async (args)=>{
|
|
352
364
|
assertMessageScope(route, message, start);
|
|
353
|
-
if (given !== undefined)
|
|
365
|
+
if (given !== undefined) {
|
|
366
|
+
// A DM to a command only a server has is told so before what its words lack, as one whose words fit is
|
|
367
|
+
if (!message.guild && (needsGuild(route) || givesGuildFlag(route, given.flags))) throw serverOnly(usageOf(route, start), start === '');
|
|
368
|
+
throw new MessageUsageError(usageOf(route, start), missingParams(route, given, types, message.guild));
|
|
369
|
+
}
|
|
354
370
|
if (!hasTypedParams(route)) return args;
|
|
355
371
|
parsed = await parseMessageParams(route, params, message, start, types);
|
|
356
372
|
return [
|
|
@@ -401,7 +417,11 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
401
417
|
const given = new Map();
|
|
402
418
|
const unknown = new Set();
|
|
403
419
|
const text = (message.content ?? '').trim();
|
|
404
|
-
|
|
420
|
+
const split = splitFlagWords(text.slice(start.length));
|
|
421
|
+
// A flag the pattern's own `--` word took is that word, and neither one of its flags nor unknown
|
|
422
|
+
const literal = literalFlags(route.tokens, route.caseSensitive, split.words, split.flags);
|
|
423
|
+
for (const [index, flag] of split.flags.entries()){
|
|
424
|
+
if (literal.has(index)) continue;
|
|
405
425
|
const declared = route.flags.find((candidate)=>key(candidate.flag) === key(flag.name));
|
|
406
426
|
if (declared) given.set(declared.flag, flag);
|
|
407
427
|
else if (!unknown.has(flag.name)) {
|
|
@@ -427,7 +447,7 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
427
447
|
value: userWords(value)
|
|
428
448
|
}
|
|
429
449
|
}, flag.flag));
|
|
430
|
-
else params
|
|
450
|
+
else setOwn(params, flag.flag, on);
|
|
431
451
|
} else if (!given.has(flag.flag)) {
|
|
432
452
|
if (!flag.optional) issues.push(usageIssue({
|
|
433
453
|
key: 'meocord.usage.missing',
|
|
@@ -455,24 +475,84 @@ const CHANNEL_MENTION = /^<#(\d{17,20})>$/;
|
|
|
455
475
|
return issues;
|
|
456
476
|
}
|
|
457
477
|
/**
|
|
458
|
-
* What is wrong with a command whose words a message named but whose pattern it does not fit,
|
|
459
|
-
*
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
478
|
+
* What is wrong with a command whose words a message named but whose pattern it does not fit, read word by word
|
|
479
|
+
* against the pattern: each word or required param it left out, a word given another in its place, else words
|
|
480
|
+
* beyond what the pattern takes, else the first word its param's built-in type does not take; and then each required
|
|
481
|
+
* flag it left out, as a message whose words fit is told. With none of those true, there are none, and the usage stands
|
|
482
|
+
* alone.
|
|
483
|
+
*/ function missingParams(route, given, types, guild) {
|
|
484
|
+
const { words, flags } = given;
|
|
485
|
+
const missing = [];
|
|
486
|
+
// The flags given bare in place of the pattern's own `--` words, which are those words and set no flag
|
|
487
|
+
const literal = new Set();
|
|
488
|
+
let unfit;
|
|
489
|
+
let w = 0;
|
|
490
|
+
for (const token of route.tokens){
|
|
491
|
+
if ('literal' in token) {
|
|
492
|
+
if (w < words.length && caseKey(words[w].value, route.caseSensitive) === caseKey(token.literal, route.caseSensitive)) {
|
|
493
|
+
w++;
|
|
494
|
+
continue;
|
|
467
495
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
496
|
+
// A pattern's own `--` word given bare in its place takes no word; another word in its place is not taken
|
|
497
|
+
const word = flags.find((flag)=>!literal.has(flag) && isLiteralFlag(flag, token.literal, w, route.caseSensitive));
|
|
498
|
+
if (word) literal.add(word);
|
|
499
|
+
else missing.push(usageIssue({
|
|
500
|
+
key: 'meocord.usage.missing',
|
|
501
|
+
params: {
|
|
502
|
+
param: token.literal
|
|
503
|
+
}
|
|
504
|
+
}));
|
|
505
|
+
} else if (w === words.length) {
|
|
506
|
+
if (!token.optional) missing.push(usageIssue({
|
|
507
|
+
key: 'meocord.usage.missing',
|
|
508
|
+
params: {
|
|
509
|
+
param: token.param
|
|
510
|
+
}
|
|
511
|
+
}, token.param));
|
|
512
|
+
} else if (token.rest) {
|
|
513
|
+
w = words.length;
|
|
514
|
+
} else {
|
|
515
|
+
// Words fill the params in order, as the usage shows them
|
|
516
|
+
const word = words[w++].value;
|
|
517
|
+
const type = token.type;
|
|
518
|
+
if (!unfit && type !== undefined && isUnfit(type, word, route.caseSensitive, guild)) {
|
|
519
|
+
unfit = {
|
|
520
|
+
key: token.param,
|
|
521
|
+
label: token.param,
|
|
522
|
+
type,
|
|
523
|
+
word
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// With none of these, the reason may be one MeoCord has no text for, as a role's name the matcher takes only as a
|
|
529
|
+
// mention or an ID
|
|
530
|
+
const reasons = missing.length > 0 ? missing : w < words.length ? [
|
|
471
531
|
usageIssue({
|
|
472
532
|
key: 'meocord.usage.tooManyWords'
|
|
473
533
|
})
|
|
534
|
+
] : unfit ? [
|
|
535
|
+
wrongType(unfit, types)
|
|
536
|
+
] : [];
|
|
537
|
+
// So one reply names all the message lacks, rather than the flags only once its words fit
|
|
538
|
+
const missingFlags = route.flags.filter((flag)=>flag.type !== undefined && !flag.optional && !flags.some((sent)=>!literal.has(sent) && caseKey(sent.name, route.caseSensitive) === caseKey(flag.flag, route.caseSensitive))).map((flag)=>usageIssue({
|
|
539
|
+
key: 'meocord.usage.missing',
|
|
540
|
+
params: {
|
|
541
|
+
param: `--${flag.flag}`
|
|
542
|
+
}
|
|
543
|
+
}, flag.flag));
|
|
544
|
+
return [
|
|
545
|
+
...reasons,
|
|
546
|
+
...missingFlags
|
|
474
547
|
];
|
|
475
548
|
}
|
|
549
|
+
/**
|
|
550
|
+
* Whether a param of a type is known not to take a word: a built-in type or words to choose from that the word doesn't
|
|
551
|
+
* fit. A role is also found by its name, so only a word that names none of the server's roles is unfit.
|
|
552
|
+
*/ function isUnfit(type, word, caseSensitive, guild) {
|
|
553
|
+
if (type === 'role') return guild !== null && roleNamed(guild, word) === undefined;
|
|
554
|
+
return (type in BUILT_IN_TYPES || Boolean(choicesOf(type))) && !fitsParamType(type, word, caseSensitive);
|
|
555
|
+
}
|
|
476
556
|
/** Whether a route's params need resolving: it declares a type for one, or has flags. */ const hasTypedParams = (route)=>route.flags.length > 0 || route.tokens.some((token)=>'param' in token && token.type !== undefined);
|
|
477
557
|
|
|
478
|
-
export { BUILT_IN_TYPES, assertMessageScope, fetchMessageParams, fitsParamType, hasTypedParams, isGuildType, isKnownParamType, messageCommandHooks, missingParams, needsGuild, paramTypeLabel, parseMessageParams, primaryFirst, subcommandUsageError, usageOf };
|
|
558
|
+
export { BUILT_IN_TYPES, assertMessageScope, effectiveScope, fetchMessageParams, fitsParamType, hasTypedParams, isGuildType, isKnownParamType, messageCommandHooks, missingParams, needsGuild, paramTypeLabel, parseMessageParams, primaryFirst, subcommandUsageError, takesGuildParam, usageOf };
|