@seedcord/gateway 0.1.0-next.5 → 0.1.0
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/dist/index.cjs +345 -1038
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +131 -428
- package/dist/index.mjs +350 -1034
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -33,11 +33,9 @@ let _seedcord_errors = require("@seedcord/errors");
|
|
|
33
33
|
let _seedcord_errors_internal = require("@seedcord/errors/internal");
|
|
34
34
|
let _seedcord_core = require("@seedcord/core");
|
|
35
35
|
let _seedcord_logger = require("@seedcord/logger");
|
|
36
|
+
let discord_js = require("discord.js");
|
|
36
37
|
let _discordjs_builders = require("@discordjs/builders");
|
|
37
38
|
let _seedcord_utils_internal = require("@seedcord/utils/internal");
|
|
38
|
-
let discord_js = require("discord.js");
|
|
39
|
-
let node_crypto = require("node:crypto");
|
|
40
|
-
node_crypto = __toESM(node_crypto, 1);
|
|
41
39
|
let _seedcord_core_plugin = require("@seedcord/core/plugin");
|
|
42
40
|
let _seedcord_core_node_internal = require("@seedcord/core/node/internal");
|
|
43
41
|
let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
|
|
@@ -46,10 +44,11 @@ let envapt = require("envapt");
|
|
|
46
44
|
let chalk = require("chalk");
|
|
47
45
|
chalk = __toESM(chalk, 1);
|
|
48
46
|
let envapt_legacy = require("envapt/legacy");
|
|
49
|
-
let node_path = require("node:path");
|
|
50
47
|
let _seedcord_core_hmr = require("@seedcord/core/hmr");
|
|
51
48
|
let _seedcord_utils = require("@seedcord/utils");
|
|
52
49
|
let _seedcord_utils_node = require("@seedcord/utils/node");
|
|
50
|
+
let node_crypto = require("node:crypto");
|
|
51
|
+
node_crypto = __toESM(node_crypto, 1);
|
|
53
52
|
|
|
54
53
|
//#region src/logger-bootstrap.ts
|
|
55
54
|
(0, _seedcord_logger_node.installNodeDefaults)();
|
|
@@ -242,59 +241,6 @@ var CannotAssignBotRole = class extends _seedcord_core.Notice {
|
|
|
242
241
|
return { components: [new _seedcord_core_internal.NoticeCard("I cannot assign a managed role.").component] };
|
|
243
242
|
}
|
|
244
243
|
};
|
|
245
|
-
var HasDangerousPermissions = class extends _seedcord_core.Notice {
|
|
246
|
-
subject;
|
|
247
|
-
dangerousPerms;
|
|
248
|
-
customLead;
|
|
249
|
-
constructor(message, subject, dangerousPerms) {
|
|
250
|
-
super(message ?? "A dangerous permission is enabled.");
|
|
251
|
-
this.subject = subject;
|
|
252
|
-
this.dangerousPerms = dangerousPerms;
|
|
253
|
-
this.customLead = message;
|
|
254
|
-
}
|
|
255
|
-
render() {
|
|
256
|
-
const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
|
|
257
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`${this.customLead ?? `${this.subject} has the following permission entries that must not be enabled:`}\n\n${bullets}`).component] };
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
var UserNotFound = class extends _seedcord_core.Notice {
|
|
261
|
-
userArg;
|
|
262
|
-
constructor(userArg) {
|
|
263
|
-
super(`User not found: ${userArg}`);
|
|
264
|
-
this.userArg = userArg;
|
|
265
|
-
}
|
|
266
|
-
render() {
|
|
267
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`User probably doesn't exist or was deleted.\n**User Argument:** \`${this.userArg}\`\nPlease check the user ID and try again. Only pass valid user IDs as the argument.`, "User Not Found").component] };
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
var UserNotInGuild = class extends _seedcord_core.Notice {
|
|
271
|
-
constructor(message = "User is not in the guild.") {
|
|
272
|
-
super(message);
|
|
273
|
-
}
|
|
274
|
-
render() {
|
|
275
|
-
return { components: [new _seedcord_core_internal.NoticeCard(this.message).component] };
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
var RoleDoesNotExist = class extends _seedcord_core.Notice {
|
|
279
|
-
roleId;
|
|
280
|
-
constructor(message, roleId) {
|
|
281
|
-
super(message);
|
|
282
|
-
this.roleId = roleId;
|
|
283
|
-
}
|
|
284
|
-
render() {
|
|
285
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`The role with ID \`${this.roleId}\` does not exist.`).component] };
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
var CouldNotFindChannel = class extends _seedcord_core.Notice {
|
|
289
|
-
channelId;
|
|
290
|
-
constructor(message, channelId) {
|
|
291
|
-
super(message);
|
|
292
|
-
this.channelId = channelId;
|
|
293
|
-
}
|
|
294
|
-
render() {
|
|
295
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`Could not find channel with ID \`${this.channelId}\`. It could also be that the channel is not a text channel.`).component] };
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
244
|
|
|
299
245
|
//#endregion
|
|
300
246
|
//#region src/bot/gates/catalog/Nsfw.ts
|
|
@@ -332,20 +278,15 @@ function Nsfw(options) {
|
|
|
332
278
|
|
|
333
279
|
//#endregion
|
|
334
280
|
//#region src/bot/injectors/EmojiInjector.ts
|
|
335
|
-
const emojiStorage =
|
|
336
|
-
function isEmojiTuple(v) {
|
|
337
|
-
return Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "string";
|
|
338
|
-
}
|
|
281
|
+
const emojiStorage = (0, _seedcord_core_internal.accessorStore)();
|
|
339
282
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* @see {@link EmojiMap}
|
|
283
|
+
* The bot's resolved emojis, keyed by {@link EmojiMap}. Filled by {@link EmojiInjector} during startup.
|
|
284
|
+
* A read before that throws.
|
|
343
285
|
*/
|
|
344
|
-
const Emojis = emojiStorage;
|
|
286
|
+
const Emojis = (0, _seedcord_core_internal.guardedAccessor)("Emojis", emojiStorage);
|
|
345
287
|
/**
|
|
346
|
-
* Loads and injects emojis based on bot configuration.
|
|
347
|
-
*
|
|
348
|
-
* For emojis injected from specific guilds, ensure that the Guilds intent is provided in client options.
|
|
288
|
+
* Loads and injects emojis based on bot configuration. A guild emoji requires the Guilds intent in the client
|
|
289
|
+
* options.
|
|
349
290
|
*
|
|
350
291
|
* @internal
|
|
351
292
|
*/
|
|
@@ -365,7 +306,7 @@ var EmojiInjector = class {
|
|
|
365
306
|
await this.core.bot.client.application?.emojis.fetch();
|
|
366
307
|
const failures = [];
|
|
367
308
|
const fetchedGuilds = /* @__PURE__ */ new Set();
|
|
368
|
-
for (const [key, value] of Object.entries(configEmojis)) if (isEmojiTuple(value)) await this.resolveTuple(key, value, failures, fetchedGuilds);
|
|
309
|
+
for (const [key, value] of Object.entries(configEmojis)) if ((0, _seedcord_core_internal.isEmojiTuple)(value)) await this.resolveTuple(key, value, failures, fetchedGuilds);
|
|
369
310
|
else if (typeof value === "string") this.resolveString(key, value, failures);
|
|
370
311
|
else failures.push(` - "${key}" has an invalid value (expected a name or [name, guildId])`);
|
|
371
312
|
if (failures.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigEmojiUnresolved, [failures.length, failures.join("\n")]);
|
|
@@ -398,126 +339,10 @@ var EmojiInjector = class {
|
|
|
398
339
|
emojiStorage[key] = appEmoji;
|
|
399
340
|
}
|
|
400
341
|
clearEmojis() {
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
//#endregion
|
|
406
|
-
//#region src/bot/injectors/CommandMentionInjector.ts
|
|
407
|
-
const mentionStorage = {};
|
|
408
|
-
/**
|
|
409
|
-
* Ready-to-send command mention strings, keyed by {@link SlashOptionRegistry}.
|
|
410
|
-
*
|
|
411
|
-
* A value is a clickable `</name:id>` when the command's id resolved from the deploy, otherwise the plain
|
|
412
|
-
* `/name` text. Populated by {@link CommandMentionInjector} after each deploy.
|
|
413
|
-
*/
|
|
414
|
-
const CommandMentions = mentionStorage;
|
|
415
|
-
const MULTI_GUILD = Symbol("seedcord:commandmentions:multi-guild");
|
|
416
|
-
/**
|
|
417
|
-
* Turns each deployed slash route into a clickable command mention exposed through {@link CommandMentions}.
|
|
418
|
-
*
|
|
419
|
-
* Consumes the ids {@link DeployResult} carries, so it runs after a deploy rather than resolving from a name
|
|
420
|
-
* cache (discord.js offers none for command ids). A global command is clickable everywhere by its app-wide id.
|
|
421
|
-
* A guild command deployed to one guild is clickable by that guild's id. A command deployed to two or more
|
|
422
|
-
* guilds mints a different id per guild, so one accessor key cannot resolve it, and it falls back to plain
|
|
423
|
-
* `/name` text with one warn. On a non-owning shard a guild command also falls back to plain text.
|
|
424
|
-
*
|
|
425
|
-
* @internal
|
|
426
|
-
*/
|
|
427
|
-
var CommandMentionInjector = class {
|
|
428
|
-
core;
|
|
429
|
-
logger = new _seedcord_logger.Logger("CommandMentions", { channel: "bot" });
|
|
430
|
-
constructor(core) {
|
|
431
|
-
this.core = core;
|
|
432
|
-
}
|
|
433
|
-
inject(deploy) {
|
|
434
|
-
this.clear();
|
|
435
|
-
const globalIds = this.indexByName(deploy.global);
|
|
436
|
-
const guildIds = this.indexGuilds(deploy.guilds);
|
|
437
|
-
const warned = /* @__PURE__ */ new Set();
|
|
438
|
-
let clickable = 0;
|
|
439
|
-
for (const command of this.allSlashBuilders()) {
|
|
440
|
-
const id = this.resolveId(command.name, globalIds, guildIds, warned);
|
|
441
|
-
for (const leaf of (0, _seedcord_utils_internal.routeLeavesOf)(command.toJSON())) {
|
|
442
|
-
mentionStorage[leaf.route] = id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route);
|
|
443
|
-
if (id) clickable++;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
this.logger.utils.summary("Linked mentions", {
|
|
447
|
-
clickable,
|
|
448
|
-
plain: Object.keys(mentionStorage).length - clickable
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
allSlashBuilders() {
|
|
452
|
-
const registry = this.core.bot.commands;
|
|
453
|
-
if (!registry) return [];
|
|
454
|
-
const slash = [...registry.globalCommands, ...registry.guildCommands.values()].flat().filter((command) => command instanceof _discordjs_builders.SlashCommandBuilder);
|
|
455
|
-
return [...new Set(slash)];
|
|
456
|
-
}
|
|
457
|
-
indexByName(collection) {
|
|
458
|
-
const map = /* @__PURE__ */ new Map();
|
|
459
|
-
for (const command of collection.values()) map.set(command.name, command.id);
|
|
460
|
-
return map;
|
|
461
|
-
}
|
|
462
|
-
indexGuilds(guilds) {
|
|
463
|
-
const map = /* @__PURE__ */ new Map();
|
|
464
|
-
for (const collection of guilds.values()) for (const command of collection.values()) {
|
|
465
|
-
const existing = map.get(command.name);
|
|
466
|
-
if (existing === void 0) map.set(command.name, command.id);
|
|
467
|
-
else if (existing !== command.id) map.set(command.name, MULTI_GUILD);
|
|
468
|
-
}
|
|
469
|
-
return map;
|
|
470
|
-
}
|
|
471
|
-
resolveId(name, globalIds, guildIds, warned) {
|
|
472
|
-
const global = globalIds.get(name);
|
|
473
|
-
if (global) return global;
|
|
474
|
-
const guild = guildIds.get(name);
|
|
475
|
-
if (guild === void 0) return void 0;
|
|
476
|
-
if (guild === MULTI_GUILD) {
|
|
477
|
-
if (!warned.has(name)) {
|
|
478
|
-
this.logger.warn(`${name} is deployed to multiple guilds, falling back to plain text (not clickable).`);
|
|
479
|
-
warned.add(name);
|
|
480
|
-
}
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
return guild;
|
|
484
|
-
}
|
|
485
|
-
toMention(route, id) {
|
|
486
|
-
const [name, middle, sub] = route.split("/");
|
|
487
|
-
if (name && middle && sub) return (0, discord_js.chatInputApplicationCommandMention)(name, middle, sub, id);
|
|
488
|
-
if (name && middle) return (0, discord_js.chatInputApplicationCommandMention)(name, middle, id);
|
|
489
|
-
return (0, discord_js.chatInputApplicationCommandMention)(name ?? route, id);
|
|
490
|
-
}
|
|
491
|
-
toPlain(route) {
|
|
492
|
-
return `/${route.replaceAll("/", " ")}`;
|
|
493
|
-
}
|
|
494
|
-
clear() {
|
|
495
|
-
for (const key of Object.keys(mentionStorage)) Reflect.deleteProperty(mentionStorage, key);
|
|
342
|
+
(0, _seedcord_core_internal.clearStore)(emojiStorage);
|
|
496
343
|
}
|
|
497
344
|
};
|
|
498
345
|
|
|
499
|
-
//#endregion
|
|
500
|
-
//#region src/bot/utilities/channels/fetchText.ts
|
|
501
|
-
/**
|
|
502
|
-
* Fetches and validates a text channel. Refuses when the channel doesn't exist or isn't a text channel.
|
|
503
|
-
*
|
|
504
|
-
* @param client - The Discord client instance
|
|
505
|
-
* @param channelId - Channel ID or TextChannel instance
|
|
506
|
-
* @returns Promise resolving to the text channel
|
|
507
|
-
*/
|
|
508
|
-
async function fetchText(client, channelId) {
|
|
509
|
-
if (channelId instanceof discord_js.TextChannel) return channelId;
|
|
510
|
-
let channel = client.channels.cache.get(channelId);
|
|
511
|
-
if (!channel) try {
|
|
512
|
-
channel = await client.channels.fetch(channelId);
|
|
513
|
-
} catch (err) {
|
|
514
|
-
if (err instanceof discord_js.DiscordAPIError && err.code === discord_js.RESTJSONErrorCodes.UnknownChannel) throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
515
|
-
throw err;
|
|
516
|
-
}
|
|
517
|
-
if (channel?.isTextBased()) return channel;
|
|
518
|
-
throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
346
|
//#endregion
|
|
522
347
|
//#region src/bot/notices/utils.ts
|
|
523
348
|
function mentionFor(subject) {
|
|
@@ -528,48 +353,37 @@ function mentionFor(subject) {
|
|
|
528
353
|
//#endregion
|
|
529
354
|
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
530
355
|
function checkPermissions(a, b, c, d, e) {
|
|
531
|
-
const { for: pFor, in: pIn, scope, inverse = false, missingNotice
|
|
356
|
+
const { for: pFor, in: pIn, scope, inverse = false, missingNotice, dangerousNotice } = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
|
|
532
357
|
for: a,
|
|
533
358
|
in: b,
|
|
534
359
|
scope: c,
|
|
535
360
|
inverse: d ?? false,
|
|
536
361
|
...e
|
|
537
362
|
} : a;
|
|
538
|
-
const Missing = missingCtor ?? _seedcord_core_internal.MissingPermissions;
|
|
539
|
-
const Dangerous = dangerousCtor ?? HasDangerousPermissions;
|
|
540
|
-
const subject = mentionFor(pFor);
|
|
541
363
|
const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
364
|
+
(0, _seedcord_core.assertPermissions)({
|
|
365
|
+
subject: mentionFor(pFor),
|
|
366
|
+
permissions: perms.bitfield,
|
|
367
|
+
scope,
|
|
368
|
+
inverse,
|
|
369
|
+
...missingNotice && { missingNotice },
|
|
370
|
+
...dangerousNotice && { dangerousNotice }
|
|
371
|
+
});
|
|
550
372
|
}
|
|
551
373
|
|
|
552
374
|
//#endregion
|
|
553
375
|
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
554
376
|
/**
|
|
555
|
-
* Checks
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
* @param target - Guild or text channel to check in
|
|
559
|
-
* @param scope - Permission bits to validate
|
|
560
|
-
* @param inverse - Whether to check for absence of the given permissions
|
|
561
|
-
* @param errors - Optional custom error constructors
|
|
377
|
+
* Checks the bot's own permissions in a {@link Guild} or {@link TextChannel}. Refuses when a permission in
|
|
378
|
+
* `scope` is missing, or with `inverse` when one is present. When the bot member is uncached, the refusal
|
|
379
|
+
* reports every permission in `scope` as missing.
|
|
562
380
|
*
|
|
563
381
|
* @example
|
|
564
382
|
* ```ts
|
|
565
383
|
* import { PermissionFlagsBits } from 'discord.js';
|
|
566
384
|
* import { checkBotPermissions } from '@seedcord/gateway';
|
|
567
385
|
*
|
|
568
|
-
*
|
|
569
|
-
* checkBotPermissions(textChannel, [
|
|
570
|
-
* PermissionFlagsBits.SendMessages,
|
|
571
|
-
* PermissionFlagsBits.ViewChannel
|
|
572
|
-
* ]);
|
|
386
|
+
* checkBotPermissions(textChannel, [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel]);
|
|
573
387
|
* ```
|
|
574
388
|
*/
|
|
575
389
|
function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
@@ -592,26 +406,17 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
592
406
|
}
|
|
593
407
|
|
|
594
408
|
//#endregion
|
|
595
|
-
//#region src/bot/utilities/
|
|
596
|
-
|
|
597
|
-
* Gets the bot's managed role in a guild.
|
|
598
|
-
*
|
|
599
|
-
* @param guild - The guild to get the bot role from
|
|
600
|
-
* @returns The bot's managed role in the guild
|
|
601
|
-
* @throws A **SeedcordError** if the client user is unavailable or if the bot role is missing
|
|
602
|
-
*/
|
|
603
|
-
function getBotRole(guild) {
|
|
409
|
+
//#region src/bot/utilities/permissions/hasPermsToAssign.ts
|
|
410
|
+
function botRoleOf(guild) {
|
|
604
411
|
const botRole = guild.roles.botRoleFor(guild.client.user);
|
|
605
412
|
if (!botRole) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreBotRoleMissing, [guild.id]);
|
|
606
413
|
return botRole;
|
|
607
414
|
}
|
|
608
|
-
|
|
609
|
-
//#endregion
|
|
610
|
-
//#region src/bot/utilities/permissions/hasPermsToAssign.ts
|
|
611
415
|
/**
|
|
612
|
-
* Validates
|
|
416
|
+
* Validates that the bot can assign a target role. Refuses when the target role is above the bot's, is managed, or the bot lacks Manage Roles.
|
|
613
417
|
*
|
|
614
418
|
* @param roleOrOptions - Target role or complete options for the check
|
|
419
|
+
* @throws A `Notice` on refusal, or a `SeedcordError` when the bot has no role in the guild's role cache.
|
|
615
420
|
*
|
|
616
421
|
* @example
|
|
617
422
|
* ```ts
|
|
@@ -639,138 +444,123 @@ function hasPermsToAssign(roleOrOptions) {
|
|
|
639
444
|
const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
|
|
640
445
|
const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
|
|
641
446
|
const MissingNotice = errors?.missingNotice ?? _seedcord_core_internal.MissingPermissions;
|
|
642
|
-
const botRole =
|
|
447
|
+
const botRole = botRoleOf(targetRole.guild);
|
|
643
448
|
if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
|
|
644
449
|
if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
|
|
645
450
|
checkBotPermissions(targetRole.guild, [discord_js.PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
|
|
646
451
|
}
|
|
647
452
|
|
|
648
453
|
//#endregion
|
|
649
|
-
//#region src/bot/
|
|
650
|
-
|
|
651
|
-
return err instanceof discord_js.DiscordAPIError && err.code === discord_js.RESTJSONErrorCodes.UnknownRole;
|
|
652
|
-
}
|
|
653
|
-
async function fetchRoleInGuild(guild, roleId) {
|
|
654
|
-
const cached = guild.roles.cache.get(roleId);
|
|
655
|
-
if (cached) return cached;
|
|
656
|
-
try {
|
|
657
|
-
return await guild.roles.fetch(roleId);
|
|
658
|
-
} catch (err) {
|
|
659
|
-
if (isUnknownRole(err)) throw new RoleDoesNotExist("Role not found in specified guild", roleId);
|
|
660
|
-
throw err;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
async function scanGuildsForRole(client, roleId) {
|
|
664
|
-
const cached = client.guilds.cache.map((guild) => guild.roles.cache.get(roleId)).find(Boolean);
|
|
665
|
-
if (cached) return cached;
|
|
666
|
-
for (const guild of client.guilds.cache.values()) try {
|
|
667
|
-
const role = await guild.roles.fetch(roleId);
|
|
668
|
-
if (role) return role;
|
|
669
|
-
} catch (err) {
|
|
670
|
-
if (isUnknownRole(err)) continue;
|
|
671
|
-
throw err;
|
|
672
|
-
}
|
|
673
|
-
return null;
|
|
674
|
-
}
|
|
454
|
+
//#region src/bot/confirm/reserved.ts
|
|
455
|
+
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
675
456
|
/**
|
|
676
|
-
*
|
|
457
|
+
* The reserved confirm/cancel codec. It has no route, so the dispatcher ignores its clicks and only the
|
|
458
|
+
* in-process collector reads them. Do not mint a real component from it.
|
|
677
459
|
*
|
|
678
|
-
* @
|
|
679
|
-
* @param roleId - The role ID to fetch
|
|
680
|
-
* @returns Promise resolving to the role
|
|
460
|
+
* @internal
|
|
681
461
|
*/
|
|
682
|
-
|
|
683
|
-
if (!roleId) throw new RoleDoesNotExist("Role ID is null or undefined", roleId);
|
|
684
|
-
const role = clientOrGuild instanceof discord_js.Guild ? await fetchRoleInGuild(clientOrGuild, roleId) : await scanGuildsForRole(clientOrGuild, roleId);
|
|
685
|
-
if (!role) throw new RoleDoesNotExist("Role not found", roleId);
|
|
686
|
-
return role;
|
|
687
|
-
}
|
|
462
|
+
const CONFIRM_DEF = new _seedcord_core.CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
|
|
688
463
|
|
|
689
464
|
//#endregion
|
|
690
|
-
//#region src/bot/
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
465
|
+
//#region src/bot/confirm/getConfirmation.ts
|
|
466
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
467
|
+
const CONFIRM_IDS = {
|
|
468
|
+
confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
|
|
469
|
+
cancel: CONFIRM_DEF.encode({ choice: "cancel" })
|
|
470
|
+
};
|
|
471
|
+
var ConfirmButtons = class extends _seedcord_core.RowComponent {
|
|
472
|
+
constructor(options) {
|
|
473
|
+
super("button");
|
|
474
|
+
this.instance.addComponents(new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? discord_js.ButtonStyle.Danger), new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(discord_js.ButtonStyle.Secondary));
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var ConfirmPromptCard = class extends _seedcord_core.BuilderComponent {
|
|
478
|
+
constructor(text, options) {
|
|
479
|
+
super("container");
|
|
480
|
+
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
function defaultPrompt(text, options) {
|
|
484
|
+
return { components: [new ConfirmPromptCard(text, options).component] };
|
|
705
485
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
async function fetchManyGuildMembers(guild, userIds) {
|
|
717
|
-
return (await Promise.allSettled(userIds.map((userId) => fetchGuildMember(guild, userId)))).filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
486
|
+
async function collectChoice(message, userId, timeoutMs) {
|
|
487
|
+
try {
|
|
488
|
+
return await message.awaitMessageComponent({
|
|
489
|
+
componentType: discord_js.ComponentType.Button,
|
|
490
|
+
time: timeoutMs,
|
|
491
|
+
filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
|
|
492
|
+
});
|
|
493
|
+
} catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
718
496
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
|
|
497
|
+
async function settle(sender, message, outcome) {
|
|
498
|
+
if (outcome === void 0) {
|
|
499
|
+
await sender.delete(message).catch(() => void 0);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
503
|
+
}
|
|
504
|
+
async function getConfirmation(handler, prompt, options) {
|
|
505
|
+
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
506
|
+
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
507
|
+
const interaction = handler.getEvent();
|
|
508
|
+
const sender = handler.getSender();
|
|
509
|
+
const message = await sender.send(response, { ephemeral });
|
|
510
|
+
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
511
|
+
if (!winner) {
|
|
512
|
+
await settle(sender, message, options?.onTimeout);
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
await winner.deferUpdate().catch(() => void 0);
|
|
516
|
+
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
517
|
+
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
518
|
+
return confirmed;
|
|
738
519
|
}
|
|
739
520
|
|
|
740
521
|
//#endregion
|
|
741
|
-
//#region src/
|
|
522
|
+
//#region src/handlers/BaseHandler.ts
|
|
742
523
|
/**
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
* @param client - The Discord client instance
|
|
746
|
-
* @param userIds - Array of Discord user IDs
|
|
747
|
-
* @param options - Optional overrides threaded to each {@link fetchUser} call
|
|
748
|
-
* @returns Promise resolving to array of successfully fetched users
|
|
524
|
+
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
525
|
+
* gate hook.
|
|
749
526
|
*/
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
527
|
+
var BaseHandler = class extends _seedcord_core.BaseHandler {
|
|
528
|
+
/** @internal */
|
|
529
|
+
getEvent() {
|
|
530
|
+
return this.event;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
753
533
|
|
|
754
534
|
//#endregion
|
|
755
|
-
//#region src/
|
|
535
|
+
//#region src/handlers/interaction/InteractionMiddleware.ts
|
|
756
536
|
/**
|
|
757
|
-
*
|
|
537
|
+
* Base class for interaction middleware
|
|
538
|
+
*
|
|
539
|
+
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
540
|
+
* Unlike handlers, middleware should not send responses directly.
|
|
758
541
|
*
|
|
759
|
-
* @
|
|
760
|
-
* @param rolesToRemove - Array of role IDs to remove from the member
|
|
761
|
-
* @param member - The guild member to update
|
|
762
|
-
* @returns Promise that resolves when roles are updated
|
|
542
|
+
* @typeParam Repliable - The interaction type this middleware processes
|
|
763
543
|
*/
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
await member.roles.set([...updated]);
|
|
770
|
-
}
|
|
544
|
+
var InteractionMiddleware = class extends BaseHandler {
|
|
545
|
+
constructor(event, core) {
|
|
546
|
+
super(event, core, void 0, "interactions");
|
|
547
|
+
}
|
|
548
|
+
};
|
|
771
549
|
|
|
772
550
|
//#endregion
|
|
773
551
|
//#region src/bot/ReplySender.ts
|
|
552
|
+
function isReplyFile(file) {
|
|
553
|
+
return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
|
|
554
|
+
}
|
|
555
|
+
function toDjsFile(file) {
|
|
556
|
+
if (!isReplyFile(file)) return file;
|
|
557
|
+
return {
|
|
558
|
+
attachment: Buffer.from(file.data),
|
|
559
|
+
name: file.name,
|
|
560
|
+
...file.description !== void 0 && { description: file.description },
|
|
561
|
+
...file.title !== void 0 && { title: file.title }
|
|
562
|
+
};
|
|
563
|
+
}
|
|
774
564
|
function seedState(interaction) {
|
|
775
565
|
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
776
566
|
if (interaction.replied) return "replied";
|
|
@@ -853,7 +643,7 @@ var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
|
853
643
|
components: reply.components,
|
|
854
644
|
flags,
|
|
855
645
|
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
856
|
-
...reply.files && { files:
|
|
646
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
857
647
|
};
|
|
858
648
|
}
|
|
859
649
|
editOptions(response) {
|
|
@@ -862,7 +652,7 @@ var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
|
862
652
|
components: reply.components,
|
|
863
653
|
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
864
654
|
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
865
|
-
...reply.files && { files:
|
|
655
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
866
656
|
};
|
|
867
657
|
}
|
|
868
658
|
updateOptions(response) {
|
|
@@ -871,7 +661,7 @@ var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
|
871
661
|
components: reply.components,
|
|
872
662
|
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
873
663
|
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
874
|
-
...reply.files && { files:
|
|
664
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
875
665
|
};
|
|
876
666
|
}
|
|
877
667
|
createdMessage(result) {
|
|
@@ -879,266 +669,6 @@ var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
|
879
669
|
}
|
|
880
670
|
};
|
|
881
671
|
|
|
882
|
-
//#endregion
|
|
883
|
-
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
884
|
-
/**
|
|
885
|
-
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
886
|
-
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
887
|
-
* interaction and a generated registry key always match.
|
|
888
|
-
*
|
|
889
|
-
* @internal
|
|
890
|
-
*/
|
|
891
|
-
function slashRouteOf(interaction) {
|
|
892
|
-
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
893
|
-
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
894
|
-
return (0, _seedcord_utils_internal.buildSlashRoute)(interaction.commandName, subcommand, group);
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
//#endregion
|
|
898
|
-
//#region src/miscellaneous/extractErrorResponse.ts
|
|
899
|
-
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling", { channel: "errors" });
|
|
900
|
-
/**
|
|
901
|
-
* Processes an error into a user-facing reply and routes its report.
|
|
902
|
-
*
|
|
903
|
-
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
904
|
-
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
905
|
-
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
906
|
-
* threaded into both the reply and the bus payload.
|
|
907
|
-
*/
|
|
908
|
-
function extractErrorResponse(error, core, origin) {
|
|
909
|
-
const uuid = node_crypto.randomUUID();
|
|
910
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
911
|
-
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
912
|
-
uuid,
|
|
913
|
-
developerUsername
|
|
914
|
-
};
|
|
915
|
-
if (error instanceof _seedcord_core.Notice) {
|
|
916
|
-
if (error.report) reportFault(error, core, origin, uuid);
|
|
917
|
-
return {
|
|
918
|
-
uuid,
|
|
919
|
-
response: error.render(ctx)
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
reportRawFault(error, core, origin, uuid);
|
|
923
|
-
const override = core.config.errors?.defaultError;
|
|
924
|
-
return {
|
|
925
|
-
uuid,
|
|
926
|
-
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
function withThrottle(core, origin, error, publish) {
|
|
930
|
-
const throttle = _seedcord_core_internal.FaultThrottle.for(core);
|
|
931
|
-
const key = faultKey(origin, error);
|
|
932
|
-
if (!throttle.shouldReport(key)) {
|
|
933
|
-
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
publish();
|
|
937
|
-
throttle.markReported(key);
|
|
938
|
-
}
|
|
939
|
-
function reportFault(denial, core, origin, uuid) {
|
|
940
|
-
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
941
|
-
withThrottle(core, origin, denial, () => {
|
|
942
|
-
if (origin.interaction) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
943
|
-
denial,
|
|
944
|
-
uuid,
|
|
945
|
-
source: buildInteractionSource(origin.interaction)
|
|
946
|
-
});
|
|
947
|
-
else if (origin.event) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
948
|
-
denial,
|
|
949
|
-
uuid,
|
|
950
|
-
source: buildEventSource(origin.event, origin)
|
|
951
|
-
});
|
|
952
|
-
else core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
953
|
-
uuid,
|
|
954
|
-
error: denial,
|
|
955
|
-
...scalarActors(origin),
|
|
956
|
-
metadata: metadataFor(origin)
|
|
957
|
-
});
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
function causeLine(error) {
|
|
961
|
-
const { cause } = error;
|
|
962
|
-
if (!Error.isError(cause)) return "";
|
|
963
|
-
const [first] = cause.message.split("\n");
|
|
964
|
-
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
965
|
-
}
|
|
966
|
-
function reportRawFault(error, core, origin, uuid) {
|
|
967
|
-
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
968
|
-
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
969
|
-
withThrottle(core, origin, error, () => {
|
|
970
|
-
core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
971
|
-
uuid,
|
|
972
|
-
error,
|
|
973
|
-
...scalarActors(origin),
|
|
974
|
-
metadata: metadataFor(origin)
|
|
975
|
-
});
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
function scalarActors(origin) {
|
|
979
|
-
return {
|
|
980
|
-
guild: origin.guild ? {
|
|
981
|
-
id: origin.guild.id,
|
|
982
|
-
name: origin.guild.name
|
|
983
|
-
} : void 0,
|
|
984
|
-
user: origin.user ? {
|
|
985
|
-
id: origin.user.id,
|
|
986
|
-
username: origin.user.username
|
|
987
|
-
} : void 0
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
function metadataFor(origin) {
|
|
991
|
-
if (origin.event) return {
|
|
992
|
-
eventName: origin.event.name,
|
|
993
|
-
handler: origin.event.handler,
|
|
994
|
-
args: origin.event.args
|
|
995
|
-
};
|
|
996
|
-
return origin.metadata;
|
|
997
|
-
}
|
|
998
|
-
function buildEventSource(event, origin) {
|
|
999
|
-
return {
|
|
1000
|
-
kind: "event",
|
|
1001
|
-
eventName: event.name,
|
|
1002
|
-
handler: event.handler,
|
|
1003
|
-
userId: origin.user?.id ?? null,
|
|
1004
|
-
guildId: origin.guild?.id ?? null,
|
|
1005
|
-
channelId: event.channelId,
|
|
1006
|
-
raw: event.args
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
function faultKey(origin, error) {
|
|
1010
|
-
const name = error instanceof _seedcord_core.Notice ? error.name : error instanceof discord_js.DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1011
|
-
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1012
|
-
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1013
|
-
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1014
|
-
return `autocomplete:${name}`;
|
|
1015
|
-
}
|
|
1016
|
-
/** The stable route id of a repliable interaction, its slash route path, command name, or customId prefix. */
|
|
1017
|
-
function interactionRoute(interaction) {
|
|
1018
|
-
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1019
|
-
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1020
|
-
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
1021
|
-
return "interaction";
|
|
1022
|
-
}
|
|
1023
|
-
function buildInteractionSource(interaction) {
|
|
1024
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1025
|
-
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1026
|
-
return {
|
|
1027
|
-
kind: "interaction",
|
|
1028
|
-
interactionKind: interactionKind(interaction),
|
|
1029
|
-
command,
|
|
1030
|
-
customId,
|
|
1031
|
-
userId: interaction.user.id,
|
|
1032
|
-
guildId: interaction.guildId,
|
|
1033
|
-
channelId: interaction.channelId,
|
|
1034
|
-
interactionId: interaction.id,
|
|
1035
|
-
raw: interaction
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
function interactionKind(interaction) {
|
|
1039
|
-
if (interaction.isChatInputCommand()) return "slash";
|
|
1040
|
-
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1041
|
-
if (interaction.isButton()) return "button";
|
|
1042
|
-
if (interaction.isAnySelectMenu()) return "select";
|
|
1043
|
-
return "modal";
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
//#endregion
|
|
1047
|
-
//#region src/bot/confirm/reserved.ts
|
|
1048
|
-
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
1049
|
-
/**
|
|
1050
|
-
* The reserved confirm/cancel codec. It has no route, so the dispatcher ignores its clicks and only the
|
|
1051
|
-
* in-process collector reads them. Do not mint a real component from it.
|
|
1052
|
-
*
|
|
1053
|
-
* @internal
|
|
1054
|
-
*/
|
|
1055
|
-
const CONFIRM_DEF = new _seedcord_core.CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
|
|
1056
|
-
|
|
1057
|
-
//#endregion
|
|
1058
|
-
//#region src/bot/confirm/getConfirmation.ts
|
|
1059
|
-
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
1060
|
-
const CONFIRM_IDS = {
|
|
1061
|
-
confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
|
|
1062
|
-
cancel: CONFIRM_DEF.encode({ choice: "cancel" })
|
|
1063
|
-
};
|
|
1064
|
-
var ConfirmButtons = class extends _seedcord_core.RowComponent {
|
|
1065
|
-
constructor(options) {
|
|
1066
|
-
super("button");
|
|
1067
|
-
this.instance.addComponents(new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? discord_js.ButtonStyle.Danger), new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(discord_js.ButtonStyle.Secondary));
|
|
1068
|
-
}
|
|
1069
|
-
};
|
|
1070
|
-
var ConfirmPromptCard = class extends _seedcord_core.BuilderComponent {
|
|
1071
|
-
constructor(text, options) {
|
|
1072
|
-
super("container");
|
|
1073
|
-
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
function defaultPrompt(text, options) {
|
|
1077
|
-
return { components: [new ConfirmPromptCard(text, options).component] };
|
|
1078
|
-
}
|
|
1079
|
-
async function collectChoice(message, userId, timeoutMs) {
|
|
1080
|
-
try {
|
|
1081
|
-
return await message.awaitMessageComponent({
|
|
1082
|
-
componentType: discord_js.ComponentType.Button,
|
|
1083
|
-
time: timeoutMs,
|
|
1084
|
-
filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
|
|
1085
|
-
});
|
|
1086
|
-
} catch {
|
|
1087
|
-
return null;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
async function settle(sender, message, outcome) {
|
|
1091
|
-
if (outcome === void 0) {
|
|
1092
|
-
await sender.delete(message).catch(() => void 0);
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
1096
|
-
}
|
|
1097
|
-
async function getConfirmation(interaction, prompt, options) {
|
|
1098
|
-
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
1099
|
-
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
1100
|
-
const sender = new ReplySender(interaction, interactionRoute(interaction));
|
|
1101
|
-
const message = await sender.send(response, { ephemeral });
|
|
1102
|
-
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
1103
|
-
if (!winner) {
|
|
1104
|
-
await settle(sender, message, options?.onTimeout);
|
|
1105
|
-
return false;
|
|
1106
|
-
}
|
|
1107
|
-
await winner.deferUpdate().catch(() => void 0);
|
|
1108
|
-
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
1109
|
-
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
1110
|
-
return confirmed;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
//#endregion
|
|
1114
|
-
//#region src/handlers/BaseHandler.ts
|
|
1115
|
-
/**
|
|
1116
|
-
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
1117
|
-
* gate hook.
|
|
1118
|
-
*/
|
|
1119
|
-
var BaseHandler = class extends _seedcord_core.BaseHandler {
|
|
1120
|
-
/** @internal */
|
|
1121
|
-
getEvent() {
|
|
1122
|
-
return this.event;
|
|
1123
|
-
}
|
|
1124
|
-
};
|
|
1125
|
-
|
|
1126
|
-
//#endregion
|
|
1127
|
-
//#region src/handlers/interaction/InteractionMiddleware.ts
|
|
1128
|
-
/**
|
|
1129
|
-
* Base class for interaction middleware
|
|
1130
|
-
*
|
|
1131
|
-
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
1132
|
-
* Unlike handlers, middleware should not send responses directly.
|
|
1133
|
-
*
|
|
1134
|
-
* @typeParam Repliable - The interaction type this middleware processes
|
|
1135
|
-
*/
|
|
1136
|
-
var InteractionMiddleware = class extends BaseHandler {
|
|
1137
|
-
constructor(event, core) {
|
|
1138
|
-
super(event, core, void 0, "interactions");
|
|
1139
|
-
}
|
|
1140
|
-
};
|
|
1141
|
-
|
|
1142
672
|
//#endregion
|
|
1143
673
|
//#region src/handlers/RepliableHandler.ts
|
|
1144
674
|
/**
|
|
@@ -1184,6 +714,21 @@ var InteractionHandler = class extends RepliableHandler {
|
|
|
1184
714
|
}
|
|
1185
715
|
};
|
|
1186
716
|
|
|
717
|
+
//#endregion
|
|
718
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
719
|
+
/**
|
|
720
|
+
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
721
|
+
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
722
|
+
* interaction and a generated registry key always match.
|
|
723
|
+
*
|
|
724
|
+
* @internal
|
|
725
|
+
*/
|
|
726
|
+
function slashRouteOf(interaction) {
|
|
727
|
+
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
728
|
+
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
729
|
+
return (0, _seedcord_utils_internal.buildSlashRoute)(interaction.commandName, subcommand, group);
|
|
730
|
+
}
|
|
731
|
+
|
|
1187
732
|
//#endregion
|
|
1188
733
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
1189
734
|
/**
|
|
@@ -1503,7 +1048,22 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1503
1048
|
* }
|
|
1504
1049
|
* ```
|
|
1505
1050
|
*/
|
|
1506
|
-
var ModalHandler = class extends ComponentHandler {
|
|
1051
|
+
var ModalHandler = class extends ComponentHandler {
|
|
1052
|
+
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
1053
|
+
update(response) {
|
|
1054
|
+
this.ensureSourceMessage("update");
|
|
1055
|
+
return super.update(response);
|
|
1056
|
+
}
|
|
1057
|
+
/** Acknowledge the submit without changing the source message. Throws when a command opened the modal. */
|
|
1058
|
+
deferUpdate() {
|
|
1059
|
+
this.ensureSourceMessage("deferUpdate");
|
|
1060
|
+
return super.deferUpdate();
|
|
1061
|
+
}
|
|
1062
|
+
ensureSourceMessage(method) {
|
|
1063
|
+
if (this.event.isFromMessage()) return;
|
|
1064
|
+
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1507
1067
|
|
|
1508
1068
|
//#endregion
|
|
1509
1069
|
//#region src/handlers/interaction/components/SelectMenuHandler.ts
|
|
@@ -1617,138 +1177,14 @@ var EventMiddleware = class extends BaseHandler {
|
|
|
1617
1177
|
}
|
|
1618
1178
|
/**
|
|
1619
1179
|
* The event that fired this middleware. Read it on a catchall or multi-event middleware, where `this.event`
|
|
1620
|
-
* is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
|
|
1621
|
-
* `this.event` directly and does not need this.
|
|
1622
|
-
*/
|
|
1623
|
-
get eventName() {
|
|
1624
|
-
if (this.firedEvent === void 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMiddlewareNameUnavailable);
|
|
1625
|
-
return this.firedEvent;
|
|
1626
|
-
}
|
|
1627
|
-
};
|
|
1628
|
-
|
|
1629
|
-
//#endregion
|
|
1630
|
-
//#region src/pagination/controls.ts
|
|
1631
|
-
const DEFAULT_LABEL = {
|
|
1632
|
-
first: "First",
|
|
1633
|
-
prev: "Prev",
|
|
1634
|
-
indicator: "",
|
|
1635
|
-
next: "Next",
|
|
1636
|
-
last: "Last"
|
|
1637
|
-
};
|
|
1638
|
-
const CONTROL_SLOT = {
|
|
1639
|
-
first: 0,
|
|
1640
|
-
prev: 1,
|
|
1641
|
-
indicator: 2,
|
|
1642
|
-
next: 3,
|
|
1643
|
-
last: 4
|
|
1644
|
-
};
|
|
1645
|
-
const MAX_BUTTONS_PER_ROW = 5;
|
|
1646
|
-
var Controls = class {
|
|
1647
|
-
cursor;
|
|
1648
|
-
view;
|
|
1649
|
-
constructor(cursor, view) {
|
|
1650
|
-
this.cursor = cursor;
|
|
1651
|
-
this.view = view;
|
|
1652
|
-
}
|
|
1653
|
-
button(key, cosmetics) {
|
|
1654
|
-
const { target, disabled } = this.navState(key);
|
|
1655
|
-
const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
|
|
1656
|
-
const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
|
|
1657
|
-
const button = new _discordjs_builders.ButtonBuilder().setCustomId(this.cursor.encode({
|
|
1658
|
-
page: Math.min(Math.max(0, target), _seedcord_core_internal.PAGE_MAX),
|
|
1659
|
-
slot: CONTROL_SLOT[key]
|
|
1660
|
-
})).setStyle(cosmetics?.style ?? discord_js.ButtonStyle.Secondary).setDisabled(disabled);
|
|
1661
|
-
if (label !== void 0) button.setLabel(label);
|
|
1662
|
-
if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
|
|
1663
|
-
return button;
|
|
1664
|
-
}
|
|
1665
|
-
navState(key) {
|
|
1666
|
-
const { page, totalPages, hasPrev, hasNext } = this.view;
|
|
1667
|
-
const knownTotal = totalPages !== void 0;
|
|
1668
|
-
return {
|
|
1669
|
-
target: {
|
|
1670
|
-
first: 0,
|
|
1671
|
-
prev: page - 1,
|
|
1672
|
-
indicator: page,
|
|
1673
|
-
next: page + 1,
|
|
1674
|
-
last: knownTotal ? totalPages - 1 : page
|
|
1675
|
-
}[key],
|
|
1676
|
-
disabled: {
|
|
1677
|
-
first: !hasPrev,
|
|
1678
|
-
prev: !hasPrev,
|
|
1679
|
-
indicator: true,
|
|
1680
|
-
next: !hasNext || page + 1 > _seedcord_core_internal.PAGE_MAX,
|
|
1681
|
-
last: !hasNext || !knownTotal || totalPages - 1 > _seedcord_core_internal.PAGE_MAX
|
|
1682
|
-
}[key]
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
|
-
row(...keys) {
|
|
1686
|
-
if (keys.length === 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationEmptyControls);
|
|
1687
|
-
if (keys.length > MAX_BUTTONS_PER_ROW) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
|
|
1688
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1689
|
-
for (const key of keys) {
|
|
1690
|
-
if (seen.has(key)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.PaginationDuplicateControls, [key]);
|
|
1691
|
-
seen.add(key);
|
|
1692
|
-
}
|
|
1693
|
-
return new _discordjs_builders.ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
|
|
1694
|
-
}
|
|
1695
|
-
indicatorText() {
|
|
1696
|
-
const { page, totalPages } = this.view;
|
|
1697
|
-
return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
|
|
1698
|
-
}
|
|
1699
|
-
};
|
|
1700
|
-
|
|
1701
|
-
//#endregion
|
|
1702
|
-
//#region src/pagination/render.ts
|
|
1703
|
-
var PageContainer = class extends _seedcord_core.BuilderComponent {
|
|
1704
|
-
constructor(view, renderItem, controlRow) {
|
|
1705
|
-
super("container");
|
|
1706
|
-
const base = view.page * view.perPage;
|
|
1707
|
-
let lines = [];
|
|
1708
|
-
const flush = () => {
|
|
1709
|
-
if (lines.length === 0) return;
|
|
1710
|
-
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(lines.join("\n")));
|
|
1711
|
-
lines = [];
|
|
1712
|
-
};
|
|
1713
|
-
view.items.forEach((item, offset) => {
|
|
1714
|
-
const rendered = renderItem(item, base + offset);
|
|
1715
|
-
if (typeof rendered === "string") {
|
|
1716
|
-
lines.push(rendered);
|
|
1717
|
-
return;
|
|
1718
|
-
}
|
|
1719
|
-
flush();
|
|
1720
|
-
this.instance.addSectionComponents(rendered.component);
|
|
1721
|
-
});
|
|
1722
|
-
flush();
|
|
1723
|
-
this.instance.addActionRowComponents(controlRow);
|
|
1180
|
+
* is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
|
|
1181
|
+
* `this.event` directly and does not need this.
|
|
1182
|
+
*/
|
|
1183
|
+
get eventName() {
|
|
1184
|
+
if (this.firedEvent === void 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.EventMiddlewareNameUnavailable);
|
|
1185
|
+
return this.firedEvent;
|
|
1724
1186
|
}
|
|
1725
1187
|
};
|
|
1726
|
-
function toReplyResponse(renderable) {
|
|
1727
|
-
if (typeof renderable === "string") return { components: [new _discordjs_builders.TextDisplayBuilder().setContent(renderable)] };
|
|
1728
|
-
if (Array.isArray(renderable)) return { components: renderable };
|
|
1729
|
-
return renderable;
|
|
1730
|
-
}
|
|
1731
|
-
const ARRAY_KEYS = [
|
|
1732
|
-
"first",
|
|
1733
|
-
"prev",
|
|
1734
|
-
"indicator",
|
|
1735
|
-
"next",
|
|
1736
|
-
"last"
|
|
1737
|
-
];
|
|
1738
|
-
const CURSOR_KEYS = [
|
|
1739
|
-
"prev",
|
|
1740
|
-
"indicator",
|
|
1741
|
-
"next"
|
|
1742
|
-
];
|
|
1743
|
-
/**
|
|
1744
|
-
* Build a page's V2 reply. A `render` override builds the whole tree, otherwise the default container lists
|
|
1745
|
-
* the items and appends the controls (all five for a known total, prev/indicator/next for a cursor source).
|
|
1746
|
-
*/
|
|
1747
|
-
function renderPage(view, cursor, config) {
|
|
1748
|
-
const controls = new Controls(cursor, view);
|
|
1749
|
-
if (config.render) return toReplyResponse(config.render(view, controls));
|
|
1750
|
-
return { components: [new PageContainer(view, config.renderItem ?? String, controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS)).component] };
|
|
1751
|
-
}
|
|
1752
1188
|
|
|
1753
1189
|
//#endregion
|
|
1754
1190
|
//#region src/pagination/Paginator.ts
|
|
@@ -1757,7 +1193,7 @@ function contextOf(interaction, core) {
|
|
|
1757
1193
|
interaction,
|
|
1758
1194
|
user: interaction.user,
|
|
1759
1195
|
guild: interaction.guild,
|
|
1760
|
-
|
|
1196
|
+
core
|
|
1761
1197
|
};
|
|
1762
1198
|
}
|
|
1763
1199
|
/**
|
|
@@ -1779,15 +1215,11 @@ function contextOf(interaction, core) {
|
|
|
1779
1215
|
* export class BansNav extends Bans.Handler {}
|
|
1780
1216
|
* ```
|
|
1781
1217
|
*/
|
|
1782
|
-
var Paginator = class {
|
|
1783
|
-
/** The page cursor, pass it to your `@ButtonRoute`. */
|
|
1784
|
-
cursor;
|
|
1218
|
+
var Paginator = class extends _seedcord_core.PaginatorBase {
|
|
1785
1219
|
/** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
|
|
1786
1220
|
Handler;
|
|
1787
|
-
config;
|
|
1788
1221
|
constructor(config) {
|
|
1789
|
-
|
|
1790
|
-
this.config = config;
|
|
1222
|
+
super(config);
|
|
1791
1223
|
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
1792
1224
|
this.Handler = class Nav extends ButtonHandler {
|
|
1793
1225
|
async execute() {
|
|
@@ -1798,7 +1230,8 @@ var Paginator = class {
|
|
|
1798
1230
|
};
|
|
1799
1231
|
}
|
|
1800
1232
|
/**
|
|
1801
|
-
* Render page 0 and send it through the handler's sender,
|
|
1233
|
+
* Render page 0 and send it through the handler's sender, which picks reply, edit, or followUp from its
|
|
1234
|
+
* ack state.
|
|
1802
1235
|
*
|
|
1803
1236
|
* @param handler - The handler starting the paginator, normally `this`.
|
|
1804
1237
|
*/
|
|
@@ -1807,34 +1240,18 @@ var Paginator = class {
|
|
|
1807
1240
|
const response = await this.page(contextOf(interaction, handler.core), 0);
|
|
1808
1241
|
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1809
1242
|
}
|
|
1810
|
-
/** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
|
|
1811
|
-
async page(ctx, n) {
|
|
1812
|
-
return renderPage(await this.config.source.page(ctx, n), this.cursor, this.config);
|
|
1813
|
-
}
|
|
1814
1243
|
};
|
|
1815
1244
|
|
|
1816
1245
|
//#endregion
|
|
1817
1246
|
//#region src/pagination/sources.ts
|
|
1818
|
-
const DEFAULT_PER_PAGE = 10;
|
|
1819
1247
|
/**
|
|
1820
1248
|
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
1821
|
-
* page
|
|
1822
|
-
*
|
|
1249
|
+
* page, so the real total is known (a real last button, "Page X of Y"). Cache inside your loader if the
|
|
1250
|
+
* load is expensive.
|
|
1823
1251
|
*
|
|
1824
1252
|
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1825
1253
|
*/
|
|
1826
|
-
var ArraySource = class {
|
|
1827
|
-
load;
|
|
1828
|
-
perPage;
|
|
1829
|
-
constructor(load, opts) {
|
|
1830
|
-
this.load = load;
|
|
1831
|
-
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
1832
|
-
if (!Number.isSafeInteger(this.perPage) || this.perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
1833
|
-
}
|
|
1834
|
-
async page(ctx, n) {
|
|
1835
|
-
return (0, _seedcord_core.paginate)(await this.load(ctx), n, this.perPage);
|
|
1836
|
-
}
|
|
1837
|
-
};
|
|
1254
|
+
var ArraySource = class extends _seedcord_core.ArraySource {};
|
|
1838
1255
|
/**
|
|
1839
1256
|
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1840
1257
|
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
@@ -1843,26 +1260,7 @@ var ArraySource = class {
|
|
|
1843
1260
|
*
|
|
1844
1261
|
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1845
1262
|
*/
|
|
1846
|
-
var CursorSource = class {
|
|
1847
|
-
fetch;
|
|
1848
|
-
perPage;
|
|
1849
|
-
constructor(fetch, opts) {
|
|
1850
|
-
this.fetch = fetch;
|
|
1851
|
-
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
1852
|
-
if (!Number.isSafeInteger(this.perPage) || this.perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
1853
|
-
}
|
|
1854
|
-
async page(ctx, n) {
|
|
1855
|
-
const page = Math.max(0, Math.trunc(n));
|
|
1856
|
-
const { items, hasNext } = await this.fetch(ctx, page, this.perPage);
|
|
1857
|
-
return {
|
|
1858
|
-
items: [...items],
|
|
1859
|
-
page,
|
|
1860
|
-
perPage: this.perPage,
|
|
1861
|
-
hasPrev: page > 0,
|
|
1862
|
-
hasNext
|
|
1863
|
-
};
|
|
1864
|
-
}
|
|
1865
|
-
};
|
|
1263
|
+
var CursorSource = class extends _seedcord_core.CursorSource {};
|
|
1866
1264
|
|
|
1867
1265
|
//#endregion
|
|
1868
1266
|
//#region src/subscribers/index.ts
|
|
@@ -1886,236 +1284,12 @@ var WebhookLog = class extends _seedcord_core.WebhookLog {};
|
|
|
1886
1284
|
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
1887
1285
|
*
|
|
1888
1286
|
* A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
|
|
1889
|
-
* `this.core`
|
|
1287
|
+
* `this.core` is typed to the shared members only.
|
|
1890
1288
|
*
|
|
1891
|
-
* @typeParam Opts - The transport
|
|
1289
|
+
* @typeParam Opts - The transport and runtime the plugin declares.
|
|
1892
1290
|
*/
|
|
1893
1291
|
var Plugin = class extends _seedcord_core_plugin.Plugin {};
|
|
1894
1292
|
|
|
1895
|
-
//#endregion
|
|
1896
|
-
//#region src/bot/utilities/miscellaneous/contextMenuLeaves.ts
|
|
1897
|
-
/**
|
|
1898
|
-
* Collects the names of every registered context-menu command across a set of command builders, split into a
|
|
1899
|
-
* user set and a message set. The same builder may appear more than once (a guild command is pushed once per
|
|
1900
|
-
* guild), and slash commands carry no context-menu name, so both collapse here. The split mirrors the
|
|
1901
|
-
* controller's two name-keyed maps, so the boot guard checks each name against its own kind.
|
|
1902
|
-
*
|
|
1903
|
-
* @internal
|
|
1904
|
-
*/
|
|
1905
|
-
function contextMenuLeaves(commands) {
|
|
1906
|
-
const user = /* @__PURE__ */ new Set();
|
|
1907
|
-
const message = /* @__PURE__ */ new Set();
|
|
1908
|
-
for (const command of commands) {
|
|
1909
|
-
if (!(command instanceof _discordjs_builders.ContextMenuCommandBuilder)) continue;
|
|
1910
|
-
if (command.type === discord_js.ApplicationCommandType.User) user.add(command.name);
|
|
1911
|
-
else message.add(command.name);
|
|
1912
|
-
}
|
|
1913
|
-
return {
|
|
1914
|
-
user,
|
|
1915
|
-
message
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
//#endregion
|
|
1920
|
-
//#region src/bot/utilities/miscellaneous/slashRouteLeaves.ts
|
|
1921
|
-
/**
|
|
1922
|
-
* Collects the route keys of every executable slash leaf across a set of command builders, deduplicated. The
|
|
1923
|
-
* same builder may appear more than once (a guild command is pushed once per guild), and context-menu
|
|
1924
|
-
* commands carry no slash route, so both collapse here. Route keys come from the same {@link routeLeavesOf}
|
|
1925
|
-
* that `seedcord codegen` reads, so the boot-time check compares against the generated registry's keys.
|
|
1926
|
-
*
|
|
1927
|
-
* @internal
|
|
1928
|
-
*/
|
|
1929
|
-
function slashRouteLeaves(commands) {
|
|
1930
|
-
const leaves = /* @__PURE__ */ new Set();
|
|
1931
|
-
for (const command of commands) {
|
|
1932
|
-
if (!(command instanceof _discordjs_builders.SlashCommandBuilder)) continue;
|
|
1933
|
-
for (const leaf of (0, _seedcord_utils_internal.routeLeavesOf)(command.toJSON())) leaves.add(leaf.route);
|
|
1934
|
-
}
|
|
1935
|
-
return leaves;
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
//#endregion
|
|
1939
|
-
//#region src/bot/controllers/CommandRegistry.ts
|
|
1940
|
-
/**
|
|
1941
|
-
* Manages Discord application command registration and deployment.
|
|
1942
|
-
*
|
|
1943
|
-
* Scans command directories, builds command structures, and registers both global and guild-scoped commands
|
|
1944
|
-
* to Discord's API. Accessed via `core.bot.commands`. Do not construct it directly.
|
|
1945
|
-
*/
|
|
1946
|
-
var CommandRegistry = class {
|
|
1947
|
-
core;
|
|
1948
|
-
onDeployed;
|
|
1949
|
-
logger = new _seedcord_logger.Logger("Commands", { channel: "commands" });
|
|
1950
|
-
isInitialised = false;
|
|
1951
|
-
globalCommands = [];
|
|
1952
|
-
guildCommands = new discord_js.Collection();
|
|
1953
|
-
ctorToCommand = /* @__PURE__ */ new Map();
|
|
1954
|
-
loading = false;
|
|
1955
|
-
loadedCommands = [];
|
|
1956
|
-
hmrHandler;
|
|
1957
|
-
pendingEvents = /* @__PURE__ */ new Map();
|
|
1958
|
-
constructor(core, onDeployed) {
|
|
1959
|
-
this.core = core;
|
|
1960
|
-
this.onDeployed = onDeployed;
|
|
1961
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
1962
|
-
if (!commandsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
|
|
1963
|
-
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
1964
|
-
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
1965
|
-
handlersDir: commandsDir,
|
|
1966
|
-
isHandler: this.isCommandClass.bind(this),
|
|
1967
|
-
registerHandler: this.registerCommand.bind(this),
|
|
1968
|
-
unregisterHandler: this.unregisterCommand.bind(this),
|
|
1969
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
1970
|
-
logger: this.logger
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
getArtifacts(ctor) {
|
|
1974
|
-
return this.ctorToCommand.get(ctor);
|
|
1975
|
-
}
|
|
1976
|
-
/** @internal */
|
|
1977
|
-
async init() {
|
|
1978
|
-
if (this.isInitialised) return;
|
|
1979
|
-
this.isInitialised = true;
|
|
1980
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
1981
|
-
if (!commandsDir) return;
|
|
1982
|
-
this.loading = true;
|
|
1983
|
-
this.loadedCommands.length = 0;
|
|
1984
|
-
try {
|
|
1985
|
-
await this.loadCommands(commandsDir);
|
|
1986
|
-
} finally {
|
|
1987
|
-
this.loading = false;
|
|
1988
|
-
}
|
|
1989
|
-
this.reportLoad();
|
|
1990
|
-
(0, _seedcord_core_internal.getDevChannel)()?.on("seedcord:refresh-commands", (data) => {
|
|
1991
|
-
this.refresh(data.shouldRefresh);
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
1994
|
-
reportLoad() {
|
|
1995
|
-
const { utils } = this.logger;
|
|
1996
|
-
utils.summary("Loaded commands", {
|
|
1997
|
-
global: this.globalCommands.length,
|
|
1998
|
-
"guild groups": this.guildCommands.size
|
|
1999
|
-
});
|
|
2000
|
-
let slash = 0;
|
|
2001
|
-
let menu = 0;
|
|
2002
|
-
for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
|
|
2003
|
-
else menu++;
|
|
2004
|
-
utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
|
|
2005
|
-
"slash commands": slash,
|
|
2006
|
-
"context menus": menu
|
|
2007
|
-
})]);
|
|
2008
|
-
}
|
|
2009
|
-
/** @internal */
|
|
2010
|
-
async refresh(shouldRefresh = true) {
|
|
2011
|
-
if (!shouldRefresh) {
|
|
2012
|
-
this.logger.info(chalk.default.italic("Command refresh cancelled."));
|
|
2013
|
-
this.pendingEvents.clear();
|
|
2014
|
-
return;
|
|
2015
|
-
}
|
|
2016
|
-
this.logger.info(chalk.default.italic("Refreshing commands..."));
|
|
2017
|
-
for (const event of this.pendingEvents.values()) await this.hmrHandler?.handle(event);
|
|
2018
|
-
this.pendingEvents.clear();
|
|
2019
|
-
await this.setCommands();
|
|
2020
|
-
}
|
|
2021
|
-
/** @internal */
|
|
2022
|
-
async onHmr(event) {
|
|
2023
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
2024
|
-
if (commandsDir && event.file.startsWith((0, node_path.resolve)(process.cwd(), commandsDir))) {
|
|
2025
|
-
this.pendingEvents.delete(event.file);
|
|
2026
|
-
this.pendingEvents.set(event.file, event);
|
|
2027
|
-
(0, _seedcord_core_internal.getDevChannel)()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => (0, _seedcord_utils.formatFilePath)(f)) });
|
|
2028
|
-
} else await this.hmrHandler?.handle(event);
|
|
2029
|
-
}
|
|
2030
|
-
async loadCommands(dir) {
|
|
2031
|
-
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, rel, mod) => {
|
|
2032
|
-
for (const exported of Object.values(mod)) if (this.isCommandClass(exported)) {
|
|
2033
|
-
this.registerCommand(exported, rel);
|
|
2034
|
-
this.hmrHandler?.trackHandler(fullPath, exported);
|
|
2035
|
-
}
|
|
2036
|
-
});
|
|
2037
|
-
}
|
|
2038
|
-
isCommandClass(obj) {
|
|
2039
|
-
if (typeof obj !== "function") return false;
|
|
2040
|
-
return obj.prototype instanceof _seedcord_core.BuilderComponent && Reflect.hasMetadata(_seedcord_core_internal.CommandMetadataKey, obj);
|
|
2041
|
-
}
|
|
2042
|
-
registerCommand(ctor, rel) {
|
|
2043
|
-
const meta = Reflect.getMetadata(_seedcord_core_internal.CommandMetadataKey, ctor);
|
|
2044
|
-
if (!meta) return;
|
|
2045
|
-
const comp = new ctor().component;
|
|
2046
|
-
const kind = comp instanceof _discordjs_builders.SlashCommandBuilder ? "slash command" : "context menu";
|
|
2047
|
-
if (meta.scope === "global") this.globalCommands.push(comp);
|
|
2048
|
-
else for (const g of meta.guilds) {
|
|
2049
|
-
const arr = this.guildCommands.get(g) ?? [];
|
|
2050
|
-
arr.push(comp);
|
|
2051
|
-
this.guildCommands.set(g, arr);
|
|
2052
|
-
}
|
|
2053
|
-
this.ctorToCommand.set(ctor, {
|
|
2054
|
-
name: comp.name,
|
|
2055
|
-
scope: meta.scope,
|
|
2056
|
-
...meta.scope === "guild" && { guilds: meta.guilds }
|
|
2057
|
-
});
|
|
2058
|
-
if (this.loading) {
|
|
2059
|
-
this.loadedCommands.push({
|
|
2060
|
-
name: comp.name,
|
|
2061
|
-
from: (0, _seedcord_utils.formatFilePath)(rel),
|
|
2062
|
-
kind
|
|
2063
|
-
});
|
|
2064
|
-
return;
|
|
2065
|
-
}
|
|
2066
|
-
this.logger.utils.registration(comp.name, rel, `${meta.scope} ${kind}`);
|
|
2067
|
-
}
|
|
2068
|
-
unregisterCommand(ctor, artifacts) {
|
|
2069
|
-
const info = artifacts ?? this.ctorToCommand.get(ctor);
|
|
2070
|
-
if (!info) return;
|
|
2071
|
-
if (info.scope === "global") {
|
|
2072
|
-
const idx = this.globalCommands.findIndex((c) => c.name === info.name);
|
|
2073
|
-
if (idx !== -1) this.globalCommands.splice(idx, 1);
|
|
2074
|
-
} else for (const g of info.guilds ?? []) {
|
|
2075
|
-
const arr = this.guildCommands.get(g);
|
|
2076
|
-
if (arr) {
|
|
2077
|
-
const idx = arr.findIndex((c) => c.name === info.name);
|
|
2078
|
-
if (idx !== -1) arr.splice(idx, 1);
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
this.ctorToCommand.delete(ctor);
|
|
2082
|
-
}
|
|
2083
|
-
/** @internal */
|
|
2084
|
-
async setCommands() {
|
|
2085
|
-
const result = {
|
|
2086
|
-
global: new discord_js.Collection(),
|
|
2087
|
-
guilds: new discord_js.Collection()
|
|
2088
|
-
};
|
|
2089
|
-
if (this.globalCommands.length > 0) {
|
|
2090
|
-
const deployed = await this.core.bot.client.application?.commands.set(this.globalCommands);
|
|
2091
|
-
if (deployed) result.global = deployed;
|
|
2092
|
-
const tag = this.globalCommands.length === 1 ? "command" : "commands";
|
|
2093
|
-
this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
|
|
2094
|
-
}
|
|
2095
|
-
for (const [guildId, commands] of this.guildCommands.entries()) {
|
|
2096
|
-
const guild = this.core.bot.client.guilds.cache.get(guildId);
|
|
2097
|
-
if (!guild) {
|
|
2098
|
-
this.logger.warn(`Guild with ID ${guildId} not found, skipping command registration.`);
|
|
2099
|
-
continue;
|
|
2100
|
-
}
|
|
2101
|
-
const deployed = await guild.commands.set(commands);
|
|
2102
|
-
result.guilds.set(guildId, deployed);
|
|
2103
|
-
const tag = commands.length === 1 ? "command" : "commands";
|
|
2104
|
-
this.logger.utils.block(`Deployed ${commands.length} ${tag} to ${_seedcord_logger.paint.amber.bold(guild.name)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
|
|
2105
|
-
}
|
|
2106
|
-
this.onDeployed?.(result);
|
|
2107
|
-
return result;
|
|
2108
|
-
}
|
|
2109
|
-
/** The deduplicated slash route keys across every global and guild command. @internal */
|
|
2110
|
-
routeLeaves() {
|
|
2111
|
-
return slashRouteLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2112
|
-
}
|
|
2113
|
-
/** The registered context-menu command names, split by kind, across every global and guild command. @internal */
|
|
2114
|
-
contextMenuLeaves() {
|
|
2115
|
-
return contextMenuLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2116
|
-
}
|
|
2117
|
-
};
|
|
2118
|
-
|
|
2119
1293
|
//#endregion
|
|
2120
1294
|
//#region src/miscellaneous/deriveEventActor.ts
|
|
2121
1295
|
function deriveEventActor(args) {
|
|
@@ -2193,6 +1367,154 @@ function eventGateContext(eventName, args, core) {
|
|
|
2193
1367
|
};
|
|
2194
1368
|
}
|
|
2195
1369
|
|
|
1370
|
+
//#endregion
|
|
1371
|
+
//#region src/miscellaneous/extractErrorResponse.ts
|
|
1372
|
+
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling", { channel: "errors" });
|
|
1373
|
+
/**
|
|
1374
|
+
* Processes an error into a user-facing reply and routes its report.
|
|
1375
|
+
*
|
|
1376
|
+
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
1377
|
+
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
1378
|
+
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
1379
|
+
* threaded into both the reply and the bus payload.
|
|
1380
|
+
*/
|
|
1381
|
+
function extractErrorResponse(error, core, origin) {
|
|
1382
|
+
const uuid = node_crypto.randomUUID();
|
|
1383
|
+
const developerUsername = core.config.notifications?.developerUsername;
|
|
1384
|
+
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
1385
|
+
uuid,
|
|
1386
|
+
developerUsername
|
|
1387
|
+
};
|
|
1388
|
+
if (error instanceof _seedcord_core.Notice) {
|
|
1389
|
+
if (error.report) reportFault(error, core, origin, uuid);
|
|
1390
|
+
return {
|
|
1391
|
+
uuid,
|
|
1392
|
+
response: error.render(ctx)
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
reportRawFault(error, core, origin, uuid);
|
|
1396
|
+
const override = core.config.errors?.defaultError;
|
|
1397
|
+
return {
|
|
1398
|
+
uuid,
|
|
1399
|
+
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function withThrottle(core, origin, error, publish) {
|
|
1403
|
+
const throttle = _seedcord_core_internal.FaultThrottle.for(core);
|
|
1404
|
+
const key = faultKey(origin, error);
|
|
1405
|
+
if (!throttle.shouldReport(key)) {
|
|
1406
|
+
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
publish();
|
|
1410
|
+
throttle.markReported(key);
|
|
1411
|
+
}
|
|
1412
|
+
function reportFault(denial, core, origin, uuid) {
|
|
1413
|
+
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
1414
|
+
withThrottle(core, origin, denial, () => {
|
|
1415
|
+
if (origin.interaction) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
1416
|
+
denial,
|
|
1417
|
+
uuid,
|
|
1418
|
+
source: buildInteractionSource(origin.interaction)
|
|
1419
|
+
});
|
|
1420
|
+
else if (origin.event) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
1421
|
+
denial,
|
|
1422
|
+
uuid,
|
|
1423
|
+
source: buildEventSource(origin.event, origin)
|
|
1424
|
+
});
|
|
1425
|
+
else core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
1426
|
+
uuid,
|
|
1427
|
+
error: denial,
|
|
1428
|
+
...scalarActors(origin),
|
|
1429
|
+
metadata: metadataFor(origin)
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
function causeLine(error) {
|
|
1434
|
+
const { cause } = error;
|
|
1435
|
+
if (!Error.isError(cause)) return "";
|
|
1436
|
+
const [first] = cause.message.split("\n");
|
|
1437
|
+
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
1438
|
+
}
|
|
1439
|
+
function reportRawFault(error, core, origin, uuid) {
|
|
1440
|
+
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
1441
|
+
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
1442
|
+
withThrottle(core, origin, error, () => {
|
|
1443
|
+
core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
1444
|
+
uuid,
|
|
1445
|
+
error,
|
|
1446
|
+
...scalarActors(origin),
|
|
1447
|
+
metadata: metadataFor(origin)
|
|
1448
|
+
});
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
function scalarActors(origin) {
|
|
1452
|
+
return {
|
|
1453
|
+
guild: origin.guild ? {
|
|
1454
|
+
id: origin.guild.id,
|
|
1455
|
+
name: origin.guild.name
|
|
1456
|
+
} : void 0,
|
|
1457
|
+
user: origin.user ? {
|
|
1458
|
+
id: origin.user.id,
|
|
1459
|
+
username: origin.user.username
|
|
1460
|
+
} : void 0
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
function metadataFor(origin) {
|
|
1464
|
+
if (origin.event) return {
|
|
1465
|
+
eventName: origin.event.name,
|
|
1466
|
+
handler: origin.event.handler,
|
|
1467
|
+
args: origin.event.args
|
|
1468
|
+
};
|
|
1469
|
+
return origin.metadata;
|
|
1470
|
+
}
|
|
1471
|
+
function buildEventSource(event, origin) {
|
|
1472
|
+
return {
|
|
1473
|
+
kind: "event",
|
|
1474
|
+
eventName: event.name,
|
|
1475
|
+
handler: event.handler,
|
|
1476
|
+
userId: origin.user?.id ?? null,
|
|
1477
|
+
guildId: origin.guild?.id ?? null,
|
|
1478
|
+
channelId: event.channelId,
|
|
1479
|
+
raw: event.args
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
function faultKey(origin, error) {
|
|
1483
|
+
const name = error instanceof _seedcord_core.Notice ? error.name : error instanceof discord_js.DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1484
|
+
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1485
|
+
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1486
|
+
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1487
|
+
return `autocomplete:${name}`;
|
|
1488
|
+
}
|
|
1489
|
+
function interactionRoute(interaction) {
|
|
1490
|
+
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1491
|
+
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1492
|
+
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
1493
|
+
return "interaction";
|
|
1494
|
+
}
|
|
1495
|
+
function buildInteractionSource(interaction) {
|
|
1496
|
+
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1497
|
+
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1498
|
+
return {
|
|
1499
|
+
kind: "interaction",
|
|
1500
|
+
interactionKind: interactionKind(interaction),
|
|
1501
|
+
command,
|
|
1502
|
+
customId,
|
|
1503
|
+
userId: interaction.user.id,
|
|
1504
|
+
guildId: interaction.guildId,
|
|
1505
|
+
channelId: interaction.channelId,
|
|
1506
|
+
interactionId: interaction.id,
|
|
1507
|
+
raw: interaction
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
function interactionKind(interaction) {
|
|
1511
|
+
if (interaction.isChatInputCommand()) return "slash";
|
|
1512
|
+
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1513
|
+
if (interaction.isButton()) return "button";
|
|
1514
|
+
if (interaction.isAnySelectMenu()) return "select";
|
|
1515
|
+
return "modal";
|
|
1516
|
+
}
|
|
1517
|
+
|
|
2196
1518
|
//#endregion
|
|
2197
1519
|
//#region src/bot/handleEventFault.ts
|
|
2198
1520
|
const logger$1 = new _seedcord_logger.Logger("EventBoundary", { channel: "errors" });
|
|
@@ -2626,21 +1948,9 @@ var InteractionDispatcher = class {
|
|
|
2626
1948
|
logger: this.logger
|
|
2627
1949
|
});
|
|
2628
1950
|
}
|
|
2629
|
-
/**
|
|
2630
|
-
* Warns for each command route with no registered `@SlashRoute` handler, which would fall through to
|
|
2631
|
-
* UnhandledRepliable at runtime. A bot may route commands outside the registry, so this does not throw.
|
|
2632
|
-
*
|
|
2633
|
-
* @internal
|
|
2634
|
-
*/
|
|
2635
1951
|
warnUnhandledRoutes(commandLeaves) {
|
|
2636
1952
|
for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${_seedcord_logger.paint.sky.bold(route)} has no registered ${chalk.default.bold("@SlashRoute")} handler.`);
|
|
2637
1953
|
}
|
|
2638
|
-
/**
|
|
2639
|
-
* Warns for each context-menu command with no matching handler, which would fall through to UnhandledRepliable
|
|
2640
|
-
* at runtime. Checked per kind since a user and a message command can share a name.
|
|
2641
|
-
*
|
|
2642
|
-
* @internal
|
|
2643
|
-
*/
|
|
2644
1954
|
warnUnhandledContextMenuRoutes(leaves) {
|
|
2645
1955
|
for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
2646
1956
|
for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
@@ -2981,29 +2291,39 @@ var Bot = class {
|
|
|
2981
2291
|
interactions;
|
|
2982
2292
|
events;
|
|
2983
2293
|
emojiInjector;
|
|
2984
|
-
|
|
2985
|
-
emojis = Emojis;
|
|
2986
|
-
mentions = CommandMentions;
|
|
2294
|
+
commandRegistry;
|
|
2987
2295
|
/** @internal */
|
|
2988
2296
|
async onHmr(event) {
|
|
2989
2297
|
if (this.interactions) await this.interactions.onHmr(event);
|
|
2990
2298
|
if (this.events) await this.events.onHmr(event);
|
|
2991
|
-
if (this.
|
|
2299
|
+
if (this.commandRegistry) await this.commandRegistry.onHmr(event);
|
|
2992
2300
|
}
|
|
2993
2301
|
/** @internal */
|
|
2994
2302
|
constructor(core) {
|
|
2995
2303
|
this._client = new discord_js.Client(core.config.bot.clientOptions);
|
|
2996
2304
|
if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
|
|
2997
2305
|
if (core.config.bot.events.path) this.events = new EventDispatcher(core);
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
2306
|
+
const commandsDir = core.config.bot.commands.path;
|
|
2307
|
+
if (commandsDir) {
|
|
2308
|
+
const injector = new _seedcord_core_internal.CommandInjector();
|
|
2309
|
+
const registry = new _seedcord_core_node_internal.CommandRegistry({
|
|
2310
|
+
dir: commandsDir,
|
|
2311
|
+
rest: this._client.rest,
|
|
2312
|
+
applicationId: () => this.applicationId(),
|
|
2313
|
+
onDeployed: (result) => {
|
|
2314
|
+
injector.inject(result, registry.allCommands());
|
|
2315
|
+
}
|
|
3002
2316
|
});
|
|
2317
|
+
this.commandRegistry = registry;
|
|
3003
2318
|
}
|
|
3004
2319
|
this.emojiInjector = new EmojiInjector(core);
|
|
3005
2320
|
this.registerShutdownTasks(core);
|
|
3006
2321
|
}
|
|
2322
|
+
applicationId() {
|
|
2323
|
+
const { application } = this._client;
|
|
2324
|
+
if (!application) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreApplicationUnavailable);
|
|
2325
|
+
return application.id;
|
|
2326
|
+
}
|
|
3007
2327
|
registerShutdownTasks(core) {
|
|
3008
2328
|
core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Unbind, "stop-dispatch", () => {
|
|
3009
2329
|
this.stopAccepting();
|
|
@@ -3028,11 +2348,11 @@ var Bot = class {
|
|
|
3028
2348
|
if (this.events) await this.events.init();
|
|
3029
2349
|
await this.login(token);
|
|
3030
2350
|
await this.emojiInjector.init();
|
|
3031
|
-
if (this.
|
|
3032
|
-
await this.
|
|
3033
|
-
await this.
|
|
3034
|
-
this.interactions?.warnUnhandledRoutes(this.
|
|
3035
|
-
this.interactions?.warnUnhandledContextMenuRoutes(this.
|
|
2351
|
+
if (this.commandRegistry) {
|
|
2352
|
+
await this.commandRegistry.init();
|
|
2353
|
+
await this.commandRegistry.setCommands();
|
|
2354
|
+
this.interactions?.warnUnhandledRoutes(this.commandRegistry.routeLeaves());
|
|
2355
|
+
this.interactions?.warnUnhandledContextMenuRoutes(this.commandRegistry.contextMenuLeaves());
|
|
3036
2356
|
}
|
|
3037
2357
|
}
|
|
3038
2358
|
/** @internal */
|
|
@@ -3061,7 +2381,7 @@ __decorate([(0, envapt_legacy.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) =>
|
|
|
3061
2381
|
//#endregion
|
|
3062
2382
|
//#region src/version.ts
|
|
3063
2383
|
/** Package version */
|
|
3064
|
-
const version = "0.1.0
|
|
2384
|
+
const version = "0.1.0";
|
|
3065
2385
|
|
|
3066
2386
|
//#endregion
|
|
3067
2387
|
//#region src/Seedcord.ts
|
|
@@ -3084,6 +2404,8 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3084
2404
|
subscribers;
|
|
3085
2405
|
/** @see {@link Bot} */
|
|
3086
2406
|
bot;
|
|
2407
|
+
/** The discord.js client's REST client. Its token is set during the Login phase. */
|
|
2408
|
+
rest;
|
|
3087
2409
|
/** @see {@link IRateLimiter} */
|
|
3088
2410
|
rateLimiter;
|
|
3089
2411
|
/**
|
|
@@ -3100,6 +2422,7 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3100
2422
|
this.bus = new _seedcord_core.Bus(this);
|
|
3101
2423
|
this.subscribers = new _seedcord_core_node_internal.SubscriberLoader(this.bus, config.subscribers.path);
|
|
3102
2424
|
this.bot = new Bot(this);
|
|
2425
|
+
this.rest = this.bot.client.rest;
|
|
3103
2426
|
this.rateLimiter = config.store ?? new _seedcord_rate_limiter.MemoryRateLimiter();
|
|
3104
2427
|
this.healthCheck = _seedcord_core_node_internal.HealthCheck.fromOption(this.shutdown, config.healthCheck);
|
|
3105
2428
|
this.registerStartupTasks();
|
|
@@ -3108,13 +2431,6 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3108
2431
|
get username() {
|
|
3109
2432
|
return this.bot.client.user?.username;
|
|
3110
2433
|
}
|
|
3111
|
-
pluginCapabilities() {
|
|
3112
|
-
return {
|
|
3113
|
-
client: this.bot.client,
|
|
3114
|
-
token: this.bot.botToken,
|
|
3115
|
-
rest: this.bot.client.rest
|
|
3116
|
-
};
|
|
3117
|
-
}
|
|
3118
2434
|
static reset() {
|
|
3119
2435
|
super.reset();
|
|
3120
2436
|
_seedcord_logger.LoggerChannelRegistry.instance.reset();
|
|
@@ -3167,7 +2483,6 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3167
2483
|
exports.ArraySource = ArraySource;
|
|
3168
2484
|
exports.AutocompleteHandler = AutocompleteHandler;
|
|
3169
2485
|
exports.ButtonHandler = ButtonHandler;
|
|
3170
|
-
exports.CommandMentions = CommandMentions;
|
|
3171
2486
|
exports.ContextMenuHandler = ContextMenuHandler;
|
|
3172
2487
|
exports.CursorSource = CursorSource;
|
|
3173
2488
|
exports.Emojis = Emojis;
|
|
@@ -3192,16 +2507,8 @@ exports.Subscriber = Subscriber;
|
|
|
3192
2507
|
exports.WebhookLog = WebhookLog;
|
|
3193
2508
|
exports.checkBotPermissions = checkBotPermissions;
|
|
3194
2509
|
exports.checkPermissions = checkPermissions;
|
|
3195
|
-
exports.fetchGuildMember = fetchGuildMember;
|
|
3196
|
-
exports.fetchManyGuildMembers = fetchManyGuildMembers;
|
|
3197
|
-
exports.fetchManyUsers = fetchManyUsers;
|
|
3198
|
-
exports.fetchRole = fetchRole;
|
|
3199
|
-
exports.fetchText = fetchText;
|
|
3200
|
-
exports.fetchUser = fetchUser;
|
|
3201
|
-
exports.getBotRole = getBotRole;
|
|
3202
2510
|
exports.getConfirmation = getConfirmation;
|
|
3203
2511
|
exports.hasPermsToAssign = hasPermsToAssign;
|
|
3204
|
-
exports.updateMemberRoles = updateMemberRoles;
|
|
3205
2512
|
exports.version = version;
|
|
3206
2513
|
Object.keys(_seedcord_core).forEach(function (k) {
|
|
3207
2514
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|