@seedcord/gateway 0.1.0-next.5 → 0.1.0-next.6
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 +330 -1035
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +101 -420
- package/dist/index.mjs +335 -1031
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { installNodeDefaults } from "@seedcord/logger/node";
|
|
3
|
-
import { BaseReplySender,
|
|
3
|
+
import { BaseReplySender, CommandInjector, EventMetadataKey, FaultThrottle, GateNotice, GatedMetadataKey, HmrManager, InteractionMetadataKey, InteractionRouteKeys, InteractionRoutes, MiddlewareMetadataKey, MissingPermissions, NoticeCard, PermissionNames, PublishDefault, accessorStore, areRoutes, asError, clearStore, decodeComponentRoute, deferFlags, dispatchedPayload, guardedAccessor, isEmojiTuple, outcomeFor, pickNotice, prefixOf, queuedMsFor, reportedWrite, routeIdOf, runHandlerGates, sendFlags, setBotColor, slowGateMonitor } from "@seedcord/core/internal";
|
|
4
4
|
import { SeedcordErrorCode } from "@seedcord/errors";
|
|
5
|
-
import { SeedcordError,
|
|
6
|
-
import { BaseHandler, BuilderComponent, Bus, CustomId, DispatchContext, Fault, Notice, RepliableHandler as RepliableHandler$1, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1,
|
|
5
|
+
import { SeedcordError, SeedcordTypeError, validateDiscordToken } from "@seedcord/errors/internal";
|
|
6
|
+
import { ArraySource as ArraySource$1, BaseHandler, BuilderComponent, Bus, CursorSource as CursorSource$1, CustomId, DispatchContext, Fault, Notice, PaginatorBase, RepliableHandler as RepliableHandler$1, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1, assertPermissions, defineGate } from "@seedcord/core";
|
|
7
7
|
import { Logger, LoggerChannelRegistry, paint } from "@seedcord/logger";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import * as crypto from "node:crypto";
|
|
8
|
+
import { ButtonStyle, Client, ComponentType, DiscordAPIError, Events, Guild, GuildMember, Message, MessageFlags, PermissionFlagsBits, RESTJSONErrorCodes, Role, User } from "discord.js";
|
|
9
|
+
import { ButtonBuilder, TextDisplayBuilder } from "@discordjs/builders";
|
|
10
|
+
import { buildSlashRoute } from "@seedcord/utils/internal";
|
|
12
11
|
import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
|
|
13
|
-
import { CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin } from "@seedcord/core/node/internal";
|
|
12
|
+
import { CommandRegistry, CoordinatedShutdown, CoordinatedStartup, HealthCheck, Pluggable, ShutdownPhase, StartupPhase, SubscriberLoader, settleWithin } from "@seedcord/core/node/internal";
|
|
14
13
|
import { MemoryRateLimiter } from "@seedcord/rate-limiter";
|
|
15
14
|
import { SeedcordBrand } from "@seedcord/types/internal";
|
|
16
15
|
import { Envapter } from "envapt";
|
|
17
16
|
import chalk from "chalk";
|
|
18
17
|
import { Envapt } from "envapt/legacy";
|
|
19
|
-
import { resolve } from "node:path";
|
|
20
18
|
import { HmrModuleHandler } from "@seedcord/core/hmr";
|
|
21
19
|
import { formatFilePath, hasKeys } from "@seedcord/utils";
|
|
22
20
|
import { traverseDirectory } from "@seedcord/utils/node";
|
|
21
|
+
import * as crypto from "node:crypto";
|
|
23
22
|
|
|
24
23
|
export * from "@seedcord/errors"
|
|
25
24
|
|
|
@@ -224,59 +223,6 @@ var CannotAssignBotRole = class extends Notice {
|
|
|
224
223
|
return { components: [new NoticeCard("I cannot assign a managed role.").component] };
|
|
225
224
|
}
|
|
226
225
|
};
|
|
227
|
-
var HasDangerousPermissions = class extends Notice {
|
|
228
|
-
subject;
|
|
229
|
-
dangerousPerms;
|
|
230
|
-
customLead;
|
|
231
|
-
constructor(message, subject, dangerousPerms) {
|
|
232
|
-
super(message ?? "A dangerous permission is enabled.");
|
|
233
|
-
this.subject = subject;
|
|
234
|
-
this.dangerousPerms = dangerousPerms;
|
|
235
|
-
this.customLead = message;
|
|
236
|
-
}
|
|
237
|
-
render() {
|
|
238
|
-
const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
|
|
239
|
-
return { components: [new NoticeCard(`${this.customLead ?? `${this.subject} has the following permission entries that must not be enabled:`}\n\n${bullets}`).component] };
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
var UserNotFound = class extends Notice {
|
|
243
|
-
userArg;
|
|
244
|
-
constructor(userArg) {
|
|
245
|
-
super(`User not found: ${userArg}`);
|
|
246
|
-
this.userArg = userArg;
|
|
247
|
-
}
|
|
248
|
-
render() {
|
|
249
|
-
return { components: [new 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] };
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
var UserNotInGuild = class extends Notice {
|
|
253
|
-
constructor(message = "User is not in the guild.") {
|
|
254
|
-
super(message);
|
|
255
|
-
}
|
|
256
|
-
render() {
|
|
257
|
-
return { components: [new NoticeCard(this.message).component] };
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
var RoleDoesNotExist = class extends Notice {
|
|
261
|
-
roleId;
|
|
262
|
-
constructor(message, roleId) {
|
|
263
|
-
super(message);
|
|
264
|
-
this.roleId = roleId;
|
|
265
|
-
}
|
|
266
|
-
render() {
|
|
267
|
-
return { components: [new NoticeCard(`The role with ID \`${this.roleId}\` does not exist.`).component] };
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
var CouldNotFindChannel = class extends Notice {
|
|
271
|
-
channelId;
|
|
272
|
-
constructor(message, channelId) {
|
|
273
|
-
super(message);
|
|
274
|
-
this.channelId = channelId;
|
|
275
|
-
}
|
|
276
|
-
render() {
|
|
277
|
-
return { components: [new NoticeCard(`Could not find channel with ID \`${this.channelId}\`. It could also be that the channel is not a text channel.`).component] };
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
226
|
|
|
281
227
|
//#endregion
|
|
282
228
|
//#region src/bot/gates/catalog/Nsfw.ts
|
|
@@ -314,20 +260,15 @@ function Nsfw(options) {
|
|
|
314
260
|
|
|
315
261
|
//#endregion
|
|
316
262
|
//#region src/bot/injectors/EmojiInjector.ts
|
|
317
|
-
const emojiStorage =
|
|
318
|
-
function isEmojiTuple(v) {
|
|
319
|
-
return Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "string";
|
|
320
|
-
}
|
|
263
|
+
const emojiStorage = accessorStore();
|
|
321
264
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
* @see {@link EmojiMap}
|
|
265
|
+
* The bot's resolved emojis, keyed by {@link EmojiMap}. Filled by {@link EmojiInjector} during startup.
|
|
266
|
+
* A read before that throws.
|
|
325
267
|
*/
|
|
326
|
-
const Emojis = emojiStorage;
|
|
268
|
+
const Emojis = guardedAccessor("Emojis", emojiStorage);
|
|
327
269
|
/**
|
|
328
|
-
* Loads and injects emojis based on bot configuration.
|
|
329
|
-
*
|
|
330
|
-
* For emojis injected from specific guilds, ensure that the Guilds intent is provided in client options.
|
|
270
|
+
* Loads and injects emojis based on bot configuration. A guild emoji requires the Guilds intent in the client
|
|
271
|
+
* options.
|
|
331
272
|
*
|
|
332
273
|
* @internal
|
|
333
274
|
*/
|
|
@@ -380,126 +321,10 @@ var EmojiInjector = class {
|
|
|
380
321
|
emojiStorage[key] = appEmoji;
|
|
381
322
|
}
|
|
382
323
|
clearEmojis() {
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
//#endregion
|
|
388
|
-
//#region src/bot/injectors/CommandMentionInjector.ts
|
|
389
|
-
const mentionStorage = {};
|
|
390
|
-
/**
|
|
391
|
-
* Ready-to-send command mention strings, keyed by {@link SlashOptionRegistry}.
|
|
392
|
-
*
|
|
393
|
-
* A value is a clickable `</name:id>` when the command's id resolved from the deploy, otherwise the plain
|
|
394
|
-
* `/name` text. Populated by {@link CommandMentionInjector} after each deploy.
|
|
395
|
-
*/
|
|
396
|
-
const CommandMentions = mentionStorage;
|
|
397
|
-
const MULTI_GUILD = Symbol("seedcord:commandmentions:multi-guild");
|
|
398
|
-
/**
|
|
399
|
-
* Turns each deployed slash route into a clickable command mention exposed through {@link CommandMentions}.
|
|
400
|
-
*
|
|
401
|
-
* Consumes the ids {@link DeployResult} carries, so it runs after a deploy rather than resolving from a name
|
|
402
|
-
* cache (discord.js offers none for command ids). A global command is clickable everywhere by its app-wide id.
|
|
403
|
-
* A guild command deployed to one guild is clickable by that guild's id. A command deployed to two or more
|
|
404
|
-
* guilds mints a different id per guild, so one accessor key cannot resolve it, and it falls back to plain
|
|
405
|
-
* `/name` text with one warn. On a non-owning shard a guild command also falls back to plain text.
|
|
406
|
-
*
|
|
407
|
-
* @internal
|
|
408
|
-
*/
|
|
409
|
-
var CommandMentionInjector = class {
|
|
410
|
-
core;
|
|
411
|
-
logger = new Logger("CommandMentions", { channel: "bot" });
|
|
412
|
-
constructor(core) {
|
|
413
|
-
this.core = core;
|
|
414
|
-
}
|
|
415
|
-
inject(deploy) {
|
|
416
|
-
this.clear();
|
|
417
|
-
const globalIds = this.indexByName(deploy.global);
|
|
418
|
-
const guildIds = this.indexGuilds(deploy.guilds);
|
|
419
|
-
const warned = /* @__PURE__ */ new Set();
|
|
420
|
-
let clickable = 0;
|
|
421
|
-
for (const command of this.allSlashBuilders()) {
|
|
422
|
-
const id = this.resolveId(command.name, globalIds, guildIds, warned);
|
|
423
|
-
for (const leaf of routeLeavesOf(command.toJSON())) {
|
|
424
|
-
mentionStorage[leaf.route] = id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route);
|
|
425
|
-
if (id) clickable++;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
this.logger.utils.summary("Linked mentions", {
|
|
429
|
-
clickable,
|
|
430
|
-
plain: Object.keys(mentionStorage).length - clickable
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
allSlashBuilders() {
|
|
434
|
-
const registry = this.core.bot.commands;
|
|
435
|
-
if (!registry) return [];
|
|
436
|
-
const slash = [...registry.globalCommands, ...registry.guildCommands.values()].flat().filter((command) => command instanceof SlashCommandBuilder);
|
|
437
|
-
return [...new Set(slash)];
|
|
438
|
-
}
|
|
439
|
-
indexByName(collection) {
|
|
440
|
-
const map = /* @__PURE__ */ new Map();
|
|
441
|
-
for (const command of collection.values()) map.set(command.name, command.id);
|
|
442
|
-
return map;
|
|
443
|
-
}
|
|
444
|
-
indexGuilds(guilds) {
|
|
445
|
-
const map = /* @__PURE__ */ new Map();
|
|
446
|
-
for (const collection of guilds.values()) for (const command of collection.values()) {
|
|
447
|
-
const existing = map.get(command.name);
|
|
448
|
-
if (existing === void 0) map.set(command.name, command.id);
|
|
449
|
-
else if (existing !== command.id) map.set(command.name, MULTI_GUILD);
|
|
450
|
-
}
|
|
451
|
-
return map;
|
|
452
|
-
}
|
|
453
|
-
resolveId(name, globalIds, guildIds, warned) {
|
|
454
|
-
const global = globalIds.get(name);
|
|
455
|
-
if (global) return global;
|
|
456
|
-
const guild = guildIds.get(name);
|
|
457
|
-
if (guild === void 0) return void 0;
|
|
458
|
-
if (guild === MULTI_GUILD) {
|
|
459
|
-
if (!warned.has(name)) {
|
|
460
|
-
this.logger.warn(`${name} is deployed to multiple guilds, falling back to plain text (not clickable).`);
|
|
461
|
-
warned.add(name);
|
|
462
|
-
}
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
return guild;
|
|
466
|
-
}
|
|
467
|
-
toMention(route, id) {
|
|
468
|
-
const [name, middle, sub] = route.split("/");
|
|
469
|
-
if (name && middle && sub) return chatInputApplicationCommandMention(name, middle, sub, id);
|
|
470
|
-
if (name && middle) return chatInputApplicationCommandMention(name, middle, id);
|
|
471
|
-
return chatInputApplicationCommandMention(name ?? route, id);
|
|
472
|
-
}
|
|
473
|
-
toPlain(route) {
|
|
474
|
-
return `/${route.replaceAll("/", " ")}`;
|
|
475
|
-
}
|
|
476
|
-
clear() {
|
|
477
|
-
for (const key of Object.keys(mentionStorage)) Reflect.deleteProperty(mentionStorage, key);
|
|
324
|
+
clearStore(emojiStorage);
|
|
478
325
|
}
|
|
479
326
|
};
|
|
480
327
|
|
|
481
|
-
//#endregion
|
|
482
|
-
//#region src/bot/utilities/channels/fetchText.ts
|
|
483
|
-
/**
|
|
484
|
-
* Fetches and validates a text channel. Refuses when the channel doesn't exist or isn't a text channel.
|
|
485
|
-
*
|
|
486
|
-
* @param client - The Discord client instance
|
|
487
|
-
* @param channelId - Channel ID or TextChannel instance
|
|
488
|
-
* @returns Promise resolving to the text channel
|
|
489
|
-
*/
|
|
490
|
-
async function fetchText(client, channelId) {
|
|
491
|
-
if (channelId instanceof TextChannel) return channelId;
|
|
492
|
-
let channel = client.channels.cache.get(channelId);
|
|
493
|
-
if (!channel) try {
|
|
494
|
-
channel = await client.channels.fetch(channelId);
|
|
495
|
-
} catch (err) {
|
|
496
|
-
if (err instanceof DiscordAPIError && err.code === RESTJSONErrorCodes.UnknownChannel) throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
497
|
-
throw err;
|
|
498
|
-
}
|
|
499
|
-
if (channel?.isTextBased()) return channel;
|
|
500
|
-
throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
328
|
//#endregion
|
|
504
329
|
//#region src/bot/notices/utils.ts
|
|
505
330
|
function mentionFor(subject) {
|
|
@@ -510,48 +335,37 @@ function mentionFor(subject) {
|
|
|
510
335
|
//#endregion
|
|
511
336
|
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
512
337
|
function checkPermissions(a, b, c, d, e) {
|
|
513
|
-
const { for: pFor, in: pIn, scope, inverse = false, missingNotice
|
|
338
|
+
const { for: pFor, in: pIn, scope, inverse = false, missingNotice, dangerousNotice } = a instanceof Role || a instanceof GuildMember ? {
|
|
514
339
|
for: a,
|
|
515
340
|
in: b,
|
|
516
341
|
scope: c,
|
|
517
342
|
inverse: d ?? false,
|
|
518
343
|
...e
|
|
519
344
|
} : a;
|
|
520
|
-
const Missing = missingCtor ?? MissingPermissions;
|
|
521
|
-
const Dangerous = dangerousCtor ?? HasDangerousPermissions;
|
|
522
|
-
const subject = mentionFor(pFor);
|
|
523
345
|
const perms = pIn instanceof Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
346
|
+
assertPermissions({
|
|
347
|
+
subject: mentionFor(pFor),
|
|
348
|
+
permissions: perms.bitfield,
|
|
349
|
+
scope,
|
|
350
|
+
inverse,
|
|
351
|
+
...missingNotice && { missingNotice },
|
|
352
|
+
...dangerousNotice && { dangerousNotice }
|
|
353
|
+
});
|
|
532
354
|
}
|
|
533
355
|
|
|
534
356
|
//#endregion
|
|
535
357
|
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
536
358
|
/**
|
|
537
|
-
* Checks
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
* @param target - Guild or text channel to check in
|
|
541
|
-
* @param scope - Permission bits to validate
|
|
542
|
-
* @param inverse - Whether to check for absence of the given permissions
|
|
543
|
-
* @param errors - Optional custom error constructors
|
|
359
|
+
* Checks the bot's own permissions in a {@link Guild} or {@link TextChannel}. Refuses when a permission in
|
|
360
|
+
* `scope` is missing, or with `inverse` when one is present. When the bot member is uncached, the refusal
|
|
361
|
+
* reports every permission in `scope` as missing.
|
|
544
362
|
*
|
|
545
363
|
* @example
|
|
546
364
|
* ```ts
|
|
547
365
|
* import { PermissionFlagsBits } from 'discord.js';
|
|
548
366
|
* import { checkBotPermissions } from '@seedcord/gateway';
|
|
549
367
|
*
|
|
550
|
-
*
|
|
551
|
-
* checkBotPermissions(textChannel, [
|
|
552
|
-
* PermissionFlagsBits.SendMessages,
|
|
553
|
-
* PermissionFlagsBits.ViewChannel
|
|
554
|
-
* ]);
|
|
368
|
+
* checkBotPermissions(textChannel, [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel]);
|
|
555
369
|
* ```
|
|
556
370
|
*/
|
|
557
371
|
function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
@@ -574,26 +388,17 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
574
388
|
}
|
|
575
389
|
|
|
576
390
|
//#endregion
|
|
577
|
-
//#region src/bot/utilities/
|
|
578
|
-
|
|
579
|
-
* Gets the bot's managed role in a guild.
|
|
580
|
-
*
|
|
581
|
-
* @param guild - The guild to get the bot role from
|
|
582
|
-
* @returns The bot's managed role in the guild
|
|
583
|
-
* @throws A **SeedcordError** if the client user is unavailable or if the bot role is missing
|
|
584
|
-
*/
|
|
585
|
-
function getBotRole(guild) {
|
|
391
|
+
//#region src/bot/utilities/permissions/hasPermsToAssign.ts
|
|
392
|
+
function botRoleOf(guild) {
|
|
586
393
|
const botRole = guild.roles.botRoleFor(guild.client.user);
|
|
587
394
|
if (!botRole) throw new SeedcordError(SeedcordErrorCode.CoreBotRoleMissing, [guild.id]);
|
|
588
395
|
return botRole;
|
|
589
396
|
}
|
|
590
|
-
|
|
591
|
-
//#endregion
|
|
592
|
-
//#region src/bot/utilities/permissions/hasPermsToAssign.ts
|
|
593
397
|
/**
|
|
594
|
-
* Validates
|
|
398
|
+
* 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.
|
|
595
399
|
*
|
|
596
400
|
* @param roleOrOptions - Target role or complete options for the check
|
|
401
|
+
* @throws A `Notice` on refusal, or a `SeedcordError` when the bot has no role in the guild's role cache.
|
|
597
402
|
*
|
|
598
403
|
* @example
|
|
599
404
|
* ```ts
|
|
@@ -621,135 +426,108 @@ function hasPermsToAssign(roleOrOptions) {
|
|
|
621
426
|
const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
|
|
622
427
|
const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
|
|
623
428
|
const MissingNotice = errors?.missingNotice ?? MissingPermissions;
|
|
624
|
-
const botRole =
|
|
429
|
+
const botRole = botRoleOf(targetRole.guild);
|
|
625
430
|
if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
|
|
626
431
|
if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
|
|
627
432
|
checkBotPermissions(targetRole.guild, [PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
|
|
628
433
|
}
|
|
629
434
|
|
|
630
435
|
//#endregion
|
|
631
|
-
//#region src/bot/
|
|
632
|
-
|
|
633
|
-
return err instanceof DiscordAPIError && err.code === RESTJSONErrorCodes.UnknownRole;
|
|
634
|
-
}
|
|
635
|
-
async function fetchRoleInGuild(guild, roleId) {
|
|
636
|
-
const cached = guild.roles.cache.get(roleId);
|
|
637
|
-
if (cached) return cached;
|
|
638
|
-
try {
|
|
639
|
-
return await guild.roles.fetch(roleId);
|
|
640
|
-
} catch (err) {
|
|
641
|
-
if (isUnknownRole(err)) throw new RoleDoesNotExist("Role not found in specified guild", roleId);
|
|
642
|
-
throw err;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
async function scanGuildsForRole(client, roleId) {
|
|
646
|
-
const cached = client.guilds.cache.map((guild) => guild.roles.cache.get(roleId)).find(Boolean);
|
|
647
|
-
if (cached) return cached;
|
|
648
|
-
for (const guild of client.guilds.cache.values()) try {
|
|
649
|
-
const role = await guild.roles.fetch(roleId);
|
|
650
|
-
if (role) return role;
|
|
651
|
-
} catch (err) {
|
|
652
|
-
if (isUnknownRole(err)) continue;
|
|
653
|
-
throw err;
|
|
654
|
-
}
|
|
655
|
-
return null;
|
|
656
|
-
}
|
|
436
|
+
//#region src/bot/confirm/reserved.ts
|
|
437
|
+
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
657
438
|
/**
|
|
658
|
-
*
|
|
439
|
+
* The reserved confirm/cancel codec. It has no route, so the dispatcher ignores its clicks and only the
|
|
440
|
+
* in-process collector reads them. Do not mint a real component from it.
|
|
659
441
|
*
|
|
660
|
-
* @
|
|
661
|
-
* @param roleId - The role ID to fetch
|
|
662
|
-
* @returns Promise resolving to the role
|
|
442
|
+
* @internal
|
|
663
443
|
*/
|
|
664
|
-
|
|
665
|
-
if (!roleId) throw new RoleDoesNotExist("Role ID is null or undefined", roleId);
|
|
666
|
-
const role = clientOrGuild instanceof Guild ? await fetchRoleInGuild(clientOrGuild, roleId) : await scanGuildsForRole(clientOrGuild, roleId);
|
|
667
|
-
if (!role) throw new RoleDoesNotExist("Role not found", roleId);
|
|
668
|
-
return role;
|
|
669
|
-
}
|
|
444
|
+
const CONFIRM_DEF = new CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
|
|
670
445
|
|
|
671
446
|
//#endregion
|
|
672
|
-
//#region src/bot/
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
447
|
+
//#region src/bot/confirm/getConfirmation.ts
|
|
448
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
449
|
+
const CONFIRM_IDS = {
|
|
450
|
+
confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
|
|
451
|
+
cancel: CONFIRM_DEF.encode({ choice: "cancel" })
|
|
452
|
+
};
|
|
453
|
+
var ConfirmButtons = class extends RowComponent {
|
|
454
|
+
constructor(options) {
|
|
455
|
+
super("button");
|
|
456
|
+
this.instance.addComponents(new ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? ButtonStyle.Danger), new ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(ButtonStyle.Secondary));
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
var ConfirmPromptCard = class extends BuilderComponent {
|
|
460
|
+
constructor(text, options) {
|
|
461
|
+
super("container");
|
|
462
|
+
this.instance.addTextDisplayComponents(new TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
function defaultPrompt(text, options) {
|
|
466
|
+
return { components: [new ConfirmPromptCard(text, options).component] };
|
|
687
467
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
async function fetchManyGuildMembers(guild, userIds) {
|
|
699
|
-
return (await Promise.allSettled(userIds.map((userId) => fetchGuildMember(guild, userId)))).filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
468
|
+
async function collectChoice(message, userId, timeoutMs) {
|
|
469
|
+
try {
|
|
470
|
+
return await message.awaitMessageComponent({
|
|
471
|
+
componentType: ComponentType.Button,
|
|
472
|
+
time: timeoutMs,
|
|
473
|
+
filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
|
|
474
|
+
});
|
|
475
|
+
} catch {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
700
478
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
719
|
-
|
|
479
|
+
async function settle(sender, message, outcome) {
|
|
480
|
+
if (outcome === void 0) {
|
|
481
|
+
await sender.delete(message).catch(() => void 0);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
485
|
+
}
|
|
486
|
+
async function getConfirmation(handler, prompt, options) {
|
|
487
|
+
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
488
|
+
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
489
|
+
const interaction = handler.getEvent();
|
|
490
|
+
const sender = handler.getSender();
|
|
491
|
+
const message = await sender.send(response, { ephemeral });
|
|
492
|
+
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
493
|
+
if (!winner) {
|
|
494
|
+
await settle(sender, message, options?.onTimeout);
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
await winner.deferUpdate().catch(() => void 0);
|
|
498
|
+
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
499
|
+
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
500
|
+
return confirmed;
|
|
720
501
|
}
|
|
721
502
|
|
|
722
503
|
//#endregion
|
|
723
|
-
//#region src/
|
|
504
|
+
//#region src/handlers/BaseHandler.ts
|
|
724
505
|
/**
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* @param client - The Discord client instance
|
|
728
|
-
* @param userIds - Array of Discord user IDs
|
|
729
|
-
* @param options - Optional overrides threaded to each {@link fetchUser} call
|
|
730
|
-
* @returns Promise resolving to array of successfully fetched users
|
|
506
|
+
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
507
|
+
* gate hook.
|
|
731
508
|
*/
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
509
|
+
var BaseHandler$1 = class extends BaseHandler {
|
|
510
|
+
/** @internal */
|
|
511
|
+
getEvent() {
|
|
512
|
+
return this.event;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
735
515
|
|
|
736
516
|
//#endregion
|
|
737
|
-
//#region src/
|
|
517
|
+
//#region src/handlers/interaction/InteractionMiddleware.ts
|
|
738
518
|
/**
|
|
739
|
-
*
|
|
519
|
+
* Base class for interaction middleware
|
|
520
|
+
*
|
|
521
|
+
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
522
|
+
* Unlike handlers, middleware should not send responses directly.
|
|
740
523
|
*
|
|
741
|
-
* @
|
|
742
|
-
* @param rolesToRemove - Array of role IDs to remove from the member
|
|
743
|
-
* @param member - The guild member to update
|
|
744
|
-
* @returns Promise that resolves when roles are updated
|
|
524
|
+
* @typeParam Repliable - The interaction type this middleware processes
|
|
745
525
|
*/
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
await member.roles.set([...updated]);
|
|
752
|
-
}
|
|
526
|
+
var InteractionMiddleware = class extends BaseHandler$1 {
|
|
527
|
+
constructor(event, core) {
|
|
528
|
+
super(event, core, void 0, "interactions");
|
|
529
|
+
}
|
|
530
|
+
};
|
|
753
531
|
|
|
754
532
|
//#endregion
|
|
755
533
|
//#region src/bot/ReplySender.ts
|
|
@@ -861,266 +639,6 @@ var ReplySender = class extends BaseReplySender {
|
|
|
861
639
|
}
|
|
862
640
|
};
|
|
863
641
|
|
|
864
|
-
//#endregion
|
|
865
|
-
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
866
|
-
/**
|
|
867
|
-
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
868
|
-
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
869
|
-
* interaction and a generated registry key always match.
|
|
870
|
-
*
|
|
871
|
-
* @internal
|
|
872
|
-
*/
|
|
873
|
-
function slashRouteOf(interaction) {
|
|
874
|
-
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
875
|
-
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
876
|
-
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
//#endregion
|
|
880
|
-
//#region src/miscellaneous/extractErrorResponse.ts
|
|
881
|
-
const logger$2 = new Logger("ErrorsHandling", { channel: "errors" });
|
|
882
|
-
/**
|
|
883
|
-
* Processes an error into a user-facing reply and routes its report.
|
|
884
|
-
*
|
|
885
|
-
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
886
|
-
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
887
|
-
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
888
|
-
* threaded into both the reply and the bus payload.
|
|
889
|
-
*/
|
|
890
|
-
function extractErrorResponse(error, core, origin) {
|
|
891
|
-
const uuid = crypto.randomUUID();
|
|
892
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
893
|
-
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
894
|
-
uuid,
|
|
895
|
-
developerUsername
|
|
896
|
-
};
|
|
897
|
-
if (error instanceof Notice) {
|
|
898
|
-
if (error.report) reportFault(error, core, origin, uuid);
|
|
899
|
-
return {
|
|
900
|
-
uuid,
|
|
901
|
-
response: error.render(ctx)
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
reportRawFault(error, core, origin, uuid);
|
|
905
|
-
const override = core.config.errors?.defaultError;
|
|
906
|
-
return {
|
|
907
|
-
uuid,
|
|
908
|
-
response: override ? new override(uuid).render(ctx) : new Fault().render(ctx)
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
function withThrottle(core, origin, error, publish) {
|
|
912
|
-
const throttle = FaultThrottle.for(core);
|
|
913
|
-
const key = faultKey(origin, error);
|
|
914
|
-
if (!throttle.shouldReport(key)) {
|
|
915
|
-
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
publish();
|
|
919
|
-
throttle.markReported(key);
|
|
920
|
-
}
|
|
921
|
-
function reportFault(denial, core, origin, uuid) {
|
|
922
|
-
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
923
|
-
withThrottle(core, origin, denial, () => {
|
|
924
|
-
if (origin.interaction) core.bus[PublishDefault]("handledException", {
|
|
925
|
-
denial,
|
|
926
|
-
uuid,
|
|
927
|
-
source: buildInteractionSource(origin.interaction)
|
|
928
|
-
});
|
|
929
|
-
else if (origin.event) core.bus[PublishDefault]("handledException", {
|
|
930
|
-
denial,
|
|
931
|
-
uuid,
|
|
932
|
-
source: buildEventSource(origin.event, origin)
|
|
933
|
-
});
|
|
934
|
-
else core.bus[PublishDefault]("unknownException", {
|
|
935
|
-
uuid,
|
|
936
|
-
error: denial,
|
|
937
|
-
...scalarActors(origin),
|
|
938
|
-
metadata: metadataFor(origin)
|
|
939
|
-
});
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
function causeLine(error) {
|
|
943
|
-
const { cause } = error;
|
|
944
|
-
if (!Error.isError(cause)) return "";
|
|
945
|
-
const [first] = cause.message.split("\n");
|
|
946
|
-
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
947
|
-
}
|
|
948
|
-
function reportRawFault(error, core, origin, uuid) {
|
|
949
|
-
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
950
|
-
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
951
|
-
withThrottle(core, origin, error, () => {
|
|
952
|
-
core.bus[PublishDefault]("unknownException", {
|
|
953
|
-
uuid,
|
|
954
|
-
error,
|
|
955
|
-
...scalarActors(origin),
|
|
956
|
-
metadata: metadataFor(origin)
|
|
957
|
-
});
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
function scalarActors(origin) {
|
|
961
|
-
return {
|
|
962
|
-
guild: origin.guild ? {
|
|
963
|
-
id: origin.guild.id,
|
|
964
|
-
name: origin.guild.name
|
|
965
|
-
} : void 0,
|
|
966
|
-
user: origin.user ? {
|
|
967
|
-
id: origin.user.id,
|
|
968
|
-
username: origin.user.username
|
|
969
|
-
} : void 0
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
function metadataFor(origin) {
|
|
973
|
-
if (origin.event) return {
|
|
974
|
-
eventName: origin.event.name,
|
|
975
|
-
handler: origin.event.handler,
|
|
976
|
-
args: origin.event.args
|
|
977
|
-
};
|
|
978
|
-
return origin.metadata;
|
|
979
|
-
}
|
|
980
|
-
function buildEventSource(event, origin) {
|
|
981
|
-
return {
|
|
982
|
-
kind: "event",
|
|
983
|
-
eventName: event.name,
|
|
984
|
-
handler: event.handler,
|
|
985
|
-
userId: origin.user?.id ?? null,
|
|
986
|
-
guildId: origin.guild?.id ?? null,
|
|
987
|
-
channelId: event.channelId,
|
|
988
|
-
raw: event.args
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
function faultKey(origin, error) {
|
|
992
|
-
const name = error instanceof Notice ? error.name : error instanceof DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
993
|
-
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
994
|
-
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
995
|
-
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
996
|
-
return `autocomplete:${name}`;
|
|
997
|
-
}
|
|
998
|
-
/** The stable route id of a repliable interaction, its slash route path, command name, or customId prefix. */
|
|
999
|
-
function interactionRoute(interaction) {
|
|
1000
|
-
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1001
|
-
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1002
|
-
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return prefixOf(interaction.customId) || interaction.customId;
|
|
1003
|
-
return "interaction";
|
|
1004
|
-
}
|
|
1005
|
-
function buildInteractionSource(interaction) {
|
|
1006
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1007
|
-
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1008
|
-
return {
|
|
1009
|
-
kind: "interaction",
|
|
1010
|
-
interactionKind: interactionKind(interaction),
|
|
1011
|
-
command,
|
|
1012
|
-
customId,
|
|
1013
|
-
userId: interaction.user.id,
|
|
1014
|
-
guildId: interaction.guildId,
|
|
1015
|
-
channelId: interaction.channelId,
|
|
1016
|
-
interactionId: interaction.id,
|
|
1017
|
-
raw: interaction
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
function interactionKind(interaction) {
|
|
1021
|
-
if (interaction.isChatInputCommand()) return "slash";
|
|
1022
|
-
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1023
|
-
if (interaction.isButton()) return "button";
|
|
1024
|
-
if (interaction.isAnySelectMenu()) return "select";
|
|
1025
|
-
return "modal";
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
//#endregion
|
|
1029
|
-
//#region src/bot/confirm/reserved.ts
|
|
1030
|
-
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
1031
|
-
/**
|
|
1032
|
-
* The reserved confirm/cancel codec. It has no route, so the dispatcher ignores its clicks and only the
|
|
1033
|
-
* in-process collector reads them. Do not mint a real component from it.
|
|
1034
|
-
*
|
|
1035
|
-
* @internal
|
|
1036
|
-
*/
|
|
1037
|
-
const CONFIRM_DEF = new CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
|
|
1038
|
-
|
|
1039
|
-
//#endregion
|
|
1040
|
-
//#region src/bot/confirm/getConfirmation.ts
|
|
1041
|
-
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
1042
|
-
const CONFIRM_IDS = {
|
|
1043
|
-
confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
|
|
1044
|
-
cancel: CONFIRM_DEF.encode({ choice: "cancel" })
|
|
1045
|
-
};
|
|
1046
|
-
var ConfirmButtons = class extends RowComponent {
|
|
1047
|
-
constructor(options) {
|
|
1048
|
-
super("button");
|
|
1049
|
-
this.instance.addComponents(new ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? ButtonStyle.Danger), new ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(ButtonStyle.Secondary));
|
|
1050
|
-
}
|
|
1051
|
-
};
|
|
1052
|
-
var ConfirmPromptCard = class extends BuilderComponent {
|
|
1053
|
-
constructor(text, options) {
|
|
1054
|
-
super("container");
|
|
1055
|
-
this.instance.addTextDisplayComponents(new TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
function defaultPrompt(text, options) {
|
|
1059
|
-
return { components: [new ConfirmPromptCard(text, options).component] };
|
|
1060
|
-
}
|
|
1061
|
-
async function collectChoice(message, userId, timeoutMs) {
|
|
1062
|
-
try {
|
|
1063
|
-
return await message.awaitMessageComponent({
|
|
1064
|
-
componentType: ComponentType.Button,
|
|
1065
|
-
time: timeoutMs,
|
|
1066
|
-
filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
|
|
1067
|
-
});
|
|
1068
|
-
} catch {
|
|
1069
|
-
return null;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
async function settle(sender, message, outcome) {
|
|
1073
|
-
if (outcome === void 0) {
|
|
1074
|
-
await sender.delete(message).catch(() => void 0);
|
|
1075
|
-
return;
|
|
1076
|
-
}
|
|
1077
|
-
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
1078
|
-
}
|
|
1079
|
-
async function getConfirmation(interaction, prompt, options) {
|
|
1080
|
-
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
1081
|
-
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
1082
|
-
const sender = new ReplySender(interaction, interactionRoute(interaction));
|
|
1083
|
-
const message = await sender.send(response, { ephemeral });
|
|
1084
|
-
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
1085
|
-
if (!winner) {
|
|
1086
|
-
await settle(sender, message, options?.onTimeout);
|
|
1087
|
-
return false;
|
|
1088
|
-
}
|
|
1089
|
-
await winner.deferUpdate().catch(() => void 0);
|
|
1090
|
-
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
1091
|
-
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
1092
|
-
return confirmed;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
//#endregion
|
|
1096
|
-
//#region src/handlers/BaseHandler.ts
|
|
1097
|
-
/**
|
|
1098
|
-
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
1099
|
-
* gate hook.
|
|
1100
|
-
*/
|
|
1101
|
-
var BaseHandler$1 = class extends BaseHandler {
|
|
1102
|
-
/** @internal */
|
|
1103
|
-
getEvent() {
|
|
1104
|
-
return this.event;
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
|
|
1108
|
-
//#endregion
|
|
1109
|
-
//#region src/handlers/interaction/InteractionMiddleware.ts
|
|
1110
|
-
/**
|
|
1111
|
-
* Base class for interaction middleware
|
|
1112
|
-
*
|
|
1113
|
-
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
1114
|
-
* Unlike handlers, middleware should not send responses directly.
|
|
1115
|
-
*
|
|
1116
|
-
* @typeParam Repliable - The interaction type this middleware processes
|
|
1117
|
-
*/
|
|
1118
|
-
var InteractionMiddleware = class extends BaseHandler$1 {
|
|
1119
|
-
constructor(event, core) {
|
|
1120
|
-
super(event, core, void 0, "interactions");
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
642
|
//#endregion
|
|
1125
643
|
//#region src/handlers/RepliableHandler.ts
|
|
1126
644
|
/**
|
|
@@ -1166,6 +684,21 @@ var InteractionHandler = class extends RepliableHandler {
|
|
|
1166
684
|
}
|
|
1167
685
|
};
|
|
1168
686
|
|
|
687
|
+
//#endregion
|
|
688
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
689
|
+
/**
|
|
690
|
+
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
691
|
+
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
692
|
+
* interaction and a generated registry key always match.
|
|
693
|
+
*
|
|
694
|
+
* @internal
|
|
695
|
+
*/
|
|
696
|
+
function slashRouteOf(interaction) {
|
|
697
|
+
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
698
|
+
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
699
|
+
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
700
|
+
}
|
|
701
|
+
|
|
1169
702
|
//#endregion
|
|
1170
703
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
1171
704
|
/**
|
|
@@ -1485,7 +1018,22 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1485
1018
|
* }
|
|
1486
1019
|
* ```
|
|
1487
1020
|
*/
|
|
1488
|
-
var ModalHandler = class extends ComponentHandler {
|
|
1021
|
+
var ModalHandler = class extends ComponentHandler {
|
|
1022
|
+
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
1023
|
+
update(response) {
|
|
1024
|
+
this.ensureSourceMessage("update");
|
|
1025
|
+
return super.update(response);
|
|
1026
|
+
}
|
|
1027
|
+
/** Acknowledge the submit without changing the source message. Throws when a command opened the modal. */
|
|
1028
|
+
deferUpdate() {
|
|
1029
|
+
this.ensureSourceMessage("deferUpdate");
|
|
1030
|
+
return super.deferUpdate();
|
|
1031
|
+
}
|
|
1032
|
+
ensureSourceMessage(method) {
|
|
1033
|
+
if (this.event.isFromMessage()) return;
|
|
1034
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1489
1037
|
|
|
1490
1038
|
//#endregion
|
|
1491
1039
|
//#region src/handlers/interaction/components/SelectMenuHandler.ts
|
|
@@ -1599,138 +1147,14 @@ var EventMiddleware = class extends BaseHandler$1 {
|
|
|
1599
1147
|
}
|
|
1600
1148
|
/**
|
|
1601
1149
|
* The event that fired this middleware. Read it on a catchall or multi-event middleware, where `this.event`
|
|
1602
|
-
* is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
|
|
1603
|
-
* `this.event` directly and does not need this.
|
|
1604
|
-
*/
|
|
1605
|
-
get eventName() {
|
|
1606
|
-
if (this.firedEvent === void 0) throw new SeedcordError(SeedcordErrorCode.EventMiddlewareNameUnavailable);
|
|
1607
|
-
return this.firedEvent;
|
|
1608
|
-
}
|
|
1609
|
-
};
|
|
1610
|
-
|
|
1611
|
-
//#endregion
|
|
1612
|
-
//#region src/pagination/controls.ts
|
|
1613
|
-
const DEFAULT_LABEL = {
|
|
1614
|
-
first: "First",
|
|
1615
|
-
prev: "Prev",
|
|
1616
|
-
indicator: "",
|
|
1617
|
-
next: "Next",
|
|
1618
|
-
last: "Last"
|
|
1619
|
-
};
|
|
1620
|
-
const CONTROL_SLOT = {
|
|
1621
|
-
first: 0,
|
|
1622
|
-
prev: 1,
|
|
1623
|
-
indicator: 2,
|
|
1624
|
-
next: 3,
|
|
1625
|
-
last: 4
|
|
1626
|
-
};
|
|
1627
|
-
const MAX_BUTTONS_PER_ROW = 5;
|
|
1628
|
-
var Controls = class {
|
|
1629
|
-
cursor;
|
|
1630
|
-
view;
|
|
1631
|
-
constructor(cursor, view) {
|
|
1632
|
-
this.cursor = cursor;
|
|
1633
|
-
this.view = view;
|
|
1634
|
-
}
|
|
1635
|
-
button(key, cosmetics) {
|
|
1636
|
-
const { target, disabled } = this.navState(key);
|
|
1637
|
-
const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
|
|
1638
|
-
const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
|
|
1639
|
-
const button = new ButtonBuilder().setCustomId(this.cursor.encode({
|
|
1640
|
-
page: Math.min(Math.max(0, target), PAGE_MAX),
|
|
1641
|
-
slot: CONTROL_SLOT[key]
|
|
1642
|
-
})).setStyle(cosmetics?.style ?? ButtonStyle.Secondary).setDisabled(disabled);
|
|
1643
|
-
if (label !== void 0) button.setLabel(label);
|
|
1644
|
-
if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
|
|
1645
|
-
return button;
|
|
1646
|
-
}
|
|
1647
|
-
navState(key) {
|
|
1648
|
-
const { page, totalPages, hasPrev, hasNext } = this.view;
|
|
1649
|
-
const knownTotal = totalPages !== void 0;
|
|
1650
|
-
return {
|
|
1651
|
-
target: {
|
|
1652
|
-
first: 0,
|
|
1653
|
-
prev: page - 1,
|
|
1654
|
-
indicator: page,
|
|
1655
|
-
next: page + 1,
|
|
1656
|
-
last: knownTotal ? totalPages - 1 : page
|
|
1657
|
-
}[key],
|
|
1658
|
-
disabled: {
|
|
1659
|
-
first: !hasPrev,
|
|
1660
|
-
prev: !hasPrev,
|
|
1661
|
-
indicator: true,
|
|
1662
|
-
next: !hasNext || page + 1 > PAGE_MAX,
|
|
1663
|
-
last: !hasNext || !knownTotal || totalPages - 1 > PAGE_MAX
|
|
1664
|
-
}[key]
|
|
1665
|
-
};
|
|
1666
|
-
}
|
|
1667
|
-
row(...keys) {
|
|
1668
|
-
if (keys.length === 0) throw new SeedcordRangeError(SeedcordErrorCode.PaginationEmptyControls);
|
|
1669
|
-
if (keys.length > MAX_BUTTONS_PER_ROW) throw new SeedcordRangeError(SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
|
|
1670
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1671
|
-
for (const key of keys) {
|
|
1672
|
-
if (seen.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.PaginationDuplicateControls, [key]);
|
|
1673
|
-
seen.add(key);
|
|
1674
|
-
}
|
|
1675
|
-
return new ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
|
|
1676
|
-
}
|
|
1677
|
-
indicatorText() {
|
|
1678
|
-
const { page, totalPages } = this.view;
|
|
1679
|
-
return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
|
|
1680
|
-
}
|
|
1681
|
-
};
|
|
1682
|
-
|
|
1683
|
-
//#endregion
|
|
1684
|
-
//#region src/pagination/render.ts
|
|
1685
|
-
var PageContainer = class extends BuilderComponent {
|
|
1686
|
-
constructor(view, renderItem, controlRow) {
|
|
1687
|
-
super("container");
|
|
1688
|
-
const base = view.page * view.perPage;
|
|
1689
|
-
let lines = [];
|
|
1690
|
-
const flush = () => {
|
|
1691
|
-
if (lines.length === 0) return;
|
|
1692
|
-
this.instance.addTextDisplayComponents(new TextDisplayBuilder().setContent(lines.join("\n")));
|
|
1693
|
-
lines = [];
|
|
1694
|
-
};
|
|
1695
|
-
view.items.forEach((item, offset) => {
|
|
1696
|
-
const rendered = renderItem(item, base + offset);
|
|
1697
|
-
if (typeof rendered === "string") {
|
|
1698
|
-
lines.push(rendered);
|
|
1699
|
-
return;
|
|
1700
|
-
}
|
|
1701
|
-
flush();
|
|
1702
|
-
this.instance.addSectionComponents(rendered.component);
|
|
1703
|
-
});
|
|
1704
|
-
flush();
|
|
1705
|
-
this.instance.addActionRowComponents(controlRow);
|
|
1150
|
+
* is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
|
|
1151
|
+
* `this.event` directly and does not need this.
|
|
1152
|
+
*/
|
|
1153
|
+
get eventName() {
|
|
1154
|
+
if (this.firedEvent === void 0) throw new SeedcordError(SeedcordErrorCode.EventMiddlewareNameUnavailable);
|
|
1155
|
+
return this.firedEvent;
|
|
1706
1156
|
}
|
|
1707
1157
|
};
|
|
1708
|
-
function toReplyResponse(renderable) {
|
|
1709
|
-
if (typeof renderable === "string") return { components: [new TextDisplayBuilder().setContent(renderable)] };
|
|
1710
|
-
if (Array.isArray(renderable)) return { components: renderable };
|
|
1711
|
-
return renderable;
|
|
1712
|
-
}
|
|
1713
|
-
const ARRAY_KEYS = [
|
|
1714
|
-
"first",
|
|
1715
|
-
"prev",
|
|
1716
|
-
"indicator",
|
|
1717
|
-
"next",
|
|
1718
|
-
"last"
|
|
1719
|
-
];
|
|
1720
|
-
const CURSOR_KEYS = [
|
|
1721
|
-
"prev",
|
|
1722
|
-
"indicator",
|
|
1723
|
-
"next"
|
|
1724
|
-
];
|
|
1725
|
-
/**
|
|
1726
|
-
* Build a page's V2 reply. A `render` override builds the whole tree, otherwise the default container lists
|
|
1727
|
-
* the items and appends the controls (all five for a known total, prev/indicator/next for a cursor source).
|
|
1728
|
-
*/
|
|
1729
|
-
function renderPage(view, cursor, config) {
|
|
1730
|
-
const controls = new Controls(cursor, view);
|
|
1731
|
-
if (config.render) return toReplyResponse(config.render(view, controls));
|
|
1732
|
-
return { components: [new PageContainer(view, config.renderItem ?? String, controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS)).component] };
|
|
1733
|
-
}
|
|
1734
1158
|
|
|
1735
1159
|
//#endregion
|
|
1736
1160
|
//#region src/pagination/Paginator.ts
|
|
@@ -1739,7 +1163,7 @@ function contextOf(interaction, core) {
|
|
|
1739
1163
|
interaction,
|
|
1740
1164
|
user: interaction.user,
|
|
1741
1165
|
guild: interaction.guild,
|
|
1742
|
-
|
|
1166
|
+
core
|
|
1743
1167
|
};
|
|
1744
1168
|
}
|
|
1745
1169
|
/**
|
|
@@ -1761,15 +1185,11 @@ function contextOf(interaction, core) {
|
|
|
1761
1185
|
* export class BansNav extends Bans.Handler {}
|
|
1762
1186
|
* ```
|
|
1763
1187
|
*/
|
|
1764
|
-
var Paginator = class {
|
|
1765
|
-
/** The page cursor, pass it to your `@ButtonRoute`. */
|
|
1766
|
-
cursor;
|
|
1188
|
+
var Paginator = class extends PaginatorBase {
|
|
1767
1189
|
/** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
|
|
1768
1190
|
Handler;
|
|
1769
|
-
config;
|
|
1770
1191
|
constructor(config) {
|
|
1771
|
-
|
|
1772
|
-
this.config = config;
|
|
1192
|
+
super(config);
|
|
1773
1193
|
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
1774
1194
|
this.Handler = class Nav extends ButtonHandler {
|
|
1775
1195
|
async execute() {
|
|
@@ -1780,7 +1200,8 @@ var Paginator = class {
|
|
|
1780
1200
|
};
|
|
1781
1201
|
}
|
|
1782
1202
|
/**
|
|
1783
|
-
* Render page 0 and send it through the handler's sender,
|
|
1203
|
+
* Render page 0 and send it through the handler's sender, which picks reply, edit, or followUp from its
|
|
1204
|
+
* ack state.
|
|
1784
1205
|
*
|
|
1785
1206
|
* @param handler - The handler starting the paginator, normally `this`.
|
|
1786
1207
|
*/
|
|
@@ -1789,34 +1210,18 @@ var Paginator = class {
|
|
|
1789
1210
|
const response = await this.page(contextOf(interaction, handler.core), 0);
|
|
1790
1211
|
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1791
1212
|
}
|
|
1792
|
-
/** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
|
|
1793
|
-
async page(ctx, n) {
|
|
1794
|
-
return renderPage(await this.config.source.page(ctx, n), this.cursor, this.config);
|
|
1795
|
-
}
|
|
1796
1213
|
};
|
|
1797
1214
|
|
|
1798
1215
|
//#endregion
|
|
1799
1216
|
//#region src/pagination/sources.ts
|
|
1800
|
-
const DEFAULT_PER_PAGE = 10;
|
|
1801
1217
|
/**
|
|
1802
1218
|
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
1803
|
-
* page
|
|
1804
|
-
*
|
|
1219
|
+
* page, so the real total is known (a real last button, "Page X of Y"). Cache inside your loader if the
|
|
1220
|
+
* load is expensive.
|
|
1805
1221
|
*
|
|
1806
1222
|
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1807
1223
|
*/
|
|
1808
|
-
var ArraySource = class {
|
|
1809
|
-
load;
|
|
1810
|
-
perPage;
|
|
1811
|
-
constructor(load, opts) {
|
|
1812
|
-
this.load = load;
|
|
1813
|
-
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
1814
|
-
if (!Number.isSafeInteger(this.perPage) || this.perPage <= 0) throw new SeedcordRangeError(SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
1815
|
-
}
|
|
1816
|
-
async page(ctx, n) {
|
|
1817
|
-
return paginate(await this.load(ctx), n, this.perPage);
|
|
1818
|
-
}
|
|
1819
|
-
};
|
|
1224
|
+
var ArraySource = class extends ArraySource$1 {};
|
|
1820
1225
|
/**
|
|
1821
1226
|
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1822
1227
|
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
@@ -1825,26 +1230,7 @@ var ArraySource = class {
|
|
|
1825
1230
|
*
|
|
1826
1231
|
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1827
1232
|
*/
|
|
1828
|
-
var CursorSource = class {
|
|
1829
|
-
fetch;
|
|
1830
|
-
perPage;
|
|
1831
|
-
constructor(fetch, opts) {
|
|
1832
|
-
this.fetch = fetch;
|
|
1833
|
-
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
1834
|
-
if (!Number.isSafeInteger(this.perPage) || this.perPage <= 0) throw new SeedcordRangeError(SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
1835
|
-
}
|
|
1836
|
-
async page(ctx, n) {
|
|
1837
|
-
const page = Math.max(0, Math.trunc(n));
|
|
1838
|
-
const { items, hasNext } = await this.fetch(ctx, page, this.perPage);
|
|
1839
|
-
return {
|
|
1840
|
-
items: [...items],
|
|
1841
|
-
page,
|
|
1842
|
-
perPage: this.perPage,
|
|
1843
|
-
hasPrev: page > 0,
|
|
1844
|
-
hasNext
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
1847
|
-
};
|
|
1233
|
+
var CursorSource = class extends CursorSource$1 {};
|
|
1848
1234
|
|
|
1849
1235
|
//#endregion
|
|
1850
1236
|
//#region src/subscribers/index.ts
|
|
@@ -1868,236 +1254,12 @@ var WebhookLog = class extends WebhookLog$1 {};
|
|
|
1868
1254
|
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
1869
1255
|
*
|
|
1870
1256
|
* A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
|
|
1871
|
-
* `this.core`
|
|
1257
|
+
* `this.core` is typed to the shared members only.
|
|
1872
1258
|
*
|
|
1873
|
-
* @typeParam Opts - The transport
|
|
1259
|
+
* @typeParam Opts - The transport and runtime the plugin declares.
|
|
1874
1260
|
*/
|
|
1875
1261
|
var Plugin = class extends Plugin$1 {};
|
|
1876
1262
|
|
|
1877
|
-
//#endregion
|
|
1878
|
-
//#region src/bot/utilities/miscellaneous/contextMenuLeaves.ts
|
|
1879
|
-
/**
|
|
1880
|
-
* Collects the names of every registered context-menu command across a set of command builders, split into a
|
|
1881
|
-
* user set and a message set. The same builder may appear more than once (a guild command is pushed once per
|
|
1882
|
-
* guild), and slash commands carry no context-menu name, so both collapse here. The split mirrors the
|
|
1883
|
-
* controller's two name-keyed maps, so the boot guard checks each name against its own kind.
|
|
1884
|
-
*
|
|
1885
|
-
* @internal
|
|
1886
|
-
*/
|
|
1887
|
-
function contextMenuLeaves(commands) {
|
|
1888
|
-
const user = /* @__PURE__ */ new Set();
|
|
1889
|
-
const message = /* @__PURE__ */ new Set();
|
|
1890
|
-
for (const command of commands) {
|
|
1891
|
-
if (!(command instanceof ContextMenuCommandBuilder)) continue;
|
|
1892
|
-
if (command.type === ApplicationCommandType.User) user.add(command.name);
|
|
1893
|
-
else message.add(command.name);
|
|
1894
|
-
}
|
|
1895
|
-
return {
|
|
1896
|
-
user,
|
|
1897
|
-
message
|
|
1898
|
-
};
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
//#endregion
|
|
1902
|
-
//#region src/bot/utilities/miscellaneous/slashRouteLeaves.ts
|
|
1903
|
-
/**
|
|
1904
|
-
* Collects the route keys of every executable slash leaf across a set of command builders, deduplicated. The
|
|
1905
|
-
* same builder may appear more than once (a guild command is pushed once per guild), and context-menu
|
|
1906
|
-
* commands carry no slash route, so both collapse here. Route keys come from the same {@link routeLeavesOf}
|
|
1907
|
-
* that `seedcord codegen` reads, so the boot-time check compares against the generated registry's keys.
|
|
1908
|
-
*
|
|
1909
|
-
* @internal
|
|
1910
|
-
*/
|
|
1911
|
-
function slashRouteLeaves(commands) {
|
|
1912
|
-
const leaves = /* @__PURE__ */ new Set();
|
|
1913
|
-
for (const command of commands) {
|
|
1914
|
-
if (!(command instanceof SlashCommandBuilder)) continue;
|
|
1915
|
-
for (const leaf of routeLeavesOf(command.toJSON())) leaves.add(leaf.route);
|
|
1916
|
-
}
|
|
1917
|
-
return leaves;
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
//#endregion
|
|
1921
|
-
//#region src/bot/controllers/CommandRegistry.ts
|
|
1922
|
-
/**
|
|
1923
|
-
* Manages Discord application command registration and deployment.
|
|
1924
|
-
*
|
|
1925
|
-
* Scans command directories, builds command structures, and registers both global and guild-scoped commands
|
|
1926
|
-
* to Discord's API. Accessed via `core.bot.commands`. Do not construct it directly.
|
|
1927
|
-
*/
|
|
1928
|
-
var CommandRegistry = class {
|
|
1929
|
-
core;
|
|
1930
|
-
onDeployed;
|
|
1931
|
-
logger = new Logger("Commands", { channel: "commands" });
|
|
1932
|
-
isInitialised = false;
|
|
1933
|
-
globalCommands = [];
|
|
1934
|
-
guildCommands = new Collection();
|
|
1935
|
-
ctorToCommand = /* @__PURE__ */ new Map();
|
|
1936
|
-
loading = false;
|
|
1937
|
-
loadedCommands = [];
|
|
1938
|
-
hmrHandler;
|
|
1939
|
-
pendingEvents = /* @__PURE__ */ new Map();
|
|
1940
|
-
constructor(core, onDeployed) {
|
|
1941
|
-
this.core = core;
|
|
1942
|
-
this.onDeployed = onDeployed;
|
|
1943
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
1944
|
-
if (!commandsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
|
|
1945
|
-
if (!Envapter.isDevelopment && !Envapter.isTest) return;
|
|
1946
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
1947
|
-
handlersDir: commandsDir,
|
|
1948
|
-
isHandler: this.isCommandClass.bind(this),
|
|
1949
|
-
registerHandler: this.registerCommand.bind(this),
|
|
1950
|
-
unregisterHandler: this.unregisterCommand.bind(this),
|
|
1951
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
1952
|
-
logger: this.logger
|
|
1953
|
-
});
|
|
1954
|
-
}
|
|
1955
|
-
getArtifacts(ctor) {
|
|
1956
|
-
return this.ctorToCommand.get(ctor);
|
|
1957
|
-
}
|
|
1958
|
-
/** @internal */
|
|
1959
|
-
async init() {
|
|
1960
|
-
if (this.isInitialised) return;
|
|
1961
|
-
this.isInitialised = true;
|
|
1962
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
1963
|
-
if (!commandsDir) return;
|
|
1964
|
-
this.loading = true;
|
|
1965
|
-
this.loadedCommands.length = 0;
|
|
1966
|
-
try {
|
|
1967
|
-
await this.loadCommands(commandsDir);
|
|
1968
|
-
} finally {
|
|
1969
|
-
this.loading = false;
|
|
1970
|
-
}
|
|
1971
|
-
this.reportLoad();
|
|
1972
|
-
getDevChannel()?.on("seedcord:refresh-commands", (data) => {
|
|
1973
|
-
this.refresh(data.shouldRefresh);
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
|
-
reportLoad() {
|
|
1977
|
-
const { utils } = this.logger;
|
|
1978
|
-
utils.summary("Loaded commands", {
|
|
1979
|
-
global: this.globalCommands.length,
|
|
1980
|
-
"guild groups": this.guildCommands.size
|
|
1981
|
-
});
|
|
1982
|
-
let slash = 0;
|
|
1983
|
-
let menu = 0;
|
|
1984
|
-
for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
|
|
1985
|
-
else menu++;
|
|
1986
|
-
utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
|
|
1987
|
-
"slash commands": slash,
|
|
1988
|
-
"context menus": menu
|
|
1989
|
-
})]);
|
|
1990
|
-
}
|
|
1991
|
-
/** @internal */
|
|
1992
|
-
async refresh(shouldRefresh = true) {
|
|
1993
|
-
if (!shouldRefresh) {
|
|
1994
|
-
this.logger.info(chalk.italic("Command refresh cancelled."));
|
|
1995
|
-
this.pendingEvents.clear();
|
|
1996
|
-
return;
|
|
1997
|
-
}
|
|
1998
|
-
this.logger.info(chalk.italic("Refreshing commands..."));
|
|
1999
|
-
for (const event of this.pendingEvents.values()) await this.hmrHandler?.handle(event);
|
|
2000
|
-
this.pendingEvents.clear();
|
|
2001
|
-
await this.setCommands();
|
|
2002
|
-
}
|
|
2003
|
-
/** @internal */
|
|
2004
|
-
async onHmr(event) {
|
|
2005
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
2006
|
-
if (commandsDir && event.file.startsWith(resolve(process.cwd(), commandsDir))) {
|
|
2007
|
-
this.pendingEvents.delete(event.file);
|
|
2008
|
-
this.pendingEvents.set(event.file, event);
|
|
2009
|
-
getDevChannel()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => formatFilePath(f)) });
|
|
2010
|
-
} else await this.hmrHandler?.handle(event);
|
|
2011
|
-
}
|
|
2012
|
-
async loadCommands(dir) {
|
|
2013
|
-
await traverseDirectory(dir, (fullPath, rel, mod) => {
|
|
2014
|
-
for (const exported of Object.values(mod)) if (this.isCommandClass(exported)) {
|
|
2015
|
-
this.registerCommand(exported, rel);
|
|
2016
|
-
this.hmrHandler?.trackHandler(fullPath, exported);
|
|
2017
|
-
}
|
|
2018
|
-
});
|
|
2019
|
-
}
|
|
2020
|
-
isCommandClass(obj) {
|
|
2021
|
-
if (typeof obj !== "function") return false;
|
|
2022
|
-
return obj.prototype instanceof BuilderComponent && Reflect.hasMetadata(CommandMetadataKey, obj);
|
|
2023
|
-
}
|
|
2024
|
-
registerCommand(ctor, rel) {
|
|
2025
|
-
const meta = Reflect.getMetadata(CommandMetadataKey, ctor);
|
|
2026
|
-
if (!meta) return;
|
|
2027
|
-
const comp = new ctor().component;
|
|
2028
|
-
const kind = comp instanceof SlashCommandBuilder ? "slash command" : "context menu";
|
|
2029
|
-
if (meta.scope === "global") this.globalCommands.push(comp);
|
|
2030
|
-
else for (const g of meta.guilds) {
|
|
2031
|
-
const arr = this.guildCommands.get(g) ?? [];
|
|
2032
|
-
arr.push(comp);
|
|
2033
|
-
this.guildCommands.set(g, arr);
|
|
2034
|
-
}
|
|
2035
|
-
this.ctorToCommand.set(ctor, {
|
|
2036
|
-
name: comp.name,
|
|
2037
|
-
scope: meta.scope,
|
|
2038
|
-
...meta.scope === "guild" && { guilds: meta.guilds }
|
|
2039
|
-
});
|
|
2040
|
-
if (this.loading) {
|
|
2041
|
-
this.loadedCommands.push({
|
|
2042
|
-
name: comp.name,
|
|
2043
|
-
from: formatFilePath(rel),
|
|
2044
|
-
kind
|
|
2045
|
-
});
|
|
2046
|
-
return;
|
|
2047
|
-
}
|
|
2048
|
-
this.logger.utils.registration(comp.name, rel, `${meta.scope} ${kind}`);
|
|
2049
|
-
}
|
|
2050
|
-
unregisterCommand(ctor, artifacts) {
|
|
2051
|
-
const info = artifacts ?? this.ctorToCommand.get(ctor);
|
|
2052
|
-
if (!info) return;
|
|
2053
|
-
if (info.scope === "global") {
|
|
2054
|
-
const idx = this.globalCommands.findIndex((c) => c.name === info.name);
|
|
2055
|
-
if (idx !== -1) this.globalCommands.splice(idx, 1);
|
|
2056
|
-
} else for (const g of info.guilds ?? []) {
|
|
2057
|
-
const arr = this.guildCommands.get(g);
|
|
2058
|
-
if (arr) {
|
|
2059
|
-
const idx = arr.findIndex((c) => c.name === info.name);
|
|
2060
|
-
if (idx !== -1) arr.splice(idx, 1);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
this.ctorToCommand.delete(ctor);
|
|
2064
|
-
}
|
|
2065
|
-
/** @internal */
|
|
2066
|
-
async setCommands() {
|
|
2067
|
-
const result = {
|
|
2068
|
-
global: new Collection(),
|
|
2069
|
-
guilds: new Collection()
|
|
2070
|
-
};
|
|
2071
|
-
if (this.globalCommands.length > 0) {
|
|
2072
|
-
const deployed = await this.core.bot.client.application?.commands.set(this.globalCommands);
|
|
2073
|
-
if (deployed) result.global = deployed;
|
|
2074
|
-
const tag = this.globalCommands.length === 1 ? "command" : "commands";
|
|
2075
|
-
this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
|
|
2076
|
-
}
|
|
2077
|
-
for (const [guildId, commands] of this.guildCommands.entries()) {
|
|
2078
|
-
const guild = this.core.bot.client.guilds.cache.get(guildId);
|
|
2079
|
-
if (!guild) {
|
|
2080
|
-
this.logger.warn(`Guild with ID ${guildId} not found, skipping command registration.`);
|
|
2081
|
-
continue;
|
|
2082
|
-
}
|
|
2083
|
-
const deployed = await guild.commands.set(commands);
|
|
2084
|
-
result.guilds.set(guildId, deployed);
|
|
2085
|
-
const tag = commands.length === 1 ? "command" : "commands";
|
|
2086
|
-
this.logger.utils.block(`Deployed ${commands.length} ${tag} to ${paint.amber.bold(guild.name)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
|
|
2087
|
-
}
|
|
2088
|
-
this.onDeployed?.(result);
|
|
2089
|
-
return result;
|
|
2090
|
-
}
|
|
2091
|
-
/** The deduplicated slash route keys across every global and guild command. @internal */
|
|
2092
|
-
routeLeaves() {
|
|
2093
|
-
return slashRouteLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2094
|
-
}
|
|
2095
|
-
/** The registered context-menu command names, split by kind, across every global and guild command. @internal */
|
|
2096
|
-
contextMenuLeaves() {
|
|
2097
|
-
return contextMenuLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2098
|
-
}
|
|
2099
|
-
};
|
|
2100
|
-
|
|
2101
1263
|
//#endregion
|
|
2102
1264
|
//#region src/miscellaneous/deriveEventActor.ts
|
|
2103
1265
|
function deriveEventActor(args) {
|
|
@@ -2175,6 +1337,154 @@ function eventGateContext(eventName, args, core) {
|
|
|
2175
1337
|
};
|
|
2176
1338
|
}
|
|
2177
1339
|
|
|
1340
|
+
//#endregion
|
|
1341
|
+
//#region src/miscellaneous/extractErrorResponse.ts
|
|
1342
|
+
const logger$2 = new Logger("ErrorsHandling", { channel: "errors" });
|
|
1343
|
+
/**
|
|
1344
|
+
* Processes an error into a user-facing reply and routes its report.
|
|
1345
|
+
*
|
|
1346
|
+
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
1347
|
+
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
1348
|
+
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
1349
|
+
* threaded into both the reply and the bus payload.
|
|
1350
|
+
*/
|
|
1351
|
+
function extractErrorResponse(error, core, origin) {
|
|
1352
|
+
const uuid = crypto.randomUUID();
|
|
1353
|
+
const developerUsername = core.config.notifications?.developerUsername;
|
|
1354
|
+
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
1355
|
+
uuid,
|
|
1356
|
+
developerUsername
|
|
1357
|
+
};
|
|
1358
|
+
if (error instanceof Notice) {
|
|
1359
|
+
if (error.report) reportFault(error, core, origin, uuid);
|
|
1360
|
+
return {
|
|
1361
|
+
uuid,
|
|
1362
|
+
response: error.render(ctx)
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
reportRawFault(error, core, origin, uuid);
|
|
1366
|
+
const override = core.config.errors?.defaultError;
|
|
1367
|
+
return {
|
|
1368
|
+
uuid,
|
|
1369
|
+
response: override ? new override(uuid).render(ctx) : new Fault().render(ctx)
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function withThrottle(core, origin, error, publish) {
|
|
1373
|
+
const throttle = FaultThrottle.for(core);
|
|
1374
|
+
const key = faultKey(origin, error);
|
|
1375
|
+
if (!throttle.shouldReport(key)) {
|
|
1376
|
+
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
publish();
|
|
1380
|
+
throttle.markReported(key);
|
|
1381
|
+
}
|
|
1382
|
+
function reportFault(denial, core, origin, uuid) {
|
|
1383
|
+
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
1384
|
+
withThrottle(core, origin, denial, () => {
|
|
1385
|
+
if (origin.interaction) core.bus[PublishDefault]("handledException", {
|
|
1386
|
+
denial,
|
|
1387
|
+
uuid,
|
|
1388
|
+
source: buildInteractionSource(origin.interaction)
|
|
1389
|
+
});
|
|
1390
|
+
else if (origin.event) core.bus[PublishDefault]("handledException", {
|
|
1391
|
+
denial,
|
|
1392
|
+
uuid,
|
|
1393
|
+
source: buildEventSource(origin.event, origin)
|
|
1394
|
+
});
|
|
1395
|
+
else core.bus[PublishDefault]("unknownException", {
|
|
1396
|
+
uuid,
|
|
1397
|
+
error: denial,
|
|
1398
|
+
...scalarActors(origin),
|
|
1399
|
+
metadata: metadataFor(origin)
|
|
1400
|
+
});
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
function causeLine(error) {
|
|
1404
|
+
const { cause } = error;
|
|
1405
|
+
if (!Error.isError(cause)) return "";
|
|
1406
|
+
const [first] = cause.message.split("\n");
|
|
1407
|
+
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
1408
|
+
}
|
|
1409
|
+
function reportRawFault(error, core, origin, uuid) {
|
|
1410
|
+
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
1411
|
+
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
1412
|
+
withThrottle(core, origin, error, () => {
|
|
1413
|
+
core.bus[PublishDefault]("unknownException", {
|
|
1414
|
+
uuid,
|
|
1415
|
+
error,
|
|
1416
|
+
...scalarActors(origin),
|
|
1417
|
+
metadata: metadataFor(origin)
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
function scalarActors(origin) {
|
|
1422
|
+
return {
|
|
1423
|
+
guild: origin.guild ? {
|
|
1424
|
+
id: origin.guild.id,
|
|
1425
|
+
name: origin.guild.name
|
|
1426
|
+
} : void 0,
|
|
1427
|
+
user: origin.user ? {
|
|
1428
|
+
id: origin.user.id,
|
|
1429
|
+
username: origin.user.username
|
|
1430
|
+
} : void 0
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function metadataFor(origin) {
|
|
1434
|
+
if (origin.event) return {
|
|
1435
|
+
eventName: origin.event.name,
|
|
1436
|
+
handler: origin.event.handler,
|
|
1437
|
+
args: origin.event.args
|
|
1438
|
+
};
|
|
1439
|
+
return origin.metadata;
|
|
1440
|
+
}
|
|
1441
|
+
function buildEventSource(event, origin) {
|
|
1442
|
+
return {
|
|
1443
|
+
kind: "event",
|
|
1444
|
+
eventName: event.name,
|
|
1445
|
+
handler: event.handler,
|
|
1446
|
+
userId: origin.user?.id ?? null,
|
|
1447
|
+
guildId: origin.guild?.id ?? null,
|
|
1448
|
+
channelId: event.channelId,
|
|
1449
|
+
raw: event.args
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
function faultKey(origin, error) {
|
|
1453
|
+
const name = error instanceof Notice ? error.name : error instanceof DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1454
|
+
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1455
|
+
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1456
|
+
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1457
|
+
return `autocomplete:${name}`;
|
|
1458
|
+
}
|
|
1459
|
+
function interactionRoute(interaction) {
|
|
1460
|
+
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1461
|
+
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1462
|
+
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return prefixOf(interaction.customId) || interaction.customId;
|
|
1463
|
+
return "interaction";
|
|
1464
|
+
}
|
|
1465
|
+
function buildInteractionSource(interaction) {
|
|
1466
|
+
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1467
|
+
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1468
|
+
return {
|
|
1469
|
+
kind: "interaction",
|
|
1470
|
+
interactionKind: interactionKind(interaction),
|
|
1471
|
+
command,
|
|
1472
|
+
customId,
|
|
1473
|
+
userId: interaction.user.id,
|
|
1474
|
+
guildId: interaction.guildId,
|
|
1475
|
+
channelId: interaction.channelId,
|
|
1476
|
+
interactionId: interaction.id,
|
|
1477
|
+
raw: interaction
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function interactionKind(interaction) {
|
|
1481
|
+
if (interaction.isChatInputCommand()) return "slash";
|
|
1482
|
+
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1483
|
+
if (interaction.isButton()) return "button";
|
|
1484
|
+
if (interaction.isAnySelectMenu()) return "select";
|
|
1485
|
+
return "modal";
|
|
1486
|
+
}
|
|
1487
|
+
|
|
2178
1488
|
//#endregion
|
|
2179
1489
|
//#region src/bot/handleEventFault.ts
|
|
2180
1490
|
const logger$1 = new Logger("EventBoundary", { channel: "errors" });
|
|
@@ -2608,21 +1918,9 @@ var InteractionDispatcher = class {
|
|
|
2608
1918
|
logger: this.logger
|
|
2609
1919
|
});
|
|
2610
1920
|
}
|
|
2611
|
-
/**
|
|
2612
|
-
* Warns for each command route with no registered `@SlashRoute` handler, which would fall through to
|
|
2613
|
-
* UnhandledRepliable at runtime. A bot may route commands outside the registry, so this does not throw.
|
|
2614
|
-
*
|
|
2615
|
-
* @internal
|
|
2616
|
-
*/
|
|
2617
1921
|
warnUnhandledRoutes(commandLeaves) {
|
|
2618
1922
|
for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${paint.sky.bold(route)} has no registered ${chalk.bold("@SlashRoute")} handler.`);
|
|
2619
1923
|
}
|
|
2620
|
-
/**
|
|
2621
|
-
* Warns for each context-menu command with no matching handler, which would fall through to UnhandledRepliable
|
|
2622
|
-
* at runtime. Checked per kind since a user and a message command can share a name.
|
|
2623
|
-
*
|
|
2624
|
-
* @internal
|
|
2625
|
-
*/
|
|
2626
1924
|
warnUnhandledContextMenuRoutes(leaves) {
|
|
2627
1925
|
for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${paint.sky.bold(name)} has no registered ${chalk.bold("@ContextMenuRoute")} handler.`);
|
|
2628
1926
|
for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${paint.sky.bold(name)} has no registered ${chalk.bold("@ContextMenuRoute")} handler.`);
|
|
@@ -2963,29 +2261,39 @@ var Bot = class {
|
|
|
2963
2261
|
interactions;
|
|
2964
2262
|
events;
|
|
2965
2263
|
emojiInjector;
|
|
2966
|
-
|
|
2967
|
-
emojis = Emojis;
|
|
2968
|
-
mentions = CommandMentions;
|
|
2264
|
+
commandRegistry;
|
|
2969
2265
|
/** @internal */
|
|
2970
2266
|
async onHmr(event) {
|
|
2971
2267
|
if (this.interactions) await this.interactions.onHmr(event);
|
|
2972
2268
|
if (this.events) await this.events.onHmr(event);
|
|
2973
|
-
if (this.
|
|
2269
|
+
if (this.commandRegistry) await this.commandRegistry.onHmr(event);
|
|
2974
2270
|
}
|
|
2975
2271
|
/** @internal */
|
|
2976
2272
|
constructor(core) {
|
|
2977
2273
|
this._client = new Client(core.config.bot.clientOptions);
|
|
2978
2274
|
if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
|
|
2979
2275
|
if (core.config.bot.events.path) this.events = new EventDispatcher(core);
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2276
|
+
const commandsDir = core.config.bot.commands.path;
|
|
2277
|
+
if (commandsDir) {
|
|
2278
|
+
const injector = new CommandInjector();
|
|
2279
|
+
const registry = new CommandRegistry({
|
|
2280
|
+
dir: commandsDir,
|
|
2281
|
+
rest: this._client.rest,
|
|
2282
|
+
applicationId: () => this.applicationId(),
|
|
2283
|
+
onDeployed: (result) => {
|
|
2284
|
+
injector.inject(result, registry.allCommands());
|
|
2285
|
+
}
|
|
2984
2286
|
});
|
|
2287
|
+
this.commandRegistry = registry;
|
|
2985
2288
|
}
|
|
2986
2289
|
this.emojiInjector = new EmojiInjector(core);
|
|
2987
2290
|
this.registerShutdownTasks(core);
|
|
2988
2291
|
}
|
|
2292
|
+
applicationId() {
|
|
2293
|
+
const { application } = this._client;
|
|
2294
|
+
if (!application) throw new SeedcordError(SeedcordErrorCode.CoreApplicationUnavailable);
|
|
2295
|
+
return application.id;
|
|
2296
|
+
}
|
|
2989
2297
|
registerShutdownTasks(core) {
|
|
2990
2298
|
core.shutdown.addTask(ShutdownPhase.Unbind, "stop-dispatch", () => {
|
|
2991
2299
|
this.stopAccepting();
|
|
@@ -3010,11 +2318,11 @@ var Bot = class {
|
|
|
3010
2318
|
if (this.events) await this.events.init();
|
|
3011
2319
|
await this.login(token);
|
|
3012
2320
|
await this.emojiInjector.init();
|
|
3013
|
-
if (this.
|
|
3014
|
-
await this.
|
|
3015
|
-
await this.
|
|
3016
|
-
this.interactions?.warnUnhandledRoutes(this.
|
|
3017
|
-
this.interactions?.warnUnhandledContextMenuRoutes(this.
|
|
2321
|
+
if (this.commandRegistry) {
|
|
2322
|
+
await this.commandRegistry.init();
|
|
2323
|
+
await this.commandRegistry.setCommands();
|
|
2324
|
+
this.interactions?.warnUnhandledRoutes(this.commandRegistry.routeLeaves());
|
|
2325
|
+
this.interactions?.warnUnhandledContextMenuRoutes(this.commandRegistry.contextMenuLeaves());
|
|
3018
2326
|
}
|
|
3019
2327
|
}
|
|
3020
2328
|
/** @internal */
|
|
@@ -3043,7 +2351,7 @@ __decorate([Envapt("DISCORD_BOT_TOKEN", { converter: (raw) => validateDiscordTok
|
|
|
3043
2351
|
//#endregion
|
|
3044
2352
|
//#region src/version.ts
|
|
3045
2353
|
/** Package version */
|
|
3046
|
-
const version = "0.1.0-next.
|
|
2354
|
+
const version = "0.1.0-next.6";
|
|
3047
2355
|
|
|
3048
2356
|
//#endregion
|
|
3049
2357
|
//#region src/Seedcord.ts
|
|
@@ -3066,6 +2374,8 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3066
2374
|
subscribers;
|
|
3067
2375
|
/** @see {@link Bot} */
|
|
3068
2376
|
bot;
|
|
2377
|
+
/** The discord.js client's REST client. Its token is set during the Login phase. */
|
|
2378
|
+
rest;
|
|
3069
2379
|
/** @see {@link IRateLimiter} */
|
|
3070
2380
|
rateLimiter;
|
|
3071
2381
|
/**
|
|
@@ -3082,6 +2392,7 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3082
2392
|
this.bus = new Bus(this);
|
|
3083
2393
|
this.subscribers = new SubscriberLoader(this.bus, config.subscribers.path);
|
|
3084
2394
|
this.bot = new Bot(this);
|
|
2395
|
+
this.rest = this.bot.client.rest;
|
|
3085
2396
|
this.rateLimiter = config.store ?? new MemoryRateLimiter();
|
|
3086
2397
|
this.healthCheck = HealthCheck.fromOption(this.shutdown, config.healthCheck);
|
|
3087
2398
|
this.registerStartupTasks();
|
|
@@ -3090,13 +2401,6 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3090
2401
|
get username() {
|
|
3091
2402
|
return this.bot.client.user?.username;
|
|
3092
2403
|
}
|
|
3093
|
-
pluginCapabilities() {
|
|
3094
|
-
return {
|
|
3095
|
-
client: this.bot.client,
|
|
3096
|
-
token: this.bot.botToken,
|
|
3097
|
-
rest: this.bot.client.rest
|
|
3098
|
-
};
|
|
3099
|
-
}
|
|
3100
2404
|
static reset() {
|
|
3101
2405
|
super.reset();
|
|
3102
2406
|
LoggerChannelRegistry.instance.reset();
|
|
@@ -3146,5 +2450,5 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3146
2450
|
};
|
|
3147
2451
|
|
|
3148
2452
|
//#endregion
|
|
3149
|
-
export { ArraySource, AutocompleteHandler, ButtonHandler,
|
|
2453
|
+
export { ArraySource, AutocompleteHandler, ButtonHandler, ContextMenuHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, IgnoreBots, InteractionHandler, InteractionMiddleware, Middleware, MiddlewareType, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RepliableHandler, Seedcord, SelectMenuHandler, SlashHandler, Subscriber, WebhookLog, checkBotPermissions, checkPermissions, getConfirmation, hasPermsToAssign, version };
|
|
3150
2454
|
//# sourceMappingURL=index.mjs.map
|