@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.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,138 +426,123 @@ 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
|
|
740
520
|
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* @
|
|
521
|
+
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
522
|
+
* Unlike handlers, middleware should not send responses directly.
|
|
523
|
+
*
|
|
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
|
|
534
|
+
function isReplyFile(file) {
|
|
535
|
+
return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
|
|
536
|
+
}
|
|
537
|
+
function toDjsFile(file) {
|
|
538
|
+
if (!isReplyFile(file)) return file;
|
|
539
|
+
return {
|
|
540
|
+
attachment: Buffer.from(file.data),
|
|
541
|
+
name: file.name,
|
|
542
|
+
...file.description !== void 0 && { description: file.description },
|
|
543
|
+
...file.title !== void 0 && { title: file.title }
|
|
544
|
+
};
|
|
545
|
+
}
|
|
756
546
|
function seedState(interaction) {
|
|
757
547
|
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
758
548
|
if (interaction.replied) return "replied";
|
|
@@ -835,7 +625,7 @@ var ReplySender = class extends BaseReplySender {
|
|
|
835
625
|
components: reply.components,
|
|
836
626
|
flags,
|
|
837
627
|
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
838
|
-
...reply.files && { files:
|
|
628
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
839
629
|
};
|
|
840
630
|
}
|
|
841
631
|
editOptions(response) {
|
|
@@ -844,7 +634,7 @@ var ReplySender = class extends BaseReplySender {
|
|
|
844
634
|
components: reply.components,
|
|
845
635
|
flags: MessageFlags.IsComponentsV2,
|
|
846
636
|
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
847
|
-
...reply.files && { files:
|
|
637
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
848
638
|
};
|
|
849
639
|
}
|
|
850
640
|
updateOptions(response) {
|
|
@@ -853,7 +643,7 @@ var ReplySender = class extends BaseReplySender {
|
|
|
853
643
|
components: reply.components,
|
|
854
644
|
flags: MessageFlags.IsComponentsV2,
|
|
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
|
createdMessage(result) {
|
|
@@ -861,266 +651,6 @@ var ReplySender = class extends BaseReplySender {
|
|
|
861
651
|
}
|
|
862
652
|
};
|
|
863
653
|
|
|
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
654
|
//#endregion
|
|
1125
655
|
//#region src/handlers/RepliableHandler.ts
|
|
1126
656
|
/**
|
|
@@ -1166,6 +696,21 @@ var InteractionHandler = class extends RepliableHandler {
|
|
|
1166
696
|
}
|
|
1167
697
|
};
|
|
1168
698
|
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
701
|
+
/**
|
|
702
|
+
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
703
|
+
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
704
|
+
* interaction and a generated registry key always match.
|
|
705
|
+
*
|
|
706
|
+
* @internal
|
|
707
|
+
*/
|
|
708
|
+
function slashRouteOf(interaction) {
|
|
709
|
+
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
710
|
+
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
711
|
+
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
712
|
+
}
|
|
713
|
+
|
|
1169
714
|
//#endregion
|
|
1170
715
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
1171
716
|
/**
|
|
@@ -1485,7 +1030,22 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1485
1030
|
* }
|
|
1486
1031
|
* ```
|
|
1487
1032
|
*/
|
|
1488
|
-
var ModalHandler = class extends ComponentHandler {
|
|
1033
|
+
var ModalHandler = class extends ComponentHandler {
|
|
1034
|
+
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
1035
|
+
update(response) {
|
|
1036
|
+
this.ensureSourceMessage("update");
|
|
1037
|
+
return super.update(response);
|
|
1038
|
+
}
|
|
1039
|
+
/** Acknowledge the submit without changing the source message. Throws when a command opened the modal. */
|
|
1040
|
+
deferUpdate() {
|
|
1041
|
+
this.ensureSourceMessage("deferUpdate");
|
|
1042
|
+
return super.deferUpdate();
|
|
1043
|
+
}
|
|
1044
|
+
ensureSourceMessage(method) {
|
|
1045
|
+
if (this.event.isFromMessage()) return;
|
|
1046
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1489
1049
|
|
|
1490
1050
|
//#endregion
|
|
1491
1051
|
//#region src/handlers/interaction/components/SelectMenuHandler.ts
|
|
@@ -1599,138 +1159,14 @@ var EventMiddleware = class extends BaseHandler$1 {
|
|
|
1599
1159
|
}
|
|
1600
1160
|
/**
|
|
1601
1161
|
* 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);
|
|
1162
|
+
* is `never`, to do work that does not depend on the payload shape. A single-event middleware reads
|
|
1163
|
+
* `this.event` directly and does not need this.
|
|
1164
|
+
*/
|
|
1165
|
+
get eventName() {
|
|
1166
|
+
if (this.firedEvent === void 0) throw new SeedcordError(SeedcordErrorCode.EventMiddlewareNameUnavailable);
|
|
1167
|
+
return this.firedEvent;
|
|
1706
1168
|
}
|
|
1707
1169
|
};
|
|
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
1170
|
|
|
1735
1171
|
//#endregion
|
|
1736
1172
|
//#region src/pagination/Paginator.ts
|
|
@@ -1739,7 +1175,7 @@ function contextOf(interaction, core) {
|
|
|
1739
1175
|
interaction,
|
|
1740
1176
|
user: interaction.user,
|
|
1741
1177
|
guild: interaction.guild,
|
|
1742
|
-
|
|
1178
|
+
core
|
|
1743
1179
|
};
|
|
1744
1180
|
}
|
|
1745
1181
|
/**
|
|
@@ -1761,15 +1197,11 @@ function contextOf(interaction, core) {
|
|
|
1761
1197
|
* export class BansNav extends Bans.Handler {}
|
|
1762
1198
|
* ```
|
|
1763
1199
|
*/
|
|
1764
|
-
var Paginator = class {
|
|
1765
|
-
/** The page cursor, pass it to your `@ButtonRoute`. */
|
|
1766
|
-
cursor;
|
|
1200
|
+
var Paginator = class extends PaginatorBase {
|
|
1767
1201
|
/** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
|
|
1768
1202
|
Handler;
|
|
1769
|
-
config;
|
|
1770
1203
|
constructor(config) {
|
|
1771
|
-
|
|
1772
|
-
this.config = config;
|
|
1204
|
+
super(config);
|
|
1773
1205
|
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
1774
1206
|
this.Handler = class Nav extends ButtonHandler {
|
|
1775
1207
|
async execute() {
|
|
@@ -1780,7 +1212,8 @@ var Paginator = class {
|
|
|
1780
1212
|
};
|
|
1781
1213
|
}
|
|
1782
1214
|
/**
|
|
1783
|
-
* Render page 0 and send it through the handler's sender,
|
|
1215
|
+
* Render page 0 and send it through the handler's sender, which picks reply, edit, or followUp from its
|
|
1216
|
+
* ack state.
|
|
1784
1217
|
*
|
|
1785
1218
|
* @param handler - The handler starting the paginator, normally `this`.
|
|
1786
1219
|
*/
|
|
@@ -1789,34 +1222,18 @@ var Paginator = class {
|
|
|
1789
1222
|
const response = await this.page(contextOf(interaction, handler.core), 0);
|
|
1790
1223
|
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1791
1224
|
}
|
|
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
1225
|
};
|
|
1797
1226
|
|
|
1798
1227
|
//#endregion
|
|
1799
1228
|
//#region src/pagination/sources.ts
|
|
1800
|
-
const DEFAULT_PER_PAGE = 10;
|
|
1801
1229
|
/**
|
|
1802
1230
|
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
1803
|
-
* page
|
|
1804
|
-
*
|
|
1231
|
+
* page, so the real total is known (a real last button, "Page X of Y"). Cache inside your loader if the
|
|
1232
|
+
* load is expensive.
|
|
1805
1233
|
*
|
|
1806
1234
|
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1807
1235
|
*/
|
|
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
|
-
};
|
|
1236
|
+
var ArraySource = class extends ArraySource$1 {};
|
|
1820
1237
|
/**
|
|
1821
1238
|
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1822
1239
|
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
@@ -1825,26 +1242,7 @@ var ArraySource = class {
|
|
|
1825
1242
|
*
|
|
1826
1243
|
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1827
1244
|
*/
|
|
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
|
-
};
|
|
1245
|
+
var CursorSource = class extends CursorSource$1 {};
|
|
1848
1246
|
|
|
1849
1247
|
//#endregion
|
|
1850
1248
|
//#region src/subscribers/index.ts
|
|
@@ -1868,236 +1266,12 @@ var WebhookLog = class extends WebhookLog$1 {};
|
|
|
1868
1266
|
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
1869
1267
|
*
|
|
1870
1268
|
* A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
|
|
1871
|
-
* `this.core`
|
|
1269
|
+
* `this.core` is typed to the shared members only.
|
|
1872
1270
|
*
|
|
1873
|
-
* @typeParam Opts - The transport
|
|
1271
|
+
* @typeParam Opts - The transport and runtime the plugin declares.
|
|
1874
1272
|
*/
|
|
1875
1273
|
var Plugin = class extends Plugin$1 {};
|
|
1876
1274
|
|
|
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
1275
|
//#endregion
|
|
2102
1276
|
//#region src/miscellaneous/deriveEventActor.ts
|
|
2103
1277
|
function deriveEventActor(args) {
|
|
@@ -2175,6 +1349,154 @@ function eventGateContext(eventName, args, core) {
|
|
|
2175
1349
|
};
|
|
2176
1350
|
}
|
|
2177
1351
|
|
|
1352
|
+
//#endregion
|
|
1353
|
+
//#region src/miscellaneous/extractErrorResponse.ts
|
|
1354
|
+
const logger$2 = new Logger("ErrorsHandling", { channel: "errors" });
|
|
1355
|
+
/**
|
|
1356
|
+
* Processes an error into a user-facing reply and routes its report.
|
|
1357
|
+
*
|
|
1358
|
+
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
1359
|
+
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
1360
|
+
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
1361
|
+
* threaded into both the reply and the bus payload.
|
|
1362
|
+
*/
|
|
1363
|
+
function extractErrorResponse(error, core, origin) {
|
|
1364
|
+
const uuid = crypto.randomUUID();
|
|
1365
|
+
const developerUsername = core.config.notifications?.developerUsername;
|
|
1366
|
+
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
1367
|
+
uuid,
|
|
1368
|
+
developerUsername
|
|
1369
|
+
};
|
|
1370
|
+
if (error instanceof Notice) {
|
|
1371
|
+
if (error.report) reportFault(error, core, origin, uuid);
|
|
1372
|
+
return {
|
|
1373
|
+
uuid,
|
|
1374
|
+
response: error.render(ctx)
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
reportRawFault(error, core, origin, uuid);
|
|
1378
|
+
const override = core.config.errors?.defaultError;
|
|
1379
|
+
return {
|
|
1380
|
+
uuid,
|
|
1381
|
+
response: override ? new override(uuid).render(ctx) : new Fault().render(ctx)
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function withThrottle(core, origin, error, publish) {
|
|
1385
|
+
const throttle = FaultThrottle.for(core);
|
|
1386
|
+
const key = faultKey(origin, error);
|
|
1387
|
+
if (!throttle.shouldReport(key)) {
|
|
1388
|
+
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
publish();
|
|
1392
|
+
throttle.markReported(key);
|
|
1393
|
+
}
|
|
1394
|
+
function reportFault(denial, core, origin, uuid) {
|
|
1395
|
+
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
1396
|
+
withThrottle(core, origin, denial, () => {
|
|
1397
|
+
if (origin.interaction) core.bus[PublishDefault]("handledException", {
|
|
1398
|
+
denial,
|
|
1399
|
+
uuid,
|
|
1400
|
+
source: buildInteractionSource(origin.interaction)
|
|
1401
|
+
});
|
|
1402
|
+
else if (origin.event) core.bus[PublishDefault]("handledException", {
|
|
1403
|
+
denial,
|
|
1404
|
+
uuid,
|
|
1405
|
+
source: buildEventSource(origin.event, origin)
|
|
1406
|
+
});
|
|
1407
|
+
else core.bus[PublishDefault]("unknownException", {
|
|
1408
|
+
uuid,
|
|
1409
|
+
error: denial,
|
|
1410
|
+
...scalarActors(origin),
|
|
1411
|
+
metadata: metadataFor(origin)
|
|
1412
|
+
});
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
function causeLine(error) {
|
|
1416
|
+
const { cause } = error;
|
|
1417
|
+
if (!Error.isError(cause)) return "";
|
|
1418
|
+
const [first] = cause.message.split("\n");
|
|
1419
|
+
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
1420
|
+
}
|
|
1421
|
+
function reportRawFault(error, core, origin, uuid) {
|
|
1422
|
+
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
1423
|
+
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
1424
|
+
withThrottle(core, origin, error, () => {
|
|
1425
|
+
core.bus[PublishDefault]("unknownException", {
|
|
1426
|
+
uuid,
|
|
1427
|
+
error,
|
|
1428
|
+
...scalarActors(origin),
|
|
1429
|
+
metadata: metadataFor(origin)
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
function scalarActors(origin) {
|
|
1434
|
+
return {
|
|
1435
|
+
guild: origin.guild ? {
|
|
1436
|
+
id: origin.guild.id,
|
|
1437
|
+
name: origin.guild.name
|
|
1438
|
+
} : void 0,
|
|
1439
|
+
user: origin.user ? {
|
|
1440
|
+
id: origin.user.id,
|
|
1441
|
+
username: origin.user.username
|
|
1442
|
+
} : void 0
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
function metadataFor(origin) {
|
|
1446
|
+
if (origin.event) return {
|
|
1447
|
+
eventName: origin.event.name,
|
|
1448
|
+
handler: origin.event.handler,
|
|
1449
|
+
args: origin.event.args
|
|
1450
|
+
};
|
|
1451
|
+
return origin.metadata;
|
|
1452
|
+
}
|
|
1453
|
+
function buildEventSource(event, origin) {
|
|
1454
|
+
return {
|
|
1455
|
+
kind: "event",
|
|
1456
|
+
eventName: event.name,
|
|
1457
|
+
handler: event.handler,
|
|
1458
|
+
userId: origin.user?.id ?? null,
|
|
1459
|
+
guildId: origin.guild?.id ?? null,
|
|
1460
|
+
channelId: event.channelId,
|
|
1461
|
+
raw: event.args
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
function faultKey(origin, error) {
|
|
1465
|
+
const name = error instanceof Notice ? error.name : error instanceof DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1466
|
+
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1467
|
+
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1468
|
+
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1469
|
+
return `autocomplete:${name}`;
|
|
1470
|
+
}
|
|
1471
|
+
function interactionRoute(interaction) {
|
|
1472
|
+
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1473
|
+
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1474
|
+
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return prefixOf(interaction.customId) || interaction.customId;
|
|
1475
|
+
return "interaction";
|
|
1476
|
+
}
|
|
1477
|
+
function buildInteractionSource(interaction) {
|
|
1478
|
+
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1479
|
+
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1480
|
+
return {
|
|
1481
|
+
kind: "interaction",
|
|
1482
|
+
interactionKind: interactionKind(interaction),
|
|
1483
|
+
command,
|
|
1484
|
+
customId,
|
|
1485
|
+
userId: interaction.user.id,
|
|
1486
|
+
guildId: interaction.guildId,
|
|
1487
|
+
channelId: interaction.channelId,
|
|
1488
|
+
interactionId: interaction.id,
|
|
1489
|
+
raw: interaction
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function interactionKind(interaction) {
|
|
1493
|
+
if (interaction.isChatInputCommand()) return "slash";
|
|
1494
|
+
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1495
|
+
if (interaction.isButton()) return "button";
|
|
1496
|
+
if (interaction.isAnySelectMenu()) return "select";
|
|
1497
|
+
return "modal";
|
|
1498
|
+
}
|
|
1499
|
+
|
|
2178
1500
|
//#endregion
|
|
2179
1501
|
//#region src/bot/handleEventFault.ts
|
|
2180
1502
|
const logger$1 = new Logger("EventBoundary", { channel: "errors" });
|
|
@@ -2608,21 +1930,9 @@ var InteractionDispatcher = class {
|
|
|
2608
1930
|
logger: this.logger
|
|
2609
1931
|
});
|
|
2610
1932
|
}
|
|
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
1933
|
warnUnhandledRoutes(commandLeaves) {
|
|
2618
1934
|
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
1935
|
}
|
|
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
1936
|
warnUnhandledContextMenuRoutes(leaves) {
|
|
2627
1937
|
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
1938
|
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 +2273,39 @@ var Bot = class {
|
|
|
2963
2273
|
interactions;
|
|
2964
2274
|
events;
|
|
2965
2275
|
emojiInjector;
|
|
2966
|
-
|
|
2967
|
-
emojis = Emojis;
|
|
2968
|
-
mentions = CommandMentions;
|
|
2276
|
+
commandRegistry;
|
|
2969
2277
|
/** @internal */
|
|
2970
2278
|
async onHmr(event) {
|
|
2971
2279
|
if (this.interactions) await this.interactions.onHmr(event);
|
|
2972
2280
|
if (this.events) await this.events.onHmr(event);
|
|
2973
|
-
if (this.
|
|
2281
|
+
if (this.commandRegistry) await this.commandRegistry.onHmr(event);
|
|
2974
2282
|
}
|
|
2975
2283
|
/** @internal */
|
|
2976
2284
|
constructor(core) {
|
|
2977
2285
|
this._client = new Client(core.config.bot.clientOptions);
|
|
2978
2286
|
if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
|
|
2979
2287
|
if (core.config.bot.events.path) this.events = new EventDispatcher(core);
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2288
|
+
const commandsDir = core.config.bot.commands.path;
|
|
2289
|
+
if (commandsDir) {
|
|
2290
|
+
const injector = new CommandInjector();
|
|
2291
|
+
const registry = new CommandRegistry({
|
|
2292
|
+
dir: commandsDir,
|
|
2293
|
+
rest: this._client.rest,
|
|
2294
|
+
applicationId: () => this.applicationId(),
|
|
2295
|
+
onDeployed: (result) => {
|
|
2296
|
+
injector.inject(result, registry.allCommands());
|
|
2297
|
+
}
|
|
2984
2298
|
});
|
|
2299
|
+
this.commandRegistry = registry;
|
|
2985
2300
|
}
|
|
2986
2301
|
this.emojiInjector = new EmojiInjector(core);
|
|
2987
2302
|
this.registerShutdownTasks(core);
|
|
2988
2303
|
}
|
|
2304
|
+
applicationId() {
|
|
2305
|
+
const { application } = this._client;
|
|
2306
|
+
if (!application) throw new SeedcordError(SeedcordErrorCode.CoreApplicationUnavailable);
|
|
2307
|
+
return application.id;
|
|
2308
|
+
}
|
|
2989
2309
|
registerShutdownTasks(core) {
|
|
2990
2310
|
core.shutdown.addTask(ShutdownPhase.Unbind, "stop-dispatch", () => {
|
|
2991
2311
|
this.stopAccepting();
|
|
@@ -3010,11 +2330,11 @@ var Bot = class {
|
|
|
3010
2330
|
if (this.events) await this.events.init();
|
|
3011
2331
|
await this.login(token);
|
|
3012
2332
|
await this.emojiInjector.init();
|
|
3013
|
-
if (this.
|
|
3014
|
-
await this.
|
|
3015
|
-
await this.
|
|
3016
|
-
this.interactions?.warnUnhandledRoutes(this.
|
|
3017
|
-
this.interactions?.warnUnhandledContextMenuRoutes(this.
|
|
2333
|
+
if (this.commandRegistry) {
|
|
2334
|
+
await this.commandRegistry.init();
|
|
2335
|
+
await this.commandRegistry.setCommands();
|
|
2336
|
+
this.interactions?.warnUnhandledRoutes(this.commandRegistry.routeLeaves());
|
|
2337
|
+
this.interactions?.warnUnhandledContextMenuRoutes(this.commandRegistry.contextMenuLeaves());
|
|
3018
2338
|
}
|
|
3019
2339
|
}
|
|
3020
2340
|
/** @internal */
|
|
@@ -3043,7 +2363,7 @@ __decorate([Envapt("DISCORD_BOT_TOKEN", { converter: (raw) => validateDiscordTok
|
|
|
3043
2363
|
//#endregion
|
|
3044
2364
|
//#region src/version.ts
|
|
3045
2365
|
/** Package version */
|
|
3046
|
-
const version = "0.1.0
|
|
2366
|
+
const version = "0.1.0";
|
|
3047
2367
|
|
|
3048
2368
|
//#endregion
|
|
3049
2369
|
//#region src/Seedcord.ts
|
|
@@ -3066,6 +2386,8 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3066
2386
|
subscribers;
|
|
3067
2387
|
/** @see {@link Bot} */
|
|
3068
2388
|
bot;
|
|
2389
|
+
/** The discord.js client's REST client. Its token is set during the Login phase. */
|
|
2390
|
+
rest;
|
|
3069
2391
|
/** @see {@link IRateLimiter} */
|
|
3070
2392
|
rateLimiter;
|
|
3071
2393
|
/**
|
|
@@ -3082,6 +2404,7 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3082
2404
|
this.bus = new Bus(this);
|
|
3083
2405
|
this.subscribers = new SubscriberLoader(this.bus, config.subscribers.path);
|
|
3084
2406
|
this.bot = new Bot(this);
|
|
2407
|
+
this.rest = this.bot.client.rest;
|
|
3085
2408
|
this.rateLimiter = config.store ?? new MemoryRateLimiter();
|
|
3086
2409
|
this.healthCheck = HealthCheck.fromOption(this.shutdown, config.healthCheck);
|
|
3087
2410
|
this.registerStartupTasks();
|
|
@@ -3090,13 +2413,6 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3090
2413
|
get username() {
|
|
3091
2414
|
return this.bot.client.user?.username;
|
|
3092
2415
|
}
|
|
3093
|
-
pluginCapabilities() {
|
|
3094
|
-
return {
|
|
3095
|
-
client: this.bot.client,
|
|
3096
|
-
token: this.bot.botToken,
|
|
3097
|
-
rest: this.bot.client.rest
|
|
3098
|
-
};
|
|
3099
|
-
}
|
|
3100
2416
|
static reset() {
|
|
3101
2417
|
super.reset();
|
|
3102
2418
|
LoggerChannelRegistry.instance.reset();
|
|
@@ -3146,5 +2462,5 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3146
2462
|
};
|
|
3147
2463
|
|
|
3148
2464
|
//#endregion
|
|
3149
|
-
export { ArraySource, AutocompleteHandler, ButtonHandler,
|
|
2465
|
+
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
2466
|
//# sourceMappingURL=index.mjs.map
|