@seedcord/gateway 0.1.0-next.4 → 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 +535 -1666
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +159 -749
- package/dist/index.mjs +538 -1660
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -8
package/dist/index.cjs
CHANGED
|
@@ -33,25 +33,22 @@ let _seedcord_errors = require("@seedcord/errors");
|
|
|
33
33
|
let _seedcord_errors_internal = require("@seedcord/errors/internal");
|
|
34
34
|
let _seedcord_core = require("@seedcord/core");
|
|
35
35
|
let _seedcord_logger = require("@seedcord/logger");
|
|
36
|
+
let discord_js = require("discord.js");
|
|
36
37
|
let _discordjs_builders = require("@discordjs/builders");
|
|
37
38
|
let _seedcord_utils_internal = require("@seedcord/utils/internal");
|
|
38
|
-
let
|
|
39
|
-
let node_crypto = require("node:crypto");
|
|
40
|
-
node_crypto = __toESM(node_crypto, 1);
|
|
41
|
-
let chalk = require("chalk");
|
|
42
|
-
chalk = __toESM(chalk, 1);
|
|
43
|
-
let envapt = require("envapt");
|
|
44
|
-
let discord_api_types_v10 = require("discord-api-types/v10");
|
|
45
|
-
let _seedcord_utils = require("@seedcord/utils");
|
|
46
|
-
let _discordjs_rest = require("@discordjs/rest");
|
|
39
|
+
let _seedcord_core_plugin = require("@seedcord/core/plugin");
|
|
47
40
|
let _seedcord_core_node_internal = require("@seedcord/core/node/internal");
|
|
48
41
|
let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
|
|
49
42
|
let _seedcord_types_internal = require("@seedcord/types/internal");
|
|
50
|
-
let
|
|
43
|
+
let envapt = require("envapt");
|
|
44
|
+
let chalk = require("chalk");
|
|
45
|
+
chalk = __toESM(chalk, 1);
|
|
51
46
|
let envapt_legacy = require("envapt/legacy");
|
|
52
|
-
let node_path = require("node:path");
|
|
53
47
|
let _seedcord_core_hmr = require("@seedcord/core/hmr");
|
|
48
|
+
let _seedcord_utils = require("@seedcord/utils");
|
|
54
49
|
let _seedcord_utils_node = require("@seedcord/utils/node");
|
|
50
|
+
let node_crypto = require("node:crypto");
|
|
51
|
+
node_crypto = __toESM(node_crypto, 1);
|
|
55
52
|
|
|
56
53
|
//#region src/logger-bootstrap.ts
|
|
57
54
|
(0, _seedcord_logger_node.installNodeDefaults)();
|
|
@@ -165,7 +162,7 @@ let MiddlewareType = /* @__PURE__ */ function(MiddlewareType) {
|
|
|
165
162
|
* Interaction middleware cannot specify event filters.
|
|
166
163
|
*
|
|
167
164
|
* @param type - Middleware kind from {@link MiddlewareType}
|
|
168
|
-
* @param priority - Ordering value where lower runs earlier. {@default `0`}
|
|
165
|
+
* @param priority - Ordering value where lower runs earlier. {@default `0` }
|
|
169
166
|
* @param options - Additional registration options
|
|
170
167
|
*
|
|
171
168
|
* @decorator
|
|
@@ -244,59 +241,6 @@ var CannotAssignBotRole = class extends _seedcord_core.Notice {
|
|
|
244
241
|
return { components: [new _seedcord_core_internal.NoticeCard("I cannot assign a managed role.").component] };
|
|
245
242
|
}
|
|
246
243
|
};
|
|
247
|
-
var HasDangerousPermissions = class extends _seedcord_core.Notice {
|
|
248
|
-
subject;
|
|
249
|
-
dangerousPerms;
|
|
250
|
-
customLead;
|
|
251
|
-
constructor(message, subject, dangerousPerms) {
|
|
252
|
-
super(message ?? "A dangerous permission is enabled.");
|
|
253
|
-
this.subject = subject;
|
|
254
|
-
this.dangerousPerms = dangerousPerms;
|
|
255
|
-
this.customLead = message;
|
|
256
|
-
}
|
|
257
|
-
render() {
|
|
258
|
-
const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
|
|
259
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`${this.customLead ?? `${this.subject} has the following permission entries that must not be enabled:`}\n\n${bullets}`).component] };
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
var UserNotFound = class extends _seedcord_core.Notice {
|
|
263
|
-
userArg;
|
|
264
|
-
constructor(userArg) {
|
|
265
|
-
super(`User not found: ${userArg}`);
|
|
266
|
-
this.userArg = userArg;
|
|
267
|
-
}
|
|
268
|
-
render() {
|
|
269
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`User probably doesn't exist or was deleted.\n**User Argument:** \`${this.userArg}\`\nPlease check the user ID and try again. Only pass valid user IDs as the argument.`, "User Not Found").component] };
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
var UserNotInGuild = class extends _seedcord_core.Notice {
|
|
273
|
-
constructor(message = "User is not in the guild.") {
|
|
274
|
-
super(message);
|
|
275
|
-
}
|
|
276
|
-
render() {
|
|
277
|
-
return { components: [new _seedcord_core_internal.NoticeCard(this.message).component] };
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
var RoleDoesNotExist = class extends _seedcord_core.Notice {
|
|
281
|
-
roleId;
|
|
282
|
-
constructor(message, roleId) {
|
|
283
|
-
super(message);
|
|
284
|
-
this.roleId = roleId;
|
|
285
|
-
}
|
|
286
|
-
render() {
|
|
287
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`The role with ID \`${this.roleId}\` does not exist.`).component] };
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
var CouldNotFindChannel = class extends _seedcord_core.Notice {
|
|
291
|
-
channelId;
|
|
292
|
-
constructor(message, channelId) {
|
|
293
|
-
super(message);
|
|
294
|
-
this.channelId = channelId;
|
|
295
|
-
}
|
|
296
|
-
render() {
|
|
297
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`Could not find channel with ID \`${this.channelId}\`. It could also be that the channel is not a text channel.`).component] };
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
244
|
|
|
301
245
|
//#endregion
|
|
302
246
|
//#region src/bot/gates/catalog/Nsfw.ts
|
|
@@ -334,26 +278,21 @@ function Nsfw(options) {
|
|
|
334
278
|
|
|
335
279
|
//#endregion
|
|
336
280
|
//#region src/bot/injectors/EmojiInjector.ts
|
|
337
|
-
const emojiStorage =
|
|
338
|
-
function isEmojiTuple(v) {
|
|
339
|
-
return Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "string";
|
|
340
|
-
}
|
|
281
|
+
const emojiStorage = (0, _seedcord_core_internal.accessorStore)();
|
|
341
282
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* @see {@link EmojiMap}
|
|
283
|
+
* The bot's resolved emojis, keyed by {@link EmojiMap}. Filled by {@link EmojiInjector} during startup.
|
|
284
|
+
* A read before that throws.
|
|
345
285
|
*/
|
|
346
|
-
const Emojis = emojiStorage;
|
|
286
|
+
const Emojis = (0, _seedcord_core_internal.guardedAccessor)("Emojis", emojiStorage);
|
|
347
287
|
/**
|
|
348
|
-
* Loads and injects emojis based on bot configuration.
|
|
349
|
-
*
|
|
350
|
-
* For emojis injected from specific guilds, ensure that the Guilds intent is provided in client options.
|
|
288
|
+
* Loads and injects emojis based on bot configuration. A guild emoji requires the Guilds intent in the client
|
|
289
|
+
* options.
|
|
351
290
|
*
|
|
352
291
|
* @internal
|
|
353
292
|
*/
|
|
354
293
|
var EmojiInjector = class {
|
|
355
294
|
core;
|
|
356
|
-
logger = new _seedcord_logger.Logger("Emojis");
|
|
295
|
+
logger = new _seedcord_logger.Logger("Emojis", { channel: "bot" });
|
|
357
296
|
constructor(core) {
|
|
358
297
|
this.core = core;
|
|
359
298
|
}
|
|
@@ -367,7 +306,7 @@ var EmojiInjector = class {
|
|
|
367
306
|
await this.core.bot.client.application?.emojis.fetch();
|
|
368
307
|
const failures = [];
|
|
369
308
|
const fetchedGuilds = /* @__PURE__ */ new Set();
|
|
370
|
-
for (const [key, value] of Object.entries(configEmojis)) if (isEmojiTuple(value)) await this.resolveTuple(key, value, failures, fetchedGuilds);
|
|
309
|
+
for (const [key, value] of Object.entries(configEmojis)) if ((0, _seedcord_core_internal.isEmojiTuple)(value)) await this.resolveTuple(key, value, failures, fetchedGuilds);
|
|
371
310
|
else if (typeof value === "string") this.resolveString(key, value, failures);
|
|
372
311
|
else failures.push(` - "${key}" has an invalid value (expected a name or [name, guildId])`);
|
|
373
312
|
if (failures.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigEmojiUnresolved, [failures.length, failures.join("\n")]);
|
|
@@ -400,126 +339,10 @@ var EmojiInjector = class {
|
|
|
400
339
|
emojiStorage[key] = appEmoji;
|
|
401
340
|
}
|
|
402
341
|
clearEmojis() {
|
|
403
|
-
|
|
342
|
+
(0, _seedcord_core_internal.clearStore)(emojiStorage);
|
|
404
343
|
}
|
|
405
344
|
};
|
|
406
345
|
|
|
407
|
-
//#endregion
|
|
408
|
-
//#region src/bot/injectors/CommandMentionInjector.ts
|
|
409
|
-
const mentionStorage = {};
|
|
410
|
-
/**
|
|
411
|
-
* Ready-to-send command mention strings, keyed by {@link SlashOptionRegistry}.
|
|
412
|
-
*
|
|
413
|
-
* A value is a clickable `</name:id>` when the command's id resolved from the deploy, otherwise the plain
|
|
414
|
-
* `/name` text. Populated by {@link CommandMentionInjector} after each deploy.
|
|
415
|
-
*/
|
|
416
|
-
const CommandMentions = mentionStorage;
|
|
417
|
-
const MULTI_GUILD = Symbol("seedcord:commandmentions:multi-guild");
|
|
418
|
-
/**
|
|
419
|
-
* Turns each deployed slash route into a clickable command mention exposed through {@link CommandMentions}.
|
|
420
|
-
*
|
|
421
|
-
* Consumes the ids {@link DeployResult} carries, so it runs after a deploy rather than resolving from a name
|
|
422
|
-
* cache (discord.js offers none for command ids). A global command is clickable everywhere by its app-wide id.
|
|
423
|
-
* A guild command deployed to one guild is clickable by that guild's id. A command deployed to two or more
|
|
424
|
-
* guilds mints a different id per guild, so one accessor key cannot resolve it, and it falls back to plain
|
|
425
|
-
* `/name` text with one warn. On a non-owning shard a guild command also falls back to plain text.
|
|
426
|
-
*
|
|
427
|
-
* @internal
|
|
428
|
-
*/
|
|
429
|
-
var CommandMentionInjector = class {
|
|
430
|
-
core;
|
|
431
|
-
logger = new _seedcord_logger.Logger("CommandMentions");
|
|
432
|
-
constructor(core) {
|
|
433
|
-
this.core = core;
|
|
434
|
-
}
|
|
435
|
-
inject(deploy) {
|
|
436
|
-
this.clear();
|
|
437
|
-
const globalIds = this.indexByName(deploy.global);
|
|
438
|
-
const guildIds = this.indexGuilds(deploy.guilds);
|
|
439
|
-
const warned = /* @__PURE__ */ new Set();
|
|
440
|
-
let clickable = 0;
|
|
441
|
-
for (const command of this.allSlashBuilders()) {
|
|
442
|
-
const id = this.resolveId(command.name, globalIds, guildIds, warned);
|
|
443
|
-
for (const leaf of (0, _seedcord_utils_internal.routeLeavesOf)(command.toJSON())) {
|
|
444
|
-
mentionStorage[leaf.route] = id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route);
|
|
445
|
-
if (id) clickable++;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
this.logger.utils.summary("Linked mentions", {
|
|
449
|
-
clickable,
|
|
450
|
-
plain: Object.keys(mentionStorage).length - clickable
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
allSlashBuilders() {
|
|
454
|
-
const registry = this.core.bot.commands;
|
|
455
|
-
if (!registry) return [];
|
|
456
|
-
const slash = [...registry.globalCommands, ...registry.guildCommands.values()].flat().filter((command) => command instanceof _discordjs_builders.SlashCommandBuilder);
|
|
457
|
-
return [...new Set(slash)];
|
|
458
|
-
}
|
|
459
|
-
indexByName(collection) {
|
|
460
|
-
const map = /* @__PURE__ */ new Map();
|
|
461
|
-
for (const command of collection.values()) map.set(command.name, command.id);
|
|
462
|
-
return map;
|
|
463
|
-
}
|
|
464
|
-
indexGuilds(guilds) {
|
|
465
|
-
const map = /* @__PURE__ */ new Map();
|
|
466
|
-
for (const collection of guilds.values()) for (const command of collection.values()) {
|
|
467
|
-
const existing = map.get(command.name);
|
|
468
|
-
if (existing === void 0) map.set(command.name, command.id);
|
|
469
|
-
else if (existing !== command.id) map.set(command.name, MULTI_GUILD);
|
|
470
|
-
}
|
|
471
|
-
return map;
|
|
472
|
-
}
|
|
473
|
-
resolveId(name, globalIds, guildIds, warned) {
|
|
474
|
-
const global = globalIds.get(name);
|
|
475
|
-
if (global) return global;
|
|
476
|
-
const guild = guildIds.get(name);
|
|
477
|
-
if (guild === void 0) return void 0;
|
|
478
|
-
if (guild === MULTI_GUILD) {
|
|
479
|
-
if (!warned.has(name)) {
|
|
480
|
-
this.logger.warn(`${name} is deployed to multiple guilds, falling back to plain text (not clickable).`);
|
|
481
|
-
warned.add(name);
|
|
482
|
-
}
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
return guild;
|
|
486
|
-
}
|
|
487
|
-
toMention(route, id) {
|
|
488
|
-
const [name, middle, sub] = route.split("/");
|
|
489
|
-
if (name && middle && sub) return (0, discord_js.chatInputApplicationCommandMention)(name, middle, sub, id);
|
|
490
|
-
if (name && middle) return (0, discord_js.chatInputApplicationCommandMention)(name, middle, id);
|
|
491
|
-
return (0, discord_js.chatInputApplicationCommandMention)(name ?? route, id);
|
|
492
|
-
}
|
|
493
|
-
toPlain(route) {
|
|
494
|
-
return `/${route.replaceAll("/", " ")}`;
|
|
495
|
-
}
|
|
496
|
-
clear() {
|
|
497
|
-
for (const key of Object.keys(mentionStorage)) Reflect.deleteProperty(mentionStorage, key);
|
|
498
|
-
}
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
//#endregion
|
|
502
|
-
//#region src/bot/utilities/channels/fetchText.ts
|
|
503
|
-
/**
|
|
504
|
-
* Fetches and validates a text channel. Refuses when the channel doesn't exist or isn't a text channel.
|
|
505
|
-
*
|
|
506
|
-
* @param client - The Discord client instance
|
|
507
|
-
* @param channelId - Channel ID or TextChannel instance
|
|
508
|
-
* @returns Promise resolving to the text channel
|
|
509
|
-
*/
|
|
510
|
-
async function fetchText(client, channelId) {
|
|
511
|
-
if (channelId instanceof discord_js.TextChannel) return channelId;
|
|
512
|
-
let channel = client.channels.cache.get(channelId);
|
|
513
|
-
if (!channel) try {
|
|
514
|
-
channel = await client.channels.fetch(channelId);
|
|
515
|
-
} catch (err) {
|
|
516
|
-
if (err instanceof discord_js.DiscordAPIError && err.code === discord_js.RESTJSONErrorCodes.UnknownChannel) throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
517
|
-
throw err;
|
|
518
|
-
}
|
|
519
|
-
if (channel?.isTextBased()) return channel;
|
|
520
|
-
throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
346
|
//#endregion
|
|
524
347
|
//#region src/bot/notices/utils.ts
|
|
525
348
|
function mentionFor(subject) {
|
|
@@ -530,48 +353,37 @@ function mentionFor(subject) {
|
|
|
530
353
|
//#endregion
|
|
531
354
|
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
532
355
|
function checkPermissions(a, b, c, d, e) {
|
|
533
|
-
const { for: pFor, in: pIn, scope, inverse = false, missingNotice
|
|
356
|
+
const { for: pFor, in: pIn, scope, inverse = false, missingNotice, dangerousNotice } = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
|
|
534
357
|
for: a,
|
|
535
358
|
in: b,
|
|
536
359
|
scope: c,
|
|
537
360
|
inverse: d ?? false,
|
|
538
361
|
...e
|
|
539
362
|
} : a;
|
|
540
|
-
const Missing = missingCtor ?? _seedcord_core_internal.MissingPermissions;
|
|
541
|
-
const Dangerous = dangerousCtor ?? HasDangerousPermissions;
|
|
542
|
-
const subject = mentionFor(pFor);
|
|
543
363
|
const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
364
|
+
(0, _seedcord_core.assertPermissions)({
|
|
365
|
+
subject: mentionFor(pFor),
|
|
366
|
+
permissions: perms.bitfield,
|
|
367
|
+
scope,
|
|
368
|
+
inverse,
|
|
369
|
+
...missingNotice && { missingNotice },
|
|
370
|
+
...dangerousNotice && { dangerousNotice }
|
|
371
|
+
});
|
|
552
372
|
}
|
|
553
373
|
|
|
554
374
|
//#endregion
|
|
555
375
|
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
556
376
|
/**
|
|
557
|
-
* Checks
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
* @param target - Guild or text channel to check in
|
|
561
|
-
* @param scope - Permission bits to validate
|
|
562
|
-
* @param inverse - Whether to check for absence of the given permissions
|
|
563
|
-
* @param errors - Optional custom error constructors
|
|
377
|
+
* Checks the bot's own permissions in a {@link Guild} or {@link TextChannel}. Refuses when a permission in
|
|
378
|
+
* `scope` is missing, or with `inverse` when one is present. When the bot member is uncached, the refusal
|
|
379
|
+
* reports every permission in `scope` as missing.
|
|
564
380
|
*
|
|
565
381
|
* @example
|
|
566
382
|
* ```ts
|
|
567
383
|
* import { PermissionFlagsBits } from 'discord.js';
|
|
568
384
|
* import { checkBotPermissions } from '@seedcord/gateway';
|
|
569
385
|
*
|
|
570
|
-
*
|
|
571
|
-
* checkBotPermissions(textChannel, [
|
|
572
|
-
* PermissionFlagsBits.SendMessages,
|
|
573
|
-
* PermissionFlagsBits.ViewChannel
|
|
574
|
-
* ]);
|
|
386
|
+
* checkBotPermissions(textChannel, [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel]);
|
|
575
387
|
* ```
|
|
576
388
|
*/
|
|
577
389
|
function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
@@ -586,7 +398,7 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
586
398
|
}
|
|
587
399
|
const me = target.guild.members.me;
|
|
588
400
|
if (!me) {
|
|
589
|
-
new _seedcord_logger.Logger("checkBotPermissions").warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
401
|
+
new _seedcord_logger.Logger("checkBotPermissions", { channel: "errors" }).warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
590
402
|
const names = scope.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
|
|
591
403
|
throw new ((errors?.missingNotice) ?? _seedcord_core_internal.MissingPermissions)(void 0, "The bot", names);
|
|
592
404
|
}
|
|
@@ -594,26 +406,17 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
594
406
|
}
|
|
595
407
|
|
|
596
408
|
//#endregion
|
|
597
|
-
//#region src/bot/utilities/
|
|
598
|
-
|
|
599
|
-
* Gets the bot's managed role in a guild.
|
|
600
|
-
*
|
|
601
|
-
* @param guild - The guild to get the bot role from
|
|
602
|
-
* @returns The bot's managed role in the guild
|
|
603
|
-
* @throws A **SeedcordError** if the client user is unavailable or if the bot role is missing
|
|
604
|
-
*/
|
|
605
|
-
function getBotRole(guild) {
|
|
409
|
+
//#region src/bot/utilities/permissions/hasPermsToAssign.ts
|
|
410
|
+
function botRoleOf(guild) {
|
|
606
411
|
const botRole = guild.roles.botRoleFor(guild.client.user);
|
|
607
412
|
if (!botRole) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreBotRoleMissing, [guild.id]);
|
|
608
413
|
return botRole;
|
|
609
414
|
}
|
|
610
|
-
|
|
611
|
-
//#endregion
|
|
612
|
-
//#region src/bot/utilities/permissions/hasPermsToAssign.ts
|
|
613
415
|
/**
|
|
614
|
-
* Validates
|
|
416
|
+
* Validates that the bot can assign a target role. Refuses when the target role is above the bot's, is managed, or the bot lacks Manage Roles.
|
|
615
417
|
*
|
|
616
418
|
* @param roleOrOptions - Target role or complete options for the check
|
|
419
|
+
* @throws A `Notice` on refusal, or a `SeedcordError` when the bot has no role in the guild's role cache.
|
|
617
420
|
*
|
|
618
421
|
* @example
|
|
619
422
|
* ```ts
|
|
@@ -641,135 +444,108 @@ function hasPermsToAssign(roleOrOptions) {
|
|
|
641
444
|
const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
|
|
642
445
|
const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
|
|
643
446
|
const MissingNotice = errors?.missingNotice ?? _seedcord_core_internal.MissingPermissions;
|
|
644
|
-
const botRole =
|
|
447
|
+
const botRole = botRoleOf(targetRole.guild);
|
|
645
448
|
if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
|
|
646
449
|
if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
|
|
647
450
|
checkBotPermissions(targetRole.guild, [discord_js.PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
|
|
648
451
|
}
|
|
649
452
|
|
|
650
453
|
//#endregion
|
|
651
|
-
//#region src/bot/
|
|
652
|
-
|
|
653
|
-
return err instanceof discord_js.DiscordAPIError && err.code === discord_js.RESTJSONErrorCodes.UnknownRole;
|
|
654
|
-
}
|
|
655
|
-
async function fetchRoleInGuild(guild, roleId) {
|
|
656
|
-
const cached = guild.roles.cache.get(roleId);
|
|
657
|
-
if (cached) return cached;
|
|
658
|
-
try {
|
|
659
|
-
return await guild.roles.fetch(roleId);
|
|
660
|
-
} catch (err) {
|
|
661
|
-
if (isUnknownRole(err)) throw new RoleDoesNotExist("Role not found in specified guild", roleId);
|
|
662
|
-
throw err;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
async function scanGuildsForRole(client, roleId) {
|
|
666
|
-
const cached = client.guilds.cache.map((guild) => guild.roles.cache.get(roleId)).find(Boolean);
|
|
667
|
-
if (cached) return cached;
|
|
668
|
-
for (const guild of client.guilds.cache.values()) try {
|
|
669
|
-
const role = await guild.roles.fetch(roleId);
|
|
670
|
-
if (role) return role;
|
|
671
|
-
} catch (err) {
|
|
672
|
-
if (isUnknownRole(err)) continue;
|
|
673
|
-
throw err;
|
|
674
|
-
}
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
454
|
+
//#region src/bot/confirm/reserved.ts
|
|
455
|
+
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
677
456
|
/**
|
|
678
|
-
*
|
|
457
|
+
* The reserved confirm/cancel codec. It has no route, so the dispatcher ignores its clicks and only the
|
|
458
|
+
* in-process collector reads them. Do not mint a real component from it.
|
|
679
459
|
*
|
|
680
|
-
* @
|
|
681
|
-
* @param roleId - The role ID to fetch
|
|
682
|
-
* @returns Promise resolving to the role
|
|
460
|
+
* @internal
|
|
683
461
|
*/
|
|
684
|
-
|
|
685
|
-
if (!roleId) throw new RoleDoesNotExist("Role ID is null or undefined", roleId);
|
|
686
|
-
const role = clientOrGuild instanceof discord_js.Guild ? await fetchRoleInGuild(clientOrGuild, roleId) : await scanGuildsForRole(clientOrGuild, roleId);
|
|
687
|
-
if (!role) throw new RoleDoesNotExist("Role not found", roleId);
|
|
688
|
-
return role;
|
|
689
|
-
}
|
|
462
|
+
const CONFIRM_DEF = new _seedcord_core.CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
|
|
690
463
|
|
|
691
464
|
//#endregion
|
|
692
|
-
//#region src/bot/
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
465
|
+
//#region src/bot/confirm/getConfirmation.ts
|
|
466
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
467
|
+
const CONFIRM_IDS = {
|
|
468
|
+
confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
|
|
469
|
+
cancel: CONFIRM_DEF.encode({ choice: "cancel" })
|
|
470
|
+
};
|
|
471
|
+
var ConfirmButtons = class extends _seedcord_core.RowComponent {
|
|
472
|
+
constructor(options) {
|
|
473
|
+
super("button");
|
|
474
|
+
this.instance.addComponents(new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? discord_js.ButtonStyle.Danger), new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(discord_js.ButtonStyle.Secondary));
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
var ConfirmPromptCard = class extends _seedcord_core.BuilderComponent {
|
|
478
|
+
constructor(text, options) {
|
|
479
|
+
super("container");
|
|
480
|
+
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
function defaultPrompt(text, options) {
|
|
484
|
+
return { components: [new ConfirmPromptCard(text, options).component] };
|
|
707
485
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
async function fetchManyGuildMembers(guild, userIds) {
|
|
719
|
-
return (await Promise.allSettled(userIds.map((userId) => fetchGuildMember(guild, userId)))).filter((result) => result.status === "fulfilled").map((result) => result.value);
|
|
486
|
+
async function collectChoice(message, userId, timeoutMs) {
|
|
487
|
+
try {
|
|
488
|
+
return await message.awaitMessageComponent({
|
|
489
|
+
componentType: discord_js.ComponentType.Button,
|
|
490
|
+
time: timeoutMs,
|
|
491
|
+
filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
|
|
492
|
+
});
|
|
493
|
+
} catch {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
720
496
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
|
|
497
|
+
async function settle(sender, message, outcome) {
|
|
498
|
+
if (outcome === void 0) {
|
|
499
|
+
await sender.delete(message).catch(() => void 0);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
503
|
+
}
|
|
504
|
+
async function getConfirmation(handler, prompt, options) {
|
|
505
|
+
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
506
|
+
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
507
|
+
const interaction = handler.getEvent();
|
|
508
|
+
const sender = handler.getSender();
|
|
509
|
+
const message = await sender.send(response, { ephemeral });
|
|
510
|
+
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
511
|
+
if (!winner) {
|
|
512
|
+
await settle(sender, message, options?.onTimeout);
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
await winner.deferUpdate().catch(() => void 0);
|
|
516
|
+
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
517
|
+
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
518
|
+
return confirmed;
|
|
740
519
|
}
|
|
741
520
|
|
|
742
521
|
//#endregion
|
|
743
|
-
//#region src/
|
|
522
|
+
//#region src/handlers/BaseHandler.ts
|
|
744
523
|
/**
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
* @param client - The Discord client instance
|
|
748
|
-
* @param userIds - Array of Discord user IDs
|
|
749
|
-
* @param options - Optional overrides threaded to each {@link fetchUser} call
|
|
750
|
-
* @returns Promise resolving to array of successfully fetched users
|
|
524
|
+
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
525
|
+
* gate hook.
|
|
751
526
|
*/
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
527
|
+
var BaseHandler = class extends _seedcord_core.BaseHandler {
|
|
528
|
+
/** @internal */
|
|
529
|
+
getEvent() {
|
|
530
|
+
return this.event;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
755
533
|
|
|
756
534
|
//#endregion
|
|
757
|
-
//#region src/
|
|
535
|
+
//#region src/handlers/interaction/InteractionMiddleware.ts
|
|
758
536
|
/**
|
|
759
|
-
*
|
|
537
|
+
* Base class for interaction middleware
|
|
760
538
|
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
* @
|
|
539
|
+
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
540
|
+
* Unlike handlers, middleware should not send responses directly.
|
|
541
|
+
*
|
|
542
|
+
* @typeParam Repliable - The interaction type this middleware processes
|
|
765
543
|
*/
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
await member.roles.set([...updated]);
|
|
772
|
-
}
|
|
544
|
+
var InteractionMiddleware = class extends BaseHandler {
|
|
545
|
+
constructor(event, core) {
|
|
546
|
+
super(event, core, void 0, "interactions");
|
|
547
|
+
}
|
|
548
|
+
};
|
|
773
549
|
|
|
774
550
|
//#endregion
|
|
775
551
|
//#region src/bot/ReplySender.ts
|
|
@@ -785,8 +561,11 @@ function seedState(interaction) {
|
|
|
785
561
|
*/
|
|
786
562
|
var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
787
563
|
interaction;
|
|
788
|
-
constructor(interaction, routeId) {
|
|
789
|
-
super(routeId, seedState(interaction)
|
|
564
|
+
constructor(interaction, routeId, bus) {
|
|
565
|
+
super(routeId, seedState(interaction), bus && {
|
|
566
|
+
bus,
|
|
567
|
+
interactionId: interaction.id
|
|
568
|
+
});
|
|
790
569
|
this.interaction = interaction;
|
|
791
570
|
}
|
|
792
571
|
async writeReply(response, opts) {
|
|
@@ -855,319 +634,26 @@ var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
|
855
634
|
...reply.files && { files: [...reply.files] }
|
|
856
635
|
};
|
|
857
636
|
}
|
|
858
|
-
editOptions(response) {
|
|
859
|
-
const reply = this.serialize(response);
|
|
860
|
-
return {
|
|
861
|
-
components: reply.components,
|
|
862
|
-
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
863
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
864
|
-
...reply.files && { files: [...reply.files] }
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
updateOptions(response) {
|
|
868
|
-
const reply = this.serialize(response);
|
|
869
|
-
return {
|
|
870
|
-
components: reply.components,
|
|
871
|
-
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
872
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
873
|
-
...reply.files && { files: [...reply.files] }
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
createdMessage(result) {
|
|
877
|
-
return result.resource?.message ?? void 0;
|
|
878
|
-
}
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
//#endregion
|
|
882
|
-
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
883
|
-
/**
|
|
884
|
-
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
885
|
-
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
886
|
-
* interaction and a generated registry key always match.
|
|
887
|
-
*
|
|
888
|
-
* @internal
|
|
889
|
-
*/
|
|
890
|
-
function slashRouteOf(interaction) {
|
|
891
|
-
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
892
|
-
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
893
|
-
return (0, _seedcord_utils_internal.buildSlashRoute)(interaction.commandName, subcommand, group);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
//#endregion
|
|
897
|
-
//#region src/miscellaneous/FaultThrottle.ts
|
|
898
|
-
/**
|
|
899
|
-
* Drops duplicate fault reports inside a fixed window so a recurring fault (a database outage on a
|
|
900
|
-
* hot path) reports once per window instead of flooding the webhook. Keyed on a structural string, so
|
|
901
|
-
* the map stays small, and a stale entry is evicted on the next read of its key.
|
|
902
|
-
*
|
|
903
|
-
* @internal
|
|
904
|
-
*/
|
|
905
|
-
var FaultThrottle = class {
|
|
906
|
-
windowMs;
|
|
907
|
-
now;
|
|
908
|
-
lastReportedAt = /* @__PURE__ */ new Map();
|
|
909
|
-
constructor(windowMs = 6e4, now = () => Date.now()) {
|
|
910
|
-
this.windowMs = windowMs;
|
|
911
|
-
this.now = now;
|
|
912
|
-
}
|
|
913
|
-
/** Evicts the entry for this key if it has aged out, so the map never holds stale keys forever. */
|
|
914
|
-
shouldReport(key) {
|
|
915
|
-
const last = this.lastReportedAt.get(key);
|
|
916
|
-
if (last === void 0) return true;
|
|
917
|
-
if (this.now() - last < this.windowMs) return false;
|
|
918
|
-
this.lastReportedAt.delete(key);
|
|
919
|
-
return true;
|
|
920
|
-
}
|
|
921
|
-
markReported(key) {
|
|
922
|
-
this.lastReportedAt.set(key, this.now());
|
|
923
|
-
}
|
|
924
|
-
clear() {
|
|
925
|
-
this.lastReportedAt.clear();
|
|
926
|
-
}
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
//#endregion
|
|
930
|
-
//#region src/miscellaneous/extractErrorResponse.ts
|
|
931
|
-
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling");
|
|
932
|
-
const faultThrottle = new FaultThrottle();
|
|
933
|
-
/**
|
|
934
|
-
* Processes an error into a user-facing reply and routes its report.
|
|
935
|
-
*
|
|
936
|
-
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
937
|
-
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
938
|
-
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
939
|
-
* threaded into both the reply and the bus payload.
|
|
940
|
-
*/
|
|
941
|
-
function extractErrorResponse(error, core, origin) {
|
|
942
|
-
const uuid = node_crypto.randomUUID();
|
|
943
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
944
|
-
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
945
|
-
uuid,
|
|
946
|
-
developerUsername
|
|
947
|
-
};
|
|
948
|
-
if (error instanceof _seedcord_core.Notice) {
|
|
949
|
-
if (error.report) reportFault(error, core, origin, uuid);
|
|
950
|
-
return {
|
|
951
|
-
uuid,
|
|
952
|
-
response: error.render(ctx)
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
reportRawFault(error, core, origin, uuid);
|
|
956
|
-
const override = core.config.errors?.defaultError;
|
|
957
|
-
return {
|
|
958
|
-
uuid,
|
|
959
|
-
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
function withThrottle(origin, error, publish) {
|
|
963
|
-
const key = faultKey(origin, error);
|
|
964
|
-
if (!faultThrottle.shouldReport(key)) {
|
|
965
|
-
logger$2.info(`throttled duplicate fault ${key}`);
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
publish();
|
|
969
|
-
faultThrottle.markReported(key);
|
|
970
|
-
}
|
|
971
|
-
function reportFault(denial, core, origin, uuid) {
|
|
972
|
-
withThrottle(origin, denial, () => {
|
|
973
|
-
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
974
|
-
if (origin.interaction) core.bus.publish("handledException", {
|
|
975
|
-
denial,
|
|
976
|
-
uuid,
|
|
977
|
-
source: buildInteractionSource(origin.interaction)
|
|
978
|
-
});
|
|
979
|
-
else if (origin.event) core.bus.publish("handledException", {
|
|
980
|
-
denial,
|
|
981
|
-
uuid,
|
|
982
|
-
source: buildEventSource(origin.event, origin)
|
|
983
|
-
});
|
|
984
|
-
else core.bus.publish("unknownException", {
|
|
985
|
-
uuid,
|
|
986
|
-
error: denial,
|
|
987
|
-
...scalarActors(origin),
|
|
988
|
-
metadata: metadataFor(origin)
|
|
989
|
-
});
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
function causeLine(error) {
|
|
993
|
-
const { cause } = error;
|
|
994
|
-
if (!Error.isError(cause)) return "";
|
|
995
|
-
const [first] = cause.message.split("\n");
|
|
996
|
-
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
997
|
-
}
|
|
998
|
-
function reportRawFault(error, core, origin, uuid) {
|
|
999
|
-
withThrottle(origin, error, () => {
|
|
1000
|
-
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
1001
|
-
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
1002
|
-
core.bus.publish("unknownException", {
|
|
1003
|
-
uuid,
|
|
1004
|
-
error,
|
|
1005
|
-
...scalarActors(origin),
|
|
1006
|
-
metadata: metadataFor(origin)
|
|
1007
|
-
});
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
function scalarActors(origin) {
|
|
1011
|
-
return {
|
|
1012
|
-
guild: origin.guild ? {
|
|
1013
|
-
id: origin.guild.id,
|
|
1014
|
-
name: origin.guild.name
|
|
1015
|
-
} : void 0,
|
|
1016
|
-
user: origin.user ? {
|
|
1017
|
-
id: origin.user.id,
|
|
1018
|
-
username: origin.user.username
|
|
1019
|
-
} : void 0
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
function metadataFor(origin) {
|
|
1023
|
-
if (origin.event) return {
|
|
1024
|
-
eventName: origin.event.name,
|
|
1025
|
-
handler: origin.event.handler,
|
|
1026
|
-
args: origin.event.args
|
|
1027
|
-
};
|
|
1028
|
-
return origin.metadata;
|
|
1029
|
-
}
|
|
1030
|
-
function buildEventSource(event, origin) {
|
|
1031
|
-
return {
|
|
1032
|
-
kind: "event",
|
|
1033
|
-
eventName: event.name,
|
|
1034
|
-
handler: event.handler,
|
|
1035
|
-
userId: origin.user?.id ?? null,
|
|
1036
|
-
guildId: origin.guild?.id ?? null,
|
|
1037
|
-
channelId: event.channelId,
|
|
1038
|
-
raw: event.args
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
function faultKey(origin, error) {
|
|
1042
|
-
const name = error instanceof _seedcord_core.Notice ? error.name : error instanceof discord_js.DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1043
|
-
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1044
|
-
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1045
|
-
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1046
|
-
return `autocomplete:${name}`;
|
|
1047
|
-
}
|
|
1048
|
-
/** The stable route id of a repliable interaction, its slash route path, command name, or customId prefix. */
|
|
1049
|
-
function interactionRoute(interaction) {
|
|
1050
|
-
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1051
|
-
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1052
|
-
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
1053
|
-
return "interaction";
|
|
1054
|
-
}
|
|
1055
|
-
function buildInteractionSource(interaction) {
|
|
1056
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1057
|
-
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1058
|
-
return {
|
|
1059
|
-
kind: "interaction",
|
|
1060
|
-
interactionKind: interactionKind(interaction),
|
|
1061
|
-
command,
|
|
1062
|
-
customId,
|
|
1063
|
-
userId: interaction.user.id,
|
|
1064
|
-
guildId: interaction.guildId,
|
|
1065
|
-
channelId: interaction.channelId,
|
|
1066
|
-
interactionId: interaction.id,
|
|
1067
|
-
raw: interaction
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
function interactionKind(interaction) {
|
|
1071
|
-
if (interaction.isChatInputCommand()) return "slash";
|
|
1072
|
-
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1073
|
-
if (interaction.isButton()) return "button";
|
|
1074
|
-
if (interaction.isAnySelectMenu()) return "select";
|
|
1075
|
-
return "modal";
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
//#endregion
|
|
1079
|
-
//#region src/bot/confirm/reserved.ts
|
|
1080
|
-
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
1081
|
-
/**
|
|
1082
|
-
* The reserved confirm/cancel codec. It has no route, so the dispatcher ignores its clicks and only the
|
|
1083
|
-
* in-process collector reads them. Do not mint a real component from it.
|
|
1084
|
-
*
|
|
1085
|
-
* @internal
|
|
1086
|
-
*/
|
|
1087
|
-
const CONFIRM_DEF = new _seedcord_core.CustomId(RESERVED_CONFIRM_PREFIX).oneOf("choice", ["confirm", "cancel"]);
|
|
1088
|
-
|
|
1089
|
-
//#endregion
|
|
1090
|
-
//#region src/bot/confirm/getConfirmation.ts
|
|
1091
|
-
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
1092
|
-
const CONFIRM_IDS = {
|
|
1093
|
-
confirm: CONFIRM_DEF.encode({ choice: "confirm" }),
|
|
1094
|
-
cancel: CONFIRM_DEF.encode({ choice: "cancel" })
|
|
1095
|
-
};
|
|
1096
|
-
var ConfirmButtons = class extends _seedcord_core.RowComponent {
|
|
1097
|
-
constructor(options) {
|
|
1098
|
-
super("button");
|
|
1099
|
-
this.instance.addComponents(new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.confirm).setLabel(options?.confirmLabel ?? "Confirm").setStyle(options?.confirmStyle ?? discord_js.ButtonStyle.Danger), new _discordjs_builders.ButtonBuilder().setCustomId(CONFIRM_IDS.cancel).setLabel(options?.cancelLabel ?? "Cancel").setStyle(discord_js.ButtonStyle.Secondary));
|
|
1100
|
-
}
|
|
1101
|
-
};
|
|
1102
|
-
var ConfirmPromptCard = class extends _seedcord_core.BuilderComponent {
|
|
1103
|
-
constructor(text, options) {
|
|
1104
|
-
super("container");
|
|
1105
|
-
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(text)).addActionRowComponents(new ConfirmButtons(options).component);
|
|
1106
|
-
}
|
|
1107
|
-
};
|
|
1108
|
-
function defaultPrompt(text, options) {
|
|
1109
|
-
return { components: [new ConfirmPromptCard(text, options).component] };
|
|
1110
|
-
}
|
|
1111
|
-
async function collectChoice(message, userId, timeoutMs) {
|
|
1112
|
-
try {
|
|
1113
|
-
return await message.awaitMessageComponent({
|
|
1114
|
-
componentType: discord_js.ComponentType.Button,
|
|
1115
|
-
time: timeoutMs,
|
|
1116
|
-
filter: (click) => click.user.id === userId && CONFIRM_DEF.owns(click.customId)
|
|
1117
|
-
});
|
|
1118
|
-
} catch {
|
|
1119
|
-
return null;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
async function settle(sender, message, outcome) {
|
|
1123
|
-
if (outcome === void 0) {
|
|
1124
|
-
await sender.delete(message).catch(() => void 0);
|
|
1125
|
-
return;
|
|
1126
|
-
}
|
|
1127
|
-
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
1128
|
-
}
|
|
1129
|
-
async function getConfirmation(interaction, prompt, options) {
|
|
1130
|
-
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
1131
|
-
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
1132
|
-
const sender = new ReplySender(interaction, interactionRoute(interaction));
|
|
1133
|
-
const message = await sender.send(response, { ephemeral });
|
|
1134
|
-
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
1135
|
-
if (!winner) {
|
|
1136
|
-
await settle(sender, message, options?.onTimeout);
|
|
1137
|
-
return false;
|
|
1138
|
-
}
|
|
1139
|
-
await winner.deferUpdate().catch(() => void 0);
|
|
1140
|
-
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
1141
|
-
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
1142
|
-
return confirmed;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
//#endregion
|
|
1146
|
-
//#region src/handlers/BaseHandler.ts
|
|
1147
|
-
/**
|
|
1148
|
-
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
1149
|
-
* gate hook.
|
|
1150
|
-
*/
|
|
1151
|
-
var BaseHandler = class extends _seedcord_core.BaseHandler {
|
|
1152
|
-
/** @internal */
|
|
1153
|
-
getEvent() {
|
|
1154
|
-
return this.event;
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
|
|
1158
|
-
//#endregion
|
|
1159
|
-
//#region src/handlers/interaction/InteractionMiddleware.ts
|
|
1160
|
-
/**
|
|
1161
|
-
* Base class for interaction middleware
|
|
1162
|
-
*
|
|
1163
|
-
* Middleware runs before interaction handlers and can modify behavior or block execution.
|
|
1164
|
-
* Unlike handlers, middleware should not send responses directly.
|
|
1165
|
-
*
|
|
1166
|
-
* @typeParam Repliable - The interaction type this middleware processes
|
|
1167
|
-
*/
|
|
1168
|
-
var InteractionMiddleware = class extends BaseHandler {
|
|
1169
|
-
constructor(event, core) {
|
|
1170
|
-
super(event, core);
|
|
637
|
+
editOptions(response) {
|
|
638
|
+
const reply = this.serialize(response);
|
|
639
|
+
return {
|
|
640
|
+
components: reply.components,
|
|
641
|
+
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
642
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
643
|
+
...reply.files && { files: [...reply.files] }
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
updateOptions(response) {
|
|
647
|
+
const reply = this.serialize(response);
|
|
648
|
+
return {
|
|
649
|
+
components: reply.components,
|
|
650
|
+
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
651
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
652
|
+
...reply.files && { files: [...reply.files] }
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
createdMessage(result) {
|
|
656
|
+
return result.resource?.message ?? void 0;
|
|
1171
657
|
}
|
|
1172
658
|
};
|
|
1173
659
|
|
|
@@ -1187,8 +673,8 @@ var RepliableHandler = class extends _seedcord_core.RepliableHandler {
|
|
|
1187
673
|
getEvent() {
|
|
1188
674
|
return this.event;
|
|
1189
675
|
}
|
|
1190
|
-
buildSender(event,
|
|
1191
|
-
return new ReplySender(event, routeId);
|
|
676
|
+
buildSender(event, core, routeId) {
|
|
677
|
+
return new ReplySender(event, routeId, core.bus);
|
|
1192
678
|
}
|
|
1193
679
|
};
|
|
1194
680
|
|
|
@@ -1216,6 +702,21 @@ var InteractionHandler = class extends RepliableHandler {
|
|
|
1216
702
|
}
|
|
1217
703
|
};
|
|
1218
704
|
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
707
|
+
/**
|
|
708
|
+
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
709
|
+
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
710
|
+
* interaction and a generated registry key always match.
|
|
711
|
+
*
|
|
712
|
+
* @internal
|
|
713
|
+
*/
|
|
714
|
+
function slashRouteOf(interaction) {
|
|
715
|
+
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
716
|
+
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
717
|
+
return (0, _seedcord_utils_internal.buildSlashRoute)(interaction.commandName, subcommand, group);
|
|
718
|
+
}
|
|
719
|
+
|
|
1219
720
|
//#endregion
|
|
1220
721
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
1221
722
|
/**
|
|
@@ -1343,7 +844,7 @@ var ContextMenuHandler = class extends InteractionHandler {
|
|
|
1343
844
|
*/
|
|
1344
845
|
var AutocompleteHandler = class extends BaseHandler {
|
|
1345
846
|
constructor(event, core, dispatch) {
|
|
1346
|
-
super(event, core, dispatch);
|
|
847
|
+
super(event, core, dispatch, "interactions");
|
|
1347
848
|
}
|
|
1348
849
|
decodedFocused;
|
|
1349
850
|
/**
|
|
@@ -1394,7 +895,11 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1394
895
|
}
|
|
1395
896
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
1396
897
|
async respond(choices) {
|
|
1397
|
-
|
|
898
|
+
const routeId = this.dispatch?.routeId ?? "autocomplete";
|
|
899
|
+
await (0, _seedcord_core_internal.reportedWrite)({
|
|
900
|
+
bus: this.core.bus,
|
|
901
|
+
interactionId: this.event.id
|
|
902
|
+
}, routeId, "respond", () => this.event.respond(choices));
|
|
1398
903
|
}
|
|
1399
904
|
/** The firing command route, for a field whose completion differs per registered command. */
|
|
1400
905
|
get route() {
|
|
@@ -1531,7 +1036,22 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1531
1036
|
* }
|
|
1532
1037
|
* ```
|
|
1533
1038
|
*/
|
|
1534
|
-
var ModalHandler = class extends ComponentHandler {
|
|
1039
|
+
var ModalHandler = class extends ComponentHandler {
|
|
1040
|
+
/** Rewrite the message this modal was opened from. Throws when a command opened the modal. */
|
|
1041
|
+
update(response) {
|
|
1042
|
+
this.ensureSourceMessage("update");
|
|
1043
|
+
return super.update(response);
|
|
1044
|
+
}
|
|
1045
|
+
/** Acknowledge the submit without changing the source message. Throws when a command opened the modal. */
|
|
1046
|
+
deferUpdate() {
|
|
1047
|
+
this.ensureSourceMessage("deferUpdate");
|
|
1048
|
+
return super.deferUpdate();
|
|
1049
|
+
}
|
|
1050
|
+
ensureSourceMessage(method) {
|
|
1051
|
+
if (this.event.isFromMessage()) return;
|
|
1052
|
+
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1535
1055
|
|
|
1536
1056
|
//#endregion
|
|
1537
1057
|
//#region src/handlers/interaction/components/SelectMenuHandler.ts
|
|
@@ -1587,7 +1107,7 @@ var SelectMenuHandler = class extends ComponentHandler {};
|
|
|
1587
1107
|
var EventHandler = class extends BaseHandler {
|
|
1588
1108
|
firedEvent;
|
|
1589
1109
|
constructor(event, core, eventName) {
|
|
1590
|
-
super(event, core);
|
|
1110
|
+
super(event, core, void 0, "events");
|
|
1591
1111
|
this.firedEvent = eventName;
|
|
1592
1112
|
}
|
|
1593
1113
|
/**
|
|
@@ -1640,7 +1160,7 @@ var EventHandler = class extends BaseHandler {
|
|
|
1640
1160
|
var EventMiddleware = class extends BaseHandler {
|
|
1641
1161
|
firedEvent;
|
|
1642
1162
|
constructor(event, core, eventName) {
|
|
1643
|
-
super(event, core);
|
|
1163
|
+
super(event, core, void 0, "events");
|
|
1644
1164
|
this.firedEvent = eventName;
|
|
1645
1165
|
}
|
|
1646
1166
|
/**
|
|
@@ -1654,130 +1174,6 @@ var EventMiddleware = class extends BaseHandler {
|
|
|
1654
1174
|
}
|
|
1655
1175
|
};
|
|
1656
1176
|
|
|
1657
|
-
//#endregion
|
|
1658
|
-
//#region src/pagination/controls.ts
|
|
1659
|
-
const DEFAULT_LABEL = {
|
|
1660
|
-
first: "First",
|
|
1661
|
-
prev: "Prev",
|
|
1662
|
-
indicator: "",
|
|
1663
|
-
next: "Next",
|
|
1664
|
-
last: "Last"
|
|
1665
|
-
};
|
|
1666
|
-
const CONTROL_SLOT = {
|
|
1667
|
-
first: 0,
|
|
1668
|
-
prev: 1,
|
|
1669
|
-
indicator: 2,
|
|
1670
|
-
next: 3,
|
|
1671
|
-
last: 4
|
|
1672
|
-
};
|
|
1673
|
-
const MAX_BUTTONS_PER_ROW = 5;
|
|
1674
|
-
var Controls = class {
|
|
1675
|
-
cursor;
|
|
1676
|
-
view;
|
|
1677
|
-
constructor(cursor, view) {
|
|
1678
|
-
this.cursor = cursor;
|
|
1679
|
-
this.view = view;
|
|
1680
|
-
}
|
|
1681
|
-
button(key, cosmetics) {
|
|
1682
|
-
const { target, disabled } = this.navState(key);
|
|
1683
|
-
const defaultLabel = key === "indicator" ? this.indicatorText() : DEFAULT_LABEL[key];
|
|
1684
|
-
const label = cosmetics?.label ?? (key === "indicator" || !cosmetics?.emoji ? defaultLabel : void 0);
|
|
1685
|
-
const button = new _discordjs_builders.ButtonBuilder().setCustomId(this.cursor.encode({
|
|
1686
|
-
page: Math.min(Math.max(0, target), _seedcord_core_internal.PAGE_MAX),
|
|
1687
|
-
slot: CONTROL_SLOT[key]
|
|
1688
|
-
})).setStyle(cosmetics?.style ?? discord_js.ButtonStyle.Secondary).setDisabled(disabled);
|
|
1689
|
-
if (label !== void 0) button.setLabel(label);
|
|
1690
|
-
if (cosmetics?.emoji) button.setEmoji(cosmetics.emoji);
|
|
1691
|
-
return button;
|
|
1692
|
-
}
|
|
1693
|
-
navState(key) {
|
|
1694
|
-
const { page, totalPages, hasPrev, hasNext } = this.view;
|
|
1695
|
-
const knownTotal = totalPages !== void 0;
|
|
1696
|
-
return {
|
|
1697
|
-
target: {
|
|
1698
|
-
first: 0,
|
|
1699
|
-
prev: page - 1,
|
|
1700
|
-
indicator: page,
|
|
1701
|
-
next: page + 1,
|
|
1702
|
-
last: knownTotal ? totalPages - 1 : page
|
|
1703
|
-
}[key],
|
|
1704
|
-
disabled: {
|
|
1705
|
-
first: !hasPrev,
|
|
1706
|
-
prev: !hasPrev,
|
|
1707
|
-
indicator: true,
|
|
1708
|
-
next: !hasNext || page + 1 > _seedcord_core_internal.PAGE_MAX,
|
|
1709
|
-
last: !hasNext || !knownTotal || totalPages - 1 > _seedcord_core_internal.PAGE_MAX
|
|
1710
|
-
}[key]
|
|
1711
|
-
};
|
|
1712
|
-
}
|
|
1713
|
-
row(...keys) {
|
|
1714
|
-
if (keys.length === 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationEmptyControls);
|
|
1715
|
-
if (keys.length > MAX_BUTTONS_PER_ROW) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationTooManyControls, [keys.length]);
|
|
1716
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1717
|
-
for (const key of keys) {
|
|
1718
|
-
if (seen.has(key)) throw new _seedcord_errors_internal.SeedcordTypeError(_seedcord_errors.SeedcordErrorCode.PaginationDuplicateControls, [key]);
|
|
1719
|
-
seen.add(key);
|
|
1720
|
-
}
|
|
1721
|
-
return new _discordjs_builders.ActionRowBuilder().addComponents(keys.map((key) => this.button(key)));
|
|
1722
|
-
}
|
|
1723
|
-
indicatorText() {
|
|
1724
|
-
const { page, totalPages } = this.view;
|
|
1725
|
-
return totalPages === void 0 ? `Page ${page + 1}` : `Page ${page + 1} of ${totalPages}`;
|
|
1726
|
-
}
|
|
1727
|
-
};
|
|
1728
|
-
|
|
1729
|
-
//#endregion
|
|
1730
|
-
//#region src/pagination/render.ts
|
|
1731
|
-
var PageContainer = class extends _seedcord_core.BuilderComponent {
|
|
1732
|
-
constructor(view, renderItem, controlRow) {
|
|
1733
|
-
super("container");
|
|
1734
|
-
const base = view.page * view.perPage;
|
|
1735
|
-
let lines = [];
|
|
1736
|
-
const flush = () => {
|
|
1737
|
-
if (lines.length === 0) return;
|
|
1738
|
-
this.instance.addTextDisplayComponents(new _discordjs_builders.TextDisplayBuilder().setContent(lines.join("\n")));
|
|
1739
|
-
lines = [];
|
|
1740
|
-
};
|
|
1741
|
-
view.items.forEach((item, offset) => {
|
|
1742
|
-
const rendered = renderItem(item, base + offset);
|
|
1743
|
-
if (typeof rendered === "string") {
|
|
1744
|
-
lines.push(rendered);
|
|
1745
|
-
return;
|
|
1746
|
-
}
|
|
1747
|
-
flush();
|
|
1748
|
-
this.instance.addSectionComponents(rendered.component);
|
|
1749
|
-
});
|
|
1750
|
-
flush();
|
|
1751
|
-
this.instance.addActionRowComponents(controlRow);
|
|
1752
|
-
}
|
|
1753
|
-
};
|
|
1754
|
-
function toReplyResponse(renderable) {
|
|
1755
|
-
if (typeof renderable === "string") return { components: [new _discordjs_builders.TextDisplayBuilder().setContent(renderable)] };
|
|
1756
|
-
if (Array.isArray(renderable)) return { components: renderable };
|
|
1757
|
-
return renderable;
|
|
1758
|
-
}
|
|
1759
|
-
const ARRAY_KEYS = [
|
|
1760
|
-
"first",
|
|
1761
|
-
"prev",
|
|
1762
|
-
"indicator",
|
|
1763
|
-
"next",
|
|
1764
|
-
"last"
|
|
1765
|
-
];
|
|
1766
|
-
const CURSOR_KEYS = [
|
|
1767
|
-
"prev",
|
|
1768
|
-
"indicator",
|
|
1769
|
-
"next"
|
|
1770
|
-
];
|
|
1771
|
-
/**
|
|
1772
|
-
* Build a page's V2 reply. A `render` override builds the whole tree, otherwise the default container lists
|
|
1773
|
-
* the items and appends the controls (all five for a known total, prev/indicator/next for a cursor source).
|
|
1774
|
-
*/
|
|
1775
|
-
function renderPage(view, cursor, config) {
|
|
1776
|
-
const controls = new Controls(cursor, view);
|
|
1777
|
-
if (config.render) return toReplyResponse(config.render(view, controls));
|
|
1778
|
-
return { components: [new PageContainer(view, config.renderItem ?? String, controls.row(...view.totalPages === void 0 ? CURSOR_KEYS : ARRAY_KEYS)).component] };
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
1177
|
//#endregion
|
|
1782
1178
|
//#region src/pagination/Paginator.ts
|
|
1783
1179
|
function contextOf(interaction, core) {
|
|
@@ -1785,7 +1181,7 @@ function contextOf(interaction, core) {
|
|
|
1785
1181
|
interaction,
|
|
1786
1182
|
user: interaction.user,
|
|
1787
1183
|
guild: interaction.guild,
|
|
1788
|
-
|
|
1184
|
+
core
|
|
1789
1185
|
};
|
|
1790
1186
|
}
|
|
1791
1187
|
/**
|
|
@@ -1807,15 +1203,11 @@ function contextOf(interaction, core) {
|
|
|
1807
1203
|
* export class BansNav extends Bans.Handler {}
|
|
1808
1204
|
* ```
|
|
1809
1205
|
*/
|
|
1810
|
-
var Paginator = class {
|
|
1811
|
-
/** The page cursor, pass it to your `@ButtonRoute`. */
|
|
1812
|
-
cursor;
|
|
1206
|
+
var Paginator = class extends _seedcord_core.PaginatorBase {
|
|
1813
1207
|
/** The nav handler base. Extend it with an empty body and decorate it, `@ButtonRoute(p.cursor)`. */
|
|
1814
1208
|
Handler;
|
|
1815
|
-
config;
|
|
1816
1209
|
constructor(config) {
|
|
1817
|
-
|
|
1818
|
-
this.config = config;
|
|
1210
|
+
super(config);
|
|
1819
1211
|
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
1820
1212
|
this.Handler = class Nav extends ButtonHandler {
|
|
1821
1213
|
async execute() {
|
|
@@ -1825,39 +1217,29 @@ var Paginator = class {
|
|
|
1825
1217
|
}
|
|
1826
1218
|
};
|
|
1827
1219
|
}
|
|
1828
|
-
/**
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
async
|
|
1835
|
-
|
|
1220
|
+
/**
|
|
1221
|
+
* Render page 0 and send it through the handler's sender, which picks reply, edit, or followUp from its
|
|
1222
|
+
* ack state.
|
|
1223
|
+
*
|
|
1224
|
+
* @param handler - The handler starting the paginator, normally `this`.
|
|
1225
|
+
*/
|
|
1226
|
+
async start(handler) {
|
|
1227
|
+
const interaction = handler.getEvent();
|
|
1228
|
+
const response = await this.page(contextOf(interaction, handler.core), 0);
|
|
1229
|
+
return handler.getSender().send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
1836
1230
|
}
|
|
1837
1231
|
};
|
|
1838
1232
|
|
|
1839
1233
|
//#endregion
|
|
1840
1234
|
//#region src/pagination/sources.ts
|
|
1841
|
-
const DEFAULT_PER_PAGE = 10;
|
|
1842
1235
|
/**
|
|
1843
1236
|
* A source for a bounded list you can load whole. It loads the full list on every click, then slices the
|
|
1844
|
-
* page
|
|
1845
|
-
*
|
|
1237
|
+
* page, so the real total is known (a real last button, "Page X of Y"). Cache inside your loader if the
|
|
1238
|
+
* load is expensive.
|
|
1846
1239
|
*
|
|
1847
1240
|
* @typeParam Item - The item type, inferred from the loader's return.
|
|
1848
1241
|
*/
|
|
1849
|
-
var ArraySource = class {
|
|
1850
|
-
load;
|
|
1851
|
-
perPage;
|
|
1852
|
-
constructor(load, opts) {
|
|
1853
|
-
this.load = load;
|
|
1854
|
-
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
1855
|
-
if (!Number.isSafeInteger(this.perPage) || this.perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
1856
|
-
}
|
|
1857
|
-
async page(ctx, n) {
|
|
1858
|
-
return (0, _seedcord_core.paginate)(await this.load(ctx), n, this.perPage);
|
|
1859
|
-
}
|
|
1860
|
-
};
|
|
1242
|
+
var ArraySource = class extends _seedcord_core.ArraySource {};
|
|
1861
1243
|
/**
|
|
1862
1244
|
* A source for a large or unknown-length set you fetch one page at a time (SQL LIMIT/OFFSET, a paged API).
|
|
1863
1245
|
* The fetcher receives the page index and the page size and reports whether a next page exists. A cursor
|
|
@@ -1866,496 +1248,35 @@ var ArraySource = class {
|
|
|
1866
1248
|
*
|
|
1867
1249
|
* @typeParam Item - The item type, inferred from the fetcher's slice.
|
|
1868
1250
|
*/
|
|
1869
|
-
var CursorSource = class {
|
|
1870
|
-
fetch;
|
|
1871
|
-
perPage;
|
|
1872
|
-
constructor(fetch, opts) {
|
|
1873
|
-
this.fetch = fetch;
|
|
1874
|
-
this.perPage = opts?.perPage ?? DEFAULT_PER_PAGE;
|
|
1875
|
-
if (!Number.isSafeInteger(this.perPage) || this.perPage <= 0) throw new _seedcord_errors_internal.SeedcordRangeError(_seedcord_errors.SeedcordErrorCode.PaginationInvalidPerPage, [this.perPage]);
|
|
1876
|
-
}
|
|
1877
|
-
async page(ctx, n) {
|
|
1878
|
-
const page = Math.max(0, Math.trunc(n));
|
|
1879
|
-
const { items, hasNext } = await this.fetch(ctx, page, this.perPage);
|
|
1880
|
-
return {
|
|
1881
|
-
items: [...items],
|
|
1882
|
-
page,
|
|
1883
|
-
perPage: this.perPage,
|
|
1884
|
-
hasPrev: page > 0,
|
|
1885
|
-
hasNext
|
|
1886
|
-
};
|
|
1887
|
-
}
|
|
1888
|
-
};
|
|
1889
|
-
|
|
1890
|
-
//#endregion
|
|
1891
|
-
//#region src/subscribers/Subscriber.ts
|
|
1892
|
-
/**
|
|
1893
|
-
* Abstract base class for handling application subscribers.
|
|
1894
|
-
*
|
|
1895
|
-
* Provides type-safe access to subscribers data and the core framework instance.
|
|
1896
|
-
* Extend this class to create custom subscriber handlers.
|
|
1897
|
-
*
|
|
1898
|
-
* @typeParam KeyOfSubscribers - The specific subscriber type this handler processes
|
|
1899
|
-
*/
|
|
1900
|
-
var Subscriber = class {
|
|
1901
|
-
data;
|
|
1902
|
-
core;
|
|
1903
|
-
logger;
|
|
1904
|
-
/**
|
|
1905
|
-
* Instantiates the subscriber with event data and core.
|
|
1906
|
-
*
|
|
1907
|
-
* @param data - The subscriber event data
|
|
1908
|
-
* @param core - The core framework instance
|
|
1909
|
-
*/
|
|
1910
|
-
constructor(data, core) {
|
|
1911
|
-
this.data = data;
|
|
1912
|
-
this.core = core;
|
|
1913
|
-
this.logger = new _seedcord_logger.Logger(this.constructor.name);
|
|
1914
|
-
}
|
|
1915
|
-
};
|
|
1916
|
-
|
|
1917
|
-
//#endregion
|
|
1918
|
-
//#region src/subscribers/decorators/Subscribe.ts
|
|
1919
|
-
/**
|
|
1920
|
-
* Registers a subscriber handler class with a specific subscriber event.
|
|
1921
|
-
*
|
|
1922
|
-
* Associates the decorated class with a subscriber event type for automatic
|
|
1923
|
-
* registration and execution when the subscriber is emitted.
|
|
1924
|
-
*
|
|
1925
|
-
* @typeParam TSubscriber - The subscriber event name to register for
|
|
1926
|
-
* @param subscriber - The subscriber event name to register for
|
|
1927
|
-
* @param options - Options to configure the subscriber handler registration.
|
|
1928
|
-
* @decorator
|
|
1929
|
-
* @example
|
|
1930
|
-
* ```typescript
|
|
1931
|
-
* \@Subscribe('userJoin')
|
|
1932
|
-
* class WelcomeHandler extends Subscriber<'userJoin'> {
|
|
1933
|
-
* async execute() {
|
|
1934
|
-
* // Handle user join event
|
|
1935
|
-
* }
|
|
1936
|
-
* }
|
|
1937
|
-
* ```
|
|
1938
|
-
* @example
|
|
1939
|
-
* ```ts
|
|
1940
|
-
* \@Subscribe('userJoin', { frequency: 'once' })
|
|
1941
|
-
* // or
|
|
1942
|
-
* \@Subscribe('userJoin')
|
|
1943
|
-
* ```
|
|
1944
|
-
*/
|
|
1945
|
-
function Subscribe(subscriber, options) {
|
|
1946
|
-
return function(constructor) {
|
|
1947
|
-
const meta = {
|
|
1948
|
-
subscriber,
|
|
1949
|
-
frequency: options?.frequency
|
|
1950
|
-
};
|
|
1951
|
-
Reflect.defineMetadata(_seedcord_core_internal.SubscribeMetadataKey, meta, constructor);
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
//#endregion
|
|
1956
|
-
//#region src/subscribers/decorators/WebhookUrl.ts
|
|
1957
|
-
/**
|
|
1958
|
-
* Declares the environment variable a {@link WebhookLog} reporter reads its webhook url from.
|
|
1959
|
-
*
|
|
1960
|
-
* An unset variable disables the reporter with a boot warning. A set but malformed value throws
|
|
1961
|
-
* at boot.
|
|
1962
|
-
*
|
|
1963
|
-
* @param envKey - The environment variable name holding the webhook url
|
|
1964
|
-
* @decorator
|
|
1965
|
-
* @example
|
|
1966
|
-
* ```ts
|
|
1967
|
-
* \@Subscribe('memberBanned')
|
|
1968
|
-
* \@WebhookUrl('BAN_LOG_WEBHOOK_URL')
|
|
1969
|
-
* class BanLog extends WebhookLog<'memberBanned'> {
|
|
1970
|
-
* report() {
|
|
1971
|
-
* return { components: [new BanCard(this.data).component] };
|
|
1972
|
-
* }
|
|
1973
|
-
* }
|
|
1974
|
-
* ```
|
|
1975
|
-
*/
|
|
1976
|
-
function WebhookUrl(envKey) {
|
|
1977
|
-
return function(constructor) {
|
|
1978
|
-
Reflect.defineMetadata(_seedcord_core_internal.WebhookUrlMetadataKey, envKey, constructor);
|
|
1979
|
-
};
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
//#endregion
|
|
1983
|
-
//#region src/miscellaneous/flagsFor.ts
|
|
1984
|
-
/** @internal */
|
|
1985
|
-
function flagsFor(ephemeral) {
|
|
1986
|
-
let flags = discord_api_types_v10.MessageFlags.IsComponentsV2;
|
|
1987
|
-
if (ephemeral) flags |= discord_api_types_v10.MessageFlags.Ephemeral;
|
|
1988
|
-
return flags;
|
|
1989
|
-
}
|
|
1251
|
+
var CursorSource = class extends _seedcord_core.CursorSource {};
|
|
1990
1252
|
|
|
1991
1253
|
//#endregion
|
|
1992
|
-
//#region src/subscribers/
|
|
1993
|
-
const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
|
|
1994
|
-
function isDiscordWebhookUrl(value) {
|
|
1995
|
-
return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);
|
|
1996
|
-
}
|
|
1997
|
-
function jsonAttachment(name, description, data) {
|
|
1998
|
-
const content = (0, _seedcord_utils.filterCirculars)(data);
|
|
1999
|
-
return {
|
|
2000
|
-
name,
|
|
2001
|
-
description,
|
|
2002
|
-
data: Buffer.from(JSON.stringify(content, void 0, 2), "utf8")
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
function neutralizeFences(text) {
|
|
2006
|
-
return text.replaceAll("```", "```");
|
|
2007
|
-
}
|
|
2008
|
-
/** An error's stack with its direct cause appended (one level), ANSI-stripped for the webhook payload. */
|
|
2009
|
-
function errorReport(error) {
|
|
2010
|
-
let report = error.stack ?? `${error.name}: ${error.message}`;
|
|
2011
|
-
if (Error.isError(error.cause)) {
|
|
2012
|
-
const cause = error.cause.stack ?? `${error.cause.name}: ${error.cause.message}`;
|
|
2013
|
-
report += `\n\nCaused by:\n${cause}`;
|
|
2014
|
-
}
|
|
2015
|
-
const stripped = neutralizeFences((0, _seedcord_utils.stripAnsi)(report));
|
|
2016
|
-
return stripped.length > 1800 ? `${stripped.slice(0, 1799)}…` : stripped;
|
|
2017
|
-
}
|
|
2018
|
-
var WebhookSeparator = class extends _seedcord_core.BuilderComponent {
|
|
2019
|
-
constructor() {
|
|
2020
|
-
super("separator");
|
|
2021
|
-
this.instance.setSpacing(discord_api_types_v10.SeparatorSpacingSize.Small).setDivider(true);
|
|
2022
|
-
}
|
|
2023
|
-
};
|
|
2024
|
-
|
|
2025
|
-
//#endregion
|
|
2026
|
-
//#region src/subscribers/bases/WebhookSender.ts
|
|
2027
|
-
const HTTP_NOT_FOUND = 404;
|
|
2028
|
-
const HTTP_UNAUTHORIZED = 401;
|
|
1254
|
+
//#region src/subscribers/index.ts
|
|
2029
1255
|
/**
|
|
2030
|
-
*
|
|
1256
|
+
* Base class for a gateway subscriber, binding `this.core` to the gateway {@link Core}.
|
|
2031
1257
|
*
|
|
2032
|
-
* @
|
|
1258
|
+
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
2033
1259
|
*/
|
|
2034
|
-
var
|
|
2035
|
-
rest = new _discordjs_rest.REST();
|
|
2036
|
-
route;
|
|
2037
|
-
constructor(url) {
|
|
2038
|
-
const match = /\/webhooks\/(\d+)\/([\w$-]+)$/.exec(url);
|
|
2039
|
-
if (!match?.[1] || !match[2]) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigWebhookUrlInvalid, ["The url"]);
|
|
2040
|
-
this.route = discord_api_types_v10.Routes.webhook(match[1], match[2]);
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Fetches the webhook object, sending no message. Returns `missing` on a 404 or 401 from
|
|
2044
|
-
* Discord and `unreachable` on any other failure.
|
|
2045
|
-
*/
|
|
2046
|
-
async verify() {
|
|
2047
|
-
try {
|
|
2048
|
-
await this.rest.get(this.route, { auth: false });
|
|
2049
|
-
return "ok";
|
|
2050
|
-
} catch (error) {
|
|
2051
|
-
return error instanceof _discordjs_rest.DiscordAPIError && (error.status === HTTP_NOT_FOUND || error.status === HTTP_UNAUTHORIZED) ? "missing" : "unreachable";
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
async send(options) {
|
|
2055
|
-
const { flags, username, components, files } = options;
|
|
2056
|
-
await this.rest.post(this.route, {
|
|
2057
|
-
auth: false,
|
|
2058
|
-
query: new URLSearchParams({
|
|
2059
|
-
with_components: "true",
|
|
2060
|
-
wait: "true"
|
|
2061
|
-
}),
|
|
2062
|
-
body: {
|
|
2063
|
-
flags,
|
|
2064
|
-
username,
|
|
2065
|
-
components: components.map((component) => component.toJSON()),
|
|
2066
|
-
...files?.length && { attachments: files.map((file, id) => ({
|
|
2067
|
-
id,
|
|
2068
|
-
filename: file.name,
|
|
2069
|
-
description: file.description
|
|
2070
|
-
})) }
|
|
2071
|
-
},
|
|
2072
|
-
...files?.length && { files: files.map((file) => ({
|
|
2073
|
-
name: file.name,
|
|
2074
|
-
data: file.data
|
|
2075
|
-
})) }
|
|
2076
|
-
});
|
|
2077
|
-
}
|
|
2078
|
-
};
|
|
2079
|
-
|
|
2080
|
-
//#endregion
|
|
2081
|
-
//#region src/subscribers/bases/WebhookLog.ts
|
|
1260
|
+
var Subscriber = class extends _seedcord_core.Subscriber {};
|
|
2082
1261
|
/**
|
|
2083
|
-
* Base class for
|
|
2084
|
-
*
|
|
2085
|
-
* Declare the url's environment variable with `@WebhookUrl` and implement {@link report}. Url
|
|
2086
|
-
* resolution, validation, sender reuse, and sending run in the base. When the variable is unset
|
|
2087
|
-
* the reporter is skipped at registration with a boot warning.
|
|
1262
|
+
* Base class for a gateway subscriber that delivers its event to a Discord webhook, binding
|
|
1263
|
+
* `this.core` to the gateway {@link Core}.
|
|
2088
1264
|
*
|
|
2089
1265
|
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
2090
1266
|
*/
|
|
2091
|
-
var WebhookLog = class
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
components,
|
|
2105
|
-
files
|
|
2106
|
-
});
|
|
2107
|
-
} catch (error) {
|
|
2108
|
-
this.logger.error("Failed to send webhook report", error);
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
/** @internal */
|
|
2112
|
-
static senderFor(url) {
|
|
2113
|
-
let sender = WebhookLog.senders.get(url);
|
|
2114
|
-
if (!sender) {
|
|
2115
|
-
sender = new WebhookSender(url);
|
|
2116
|
-
WebhookLog.senders.set(url, sender);
|
|
2117
|
-
}
|
|
2118
|
-
return sender;
|
|
2119
|
-
}
|
|
2120
|
-
/** @internal */
|
|
2121
|
-
static envKeyOf(ctor) {
|
|
2122
|
-
const envKey = Reflect.getMetadata(_seedcord_core_internal.WebhookUrlMetadataKey, ctor);
|
|
2123
|
-
if (!envKey) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorWebhookUrlMissing, [ctor.name]);
|
|
2124
|
-
return envKey;
|
|
2125
|
-
}
|
|
2126
|
-
/** @internal Returns null when the env var is unset, throws when set but malformed. */
|
|
2127
|
-
static urlOf(envKey) {
|
|
2128
|
-
if (!envapt.Envapter.has(envKey)) return null;
|
|
2129
|
-
const raw = envapt.Envapter.getRequired(envKey, envapt.Converters.String).trim();
|
|
2130
|
-
if (raw === "") return null;
|
|
2131
|
-
if (!isDiscordWebhookUrl(raw)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigWebhookUrlInvalid, [envKey]);
|
|
2132
|
-
return raw;
|
|
2133
|
-
}
|
|
2134
|
-
};
|
|
2135
|
-
|
|
2136
|
-
//#endregion
|
|
2137
|
-
//#region src/bot/utilities/miscellaneous/contextMenuLeaves.ts
|
|
2138
|
-
/**
|
|
2139
|
-
* Collects the names of every registered context-menu command across a set of command builders, split into a
|
|
2140
|
-
* user set and a message set. The same builder may appear more than once (a guild command is pushed once per
|
|
2141
|
-
* guild), and slash commands carry no context-menu name, so both collapse here. The split mirrors the
|
|
2142
|
-
* controller's two name-keyed maps, so the boot guard checks each name against its own kind.
|
|
2143
|
-
*
|
|
2144
|
-
* @internal
|
|
2145
|
-
*/
|
|
2146
|
-
function contextMenuLeaves(commands) {
|
|
2147
|
-
const user = /* @__PURE__ */ new Set();
|
|
2148
|
-
const message = /* @__PURE__ */ new Set();
|
|
2149
|
-
for (const command of commands) {
|
|
2150
|
-
if (!(command instanceof _discordjs_builders.ContextMenuCommandBuilder)) continue;
|
|
2151
|
-
if (command.type === discord_js.ApplicationCommandType.User) user.add(command.name);
|
|
2152
|
-
else message.add(command.name);
|
|
2153
|
-
}
|
|
2154
|
-
return {
|
|
2155
|
-
user,
|
|
2156
|
-
message
|
|
2157
|
-
};
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
//#endregion
|
|
2161
|
-
//#region src/bot/utilities/miscellaneous/slashRouteLeaves.ts
|
|
2162
|
-
/**
|
|
2163
|
-
* Collects the route keys of every executable slash leaf across a set of command builders, deduplicated. The
|
|
2164
|
-
* same builder may appear more than once (a guild command is pushed once per guild), and context-menu
|
|
2165
|
-
* commands carry no slash route, so both collapse here. Route keys come from the same {@link routeLeavesOf}
|
|
2166
|
-
* that `seedcord codegen` reads, so the boot-time check compares against the generated registry's keys.
|
|
2167
|
-
*
|
|
2168
|
-
* @internal
|
|
2169
|
-
*/
|
|
2170
|
-
function slashRouteLeaves(commands) {
|
|
2171
|
-
const leaves = /* @__PURE__ */ new Set();
|
|
2172
|
-
for (const command of commands) {
|
|
2173
|
-
if (!(command instanceof _discordjs_builders.SlashCommandBuilder)) continue;
|
|
2174
|
-
for (const leaf of (0, _seedcord_utils_internal.routeLeavesOf)(command.toJSON())) leaves.add(leaf.route);
|
|
2175
|
-
}
|
|
2176
|
-
return leaves;
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
//#endregion
|
|
2180
|
-
//#region src/bot/controllers/CommandRegistry.ts
|
|
2181
|
-
/**
|
|
2182
|
-
* Manages Discord application command registration and deployment.
|
|
2183
|
-
*
|
|
2184
|
-
* Scans command directories, builds command structures, and registers both global and guild-scoped commands
|
|
2185
|
-
* to Discord's API. Accessed via `core.bot.commands`. Do not construct it directly.
|
|
2186
|
-
*/
|
|
2187
|
-
var CommandRegistry = class {
|
|
2188
|
-
core;
|
|
2189
|
-
onDeployed;
|
|
2190
|
-
logger = new _seedcord_logger.Logger("Commands");
|
|
2191
|
-
isInitialised = false;
|
|
2192
|
-
globalCommands = [];
|
|
2193
|
-
guildCommands = new discord_js.Collection();
|
|
2194
|
-
ctorToCommand = /* @__PURE__ */ new Map();
|
|
2195
|
-
loading = false;
|
|
2196
|
-
loadedCommands = [];
|
|
2197
|
-
hmrHandler;
|
|
2198
|
-
pendingEvents = /* @__PURE__ */ new Map();
|
|
2199
|
-
constructor(core, onDeployed) {
|
|
2200
|
-
this.core = core;
|
|
2201
|
-
this.onDeployed = onDeployed;
|
|
2202
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
2203
|
-
if (!commandsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
|
|
2204
|
-
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
2205
|
-
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
2206
|
-
handlersDir: commandsDir,
|
|
2207
|
-
isHandler: this.isCommandClass.bind(this),
|
|
2208
|
-
registerHandler: this.registerCommand.bind(this),
|
|
2209
|
-
unregisterHandler: this.unregisterCommand.bind(this),
|
|
2210
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
2211
|
-
logger: this.logger
|
|
2212
|
-
});
|
|
2213
|
-
}
|
|
2214
|
-
getArtifacts(ctor) {
|
|
2215
|
-
return this.ctorToCommand.get(ctor);
|
|
2216
|
-
}
|
|
2217
|
-
/** @internal */
|
|
2218
|
-
async init() {
|
|
2219
|
-
if (this.isInitialised) return;
|
|
2220
|
-
this.isInitialised = true;
|
|
2221
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
2222
|
-
if (!commandsDir) return;
|
|
2223
|
-
this.loading = true;
|
|
2224
|
-
this.loadedCommands.length = 0;
|
|
2225
|
-
try {
|
|
2226
|
-
await this.loadCommands(commandsDir);
|
|
2227
|
-
} finally {
|
|
2228
|
-
this.loading = false;
|
|
2229
|
-
}
|
|
2230
|
-
this.reportLoad();
|
|
2231
|
-
(0, _seedcord_core_internal.getDevChannel)()?.on("seedcord:refresh-commands", (data) => {
|
|
2232
|
-
this.refresh(data.shouldRefresh);
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
reportLoad() {
|
|
2236
|
-
const { utils } = this.logger;
|
|
2237
|
-
utils.summary("Loaded commands", {
|
|
2238
|
-
global: this.globalCommands.length,
|
|
2239
|
-
"guild groups": this.guildCommands.size
|
|
2240
|
-
});
|
|
2241
|
-
let slash = 0;
|
|
2242
|
-
let menu = 0;
|
|
2243
|
-
for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
|
|
2244
|
-
else menu++;
|
|
2245
|
-
utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
|
|
2246
|
-
"slash commands": slash,
|
|
2247
|
-
"context menus": menu
|
|
2248
|
-
})]);
|
|
2249
|
-
}
|
|
2250
|
-
/** @internal */
|
|
2251
|
-
async refresh(shouldRefresh = true) {
|
|
2252
|
-
if (!shouldRefresh) {
|
|
2253
|
-
this.logger.info(chalk.default.italic("Command refresh cancelled."));
|
|
2254
|
-
this.pendingEvents.clear();
|
|
2255
|
-
return;
|
|
2256
|
-
}
|
|
2257
|
-
this.logger.info(chalk.default.italic("Refreshing commands..."));
|
|
2258
|
-
for (const event of this.pendingEvents.values()) await this.hmrHandler?.handle(event);
|
|
2259
|
-
this.pendingEvents.clear();
|
|
2260
|
-
await this.setCommands();
|
|
2261
|
-
}
|
|
2262
|
-
/** @internal */
|
|
2263
|
-
async onHmr(event) {
|
|
2264
|
-
const commandsDir = this.core.config.bot.commands.path;
|
|
2265
|
-
if (commandsDir && event.file.startsWith((0, node_path.resolve)(process.cwd(), commandsDir))) {
|
|
2266
|
-
this.pendingEvents.delete(event.file);
|
|
2267
|
-
this.pendingEvents.set(event.file, event);
|
|
2268
|
-
(0, _seedcord_core_internal.getDevChannel)()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => (0, _seedcord_utils.formatFilePath)(f)) });
|
|
2269
|
-
} else await this.hmrHandler?.handle(event);
|
|
2270
|
-
}
|
|
2271
|
-
async loadCommands(dir) {
|
|
2272
|
-
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, rel, mod) => {
|
|
2273
|
-
for (const exported of Object.values(mod)) if (this.isCommandClass(exported)) {
|
|
2274
|
-
this.registerCommand(exported, rel);
|
|
2275
|
-
this.hmrHandler?.trackHandler(fullPath, exported);
|
|
2276
|
-
}
|
|
2277
|
-
}, this.logger);
|
|
2278
|
-
}
|
|
2279
|
-
isCommandClass(obj) {
|
|
2280
|
-
if (typeof obj !== "function") return false;
|
|
2281
|
-
return obj.prototype instanceof _seedcord_core.BuilderComponent && Reflect.hasMetadata(_seedcord_core_internal.CommandMetadataKey, obj);
|
|
2282
|
-
}
|
|
2283
|
-
registerCommand(ctor, rel) {
|
|
2284
|
-
const meta = Reflect.getMetadata(_seedcord_core_internal.CommandMetadataKey, ctor);
|
|
2285
|
-
if (!meta) return;
|
|
2286
|
-
const comp = new ctor().component;
|
|
2287
|
-
const kind = comp instanceof _discordjs_builders.SlashCommandBuilder ? "slash command" : "context menu";
|
|
2288
|
-
if (meta.scope === "global") this.globalCommands.push(comp);
|
|
2289
|
-
else for (const g of meta.guilds) {
|
|
2290
|
-
const arr = this.guildCommands.get(g) ?? [];
|
|
2291
|
-
arr.push(comp);
|
|
2292
|
-
this.guildCommands.set(g, arr);
|
|
2293
|
-
}
|
|
2294
|
-
this.ctorToCommand.set(ctor, {
|
|
2295
|
-
name: comp.name,
|
|
2296
|
-
scope: meta.scope,
|
|
2297
|
-
...meta.scope === "guild" && { guilds: meta.guilds }
|
|
2298
|
-
});
|
|
2299
|
-
if (this.loading) {
|
|
2300
|
-
this.loadedCommands.push({
|
|
2301
|
-
name: comp.name,
|
|
2302
|
-
from: (0, _seedcord_utils.formatFilePath)(rel),
|
|
2303
|
-
kind
|
|
2304
|
-
});
|
|
2305
|
-
return;
|
|
2306
|
-
}
|
|
2307
|
-
this.logger.utils.registration(comp.name, rel, `${meta.scope} ${kind}`);
|
|
2308
|
-
}
|
|
2309
|
-
unregisterCommand(ctor, artifacts) {
|
|
2310
|
-
const info = artifacts ?? this.ctorToCommand.get(ctor);
|
|
2311
|
-
if (!info) return;
|
|
2312
|
-
if (info.scope === "global") {
|
|
2313
|
-
const idx = this.globalCommands.findIndex((c) => c.name === info.name);
|
|
2314
|
-
if (idx !== -1) this.globalCommands.splice(idx, 1);
|
|
2315
|
-
} else for (const g of info.guilds ?? []) {
|
|
2316
|
-
const arr = this.guildCommands.get(g);
|
|
2317
|
-
if (arr) {
|
|
2318
|
-
const idx = arr.findIndex((c) => c.name === info.name);
|
|
2319
|
-
if (idx !== -1) arr.splice(idx, 1);
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
this.ctorToCommand.delete(ctor);
|
|
2323
|
-
}
|
|
2324
|
-
/** @internal */
|
|
2325
|
-
async setCommands() {
|
|
2326
|
-
const result = {
|
|
2327
|
-
global: new discord_js.Collection(),
|
|
2328
|
-
guilds: new discord_js.Collection()
|
|
2329
|
-
};
|
|
2330
|
-
if (this.globalCommands.length > 0) {
|
|
2331
|
-
const deployed = await this.core.bot.client.application?.commands.set(this.globalCommands);
|
|
2332
|
-
if (deployed) result.global = deployed;
|
|
2333
|
-
const tag = this.globalCommands.length === 1 ? "command" : "commands";
|
|
2334
|
-
this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
|
|
2335
|
-
}
|
|
2336
|
-
for (const [guildId, commands] of this.guildCommands.entries()) {
|
|
2337
|
-
const guild = this.core.bot.client.guilds.cache.get(guildId);
|
|
2338
|
-
if (!guild) {
|
|
2339
|
-
this.logger.warn(`Guild with ID ${guildId} not found, skipping command registration.`);
|
|
2340
|
-
continue;
|
|
2341
|
-
}
|
|
2342
|
-
const deployed = await guild.commands.set(commands);
|
|
2343
|
-
result.guilds.set(guildId, deployed);
|
|
2344
|
-
const tag = commands.length === 1 ? "command" : "commands";
|
|
2345
|
-
this.logger.utils.block(`Deployed ${commands.length} ${tag} to ${_seedcord_logger.paint.amber.bold(guild.name)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
|
|
2346
|
-
}
|
|
2347
|
-
this.onDeployed?.(result);
|
|
2348
|
-
return result;
|
|
2349
|
-
}
|
|
2350
|
-
/** The deduplicated slash route keys across every global and guild command. @internal */
|
|
2351
|
-
routeLeaves() {
|
|
2352
|
-
return slashRouteLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2353
|
-
}
|
|
2354
|
-
/** The registered context-menu command names, split by kind, across every global and guild command. @internal */
|
|
2355
|
-
contextMenuLeaves() {
|
|
2356
|
-
return contextMenuLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2357
|
-
}
|
|
2358
|
-
};
|
|
1267
|
+
var WebhookLog = class extends _seedcord_core.WebhookLog {};
|
|
1268
|
+
|
|
1269
|
+
//#endregion
|
|
1270
|
+
//#region src/plugin.ts
|
|
1271
|
+
/**
|
|
1272
|
+
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
1273
|
+
*
|
|
1274
|
+
* A plugin that runs on either transport extends the base from `@seedcord/core/plugin`, whose
|
|
1275
|
+
* `this.core` is typed to the shared members only.
|
|
1276
|
+
*
|
|
1277
|
+
* @typeParam Opts - The transport and runtime the plugin declares.
|
|
1278
|
+
*/
|
|
1279
|
+
var Plugin = class extends _seedcord_core_plugin.Plugin {};
|
|
2359
1280
|
|
|
2360
1281
|
//#endregion
|
|
2361
1282
|
//#region src/miscellaneous/deriveEventActor.ts
|
|
@@ -2434,9 +1355,157 @@ function eventGateContext(eventName, args, core) {
|
|
|
2434
1355
|
};
|
|
2435
1356
|
}
|
|
2436
1357
|
|
|
1358
|
+
//#endregion
|
|
1359
|
+
//#region src/miscellaneous/extractErrorResponse.ts
|
|
1360
|
+
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling", { channel: "errors" });
|
|
1361
|
+
/**
|
|
1362
|
+
* Processes an error into a user-facing reply and routes its report.
|
|
1363
|
+
*
|
|
1364
|
+
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
1365
|
+
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
1366
|
+
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
1367
|
+
* threaded into both the reply and the bus payload.
|
|
1368
|
+
*/
|
|
1369
|
+
function extractErrorResponse(error, core, origin) {
|
|
1370
|
+
const uuid = node_crypto.randomUUID();
|
|
1371
|
+
const developerUsername = core.config.notifications?.developerUsername;
|
|
1372
|
+
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
1373
|
+
uuid,
|
|
1374
|
+
developerUsername
|
|
1375
|
+
};
|
|
1376
|
+
if (error instanceof _seedcord_core.Notice) {
|
|
1377
|
+
if (error.report) reportFault(error, core, origin, uuid);
|
|
1378
|
+
return {
|
|
1379
|
+
uuid,
|
|
1380
|
+
response: error.render(ctx)
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
reportRawFault(error, core, origin, uuid);
|
|
1384
|
+
const override = core.config.errors?.defaultError;
|
|
1385
|
+
return {
|
|
1386
|
+
uuid,
|
|
1387
|
+
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
function withThrottle(core, origin, error, publish) {
|
|
1391
|
+
const throttle = _seedcord_core_internal.FaultThrottle.for(core);
|
|
1392
|
+
const key = faultKey(origin, error);
|
|
1393
|
+
if (!throttle.shouldReport(key)) {
|
|
1394
|
+
logger$2.debug(`throttled duplicate fault ${key}`);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
publish();
|
|
1398
|
+
throttle.markReported(key);
|
|
1399
|
+
}
|
|
1400
|
+
function reportFault(denial, core, origin, uuid) {
|
|
1401
|
+
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
1402
|
+
withThrottle(core, origin, denial, () => {
|
|
1403
|
+
if (origin.interaction) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
1404
|
+
denial,
|
|
1405
|
+
uuid,
|
|
1406
|
+
source: buildInteractionSource(origin.interaction)
|
|
1407
|
+
});
|
|
1408
|
+
else if (origin.event) core.bus[_seedcord_core_internal.PublishDefault]("handledException", {
|
|
1409
|
+
denial,
|
|
1410
|
+
uuid,
|
|
1411
|
+
source: buildEventSource(origin.event, origin)
|
|
1412
|
+
});
|
|
1413
|
+
else core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
1414
|
+
uuid,
|
|
1415
|
+
error: denial,
|
|
1416
|
+
...scalarActors(origin),
|
|
1417
|
+
metadata: metadataFor(origin)
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
function causeLine(error) {
|
|
1422
|
+
const { cause } = error;
|
|
1423
|
+
if (!Error.isError(cause)) return "";
|
|
1424
|
+
const [first] = cause.message.split("\n");
|
|
1425
|
+
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
1426
|
+
}
|
|
1427
|
+
function reportRawFault(error, core, origin, uuid) {
|
|
1428
|
+
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
1429
|
+
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
1430
|
+
withThrottle(core, origin, error, () => {
|
|
1431
|
+
core.bus[_seedcord_core_internal.PublishDefault]("unknownException", {
|
|
1432
|
+
uuid,
|
|
1433
|
+
error,
|
|
1434
|
+
...scalarActors(origin),
|
|
1435
|
+
metadata: metadataFor(origin)
|
|
1436
|
+
});
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
function scalarActors(origin) {
|
|
1440
|
+
return {
|
|
1441
|
+
guild: origin.guild ? {
|
|
1442
|
+
id: origin.guild.id,
|
|
1443
|
+
name: origin.guild.name
|
|
1444
|
+
} : void 0,
|
|
1445
|
+
user: origin.user ? {
|
|
1446
|
+
id: origin.user.id,
|
|
1447
|
+
username: origin.user.username
|
|
1448
|
+
} : void 0
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
function metadataFor(origin) {
|
|
1452
|
+
if (origin.event) return {
|
|
1453
|
+
eventName: origin.event.name,
|
|
1454
|
+
handler: origin.event.handler,
|
|
1455
|
+
args: origin.event.args
|
|
1456
|
+
};
|
|
1457
|
+
return origin.metadata;
|
|
1458
|
+
}
|
|
1459
|
+
function buildEventSource(event, origin) {
|
|
1460
|
+
return {
|
|
1461
|
+
kind: "event",
|
|
1462
|
+
eventName: event.name,
|
|
1463
|
+
handler: event.handler,
|
|
1464
|
+
userId: origin.user?.id ?? null,
|
|
1465
|
+
guildId: origin.guild?.id ?? null,
|
|
1466
|
+
channelId: event.channelId,
|
|
1467
|
+
raw: event.args
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function faultKey(origin, error) {
|
|
1471
|
+
const name = error instanceof _seedcord_core.Notice ? error.name : error instanceof discord_js.DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1472
|
+
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1473
|
+
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1474
|
+
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1475
|
+
return `autocomplete:${name}`;
|
|
1476
|
+
}
|
|
1477
|
+
function interactionRoute(interaction) {
|
|
1478
|
+
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1479
|
+
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1480
|
+
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
1481
|
+
return "interaction";
|
|
1482
|
+
}
|
|
1483
|
+
function buildInteractionSource(interaction) {
|
|
1484
|
+
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1485
|
+
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1486
|
+
return {
|
|
1487
|
+
kind: "interaction",
|
|
1488
|
+
interactionKind: interactionKind(interaction),
|
|
1489
|
+
command,
|
|
1490
|
+
customId,
|
|
1491
|
+
userId: interaction.user.id,
|
|
1492
|
+
guildId: interaction.guildId,
|
|
1493
|
+
channelId: interaction.channelId,
|
|
1494
|
+
interactionId: interaction.id,
|
|
1495
|
+
raw: interaction
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
function interactionKind(interaction) {
|
|
1499
|
+
if (interaction.isChatInputCommand()) return "slash";
|
|
1500
|
+
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1501
|
+
if (interaction.isButton()) return "button";
|
|
1502
|
+
if (interaction.isAnySelectMenu()) return "select";
|
|
1503
|
+
return "modal";
|
|
1504
|
+
}
|
|
1505
|
+
|
|
2437
1506
|
//#endregion
|
|
2438
1507
|
//#region src/bot/handleEventFault.ts
|
|
2439
|
-
const logger$1 = new _seedcord_logger.Logger("EventBoundary");
|
|
1508
|
+
const logger$1 = new _seedcord_logger.Logger("EventBoundary", { channel: "errors" });
|
|
2440
1509
|
/**
|
|
2441
1510
|
* The event controller boundary. Reports a throw from an event handler and never replies, since a
|
|
2442
1511
|
* generic event has no reply target. A {@link Silence} or a non-reporting {@link Notice} stops quietly, a
|
|
@@ -2449,15 +1518,15 @@ function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
|
2449
1518
|
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger$1.debug(`Silence: ${caught.reason}`);
|
|
2450
1519
|
return;
|
|
2451
1520
|
}
|
|
2452
|
-
|
|
1521
|
+
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
2453
1522
|
const ignore = new Set(core.config.errors?.ignoreEventApiCodes ?? []);
|
|
2454
|
-
if (
|
|
2455
|
-
logger$1.debug(`swallowed api code ${
|
|
1523
|
+
if (error instanceof discord_js.DiscordAPIError && ignore.has(error.code)) {
|
|
1524
|
+
logger$1.debug(`swallowed api code ${error.code}`);
|
|
2456
1525
|
return;
|
|
2457
1526
|
}
|
|
2458
|
-
if (
|
|
1527
|
+
if (error instanceof _seedcord_core.Notice && !error.report) return;
|
|
2459
1528
|
const actor = deriveEventActor(args);
|
|
2460
|
-
extractErrorResponse(
|
|
1529
|
+
extractErrorResponse(error, core, {
|
|
2461
1530
|
event: {
|
|
2462
1531
|
name: eventName,
|
|
2463
1532
|
handler: handlerName,
|
|
@@ -2472,15 +1541,15 @@ function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
|
2472
1541
|
//#endregion
|
|
2473
1542
|
//#region src/bot/controllers/EventDispatcher.ts
|
|
2474
1543
|
/**
|
|
2475
|
-
* Scans event handler directories,
|
|
2476
|
-
*
|
|
2477
|
-
*
|
|
1544
|
+
* Scans event handler directories, attaches one client listener per registered event, and runs the
|
|
1545
|
+
* handlers for each fire. Interactions route through InteractionDispatcher. Multiple handlers can
|
|
1546
|
+
* point to one event.
|
|
2478
1547
|
*
|
|
2479
1548
|
* @internal
|
|
2480
1549
|
*/
|
|
2481
1550
|
var EventDispatcher = class {
|
|
2482
1551
|
core;
|
|
2483
|
-
logger = new _seedcord_logger.Logger("Events");
|
|
1552
|
+
logger = new _seedcord_logger.Logger("Events", { channel: "events" });
|
|
2484
1553
|
isInitialized = false;
|
|
2485
1554
|
eventMap = /* @__PURE__ */ new Map();
|
|
2486
1555
|
middlewares = [];
|
|
@@ -2551,7 +1620,7 @@ var EventDispatcher = class {
|
|
|
2551
1620
|
this.registerHandler(val, relativePath);
|
|
2552
1621
|
this.hmrHandler?.trackHandler(fullPath, val);
|
|
2553
1622
|
}
|
|
2554
|
-
}
|
|
1623
|
+
});
|
|
2555
1624
|
}
|
|
2556
1625
|
async loadMiddlewares(dir) {
|
|
2557
1626
|
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
@@ -2560,7 +1629,7 @@ var EventDispatcher = class {
|
|
|
2560
1629
|
this.registerMiddleware(val, relativePath);
|
|
2561
1630
|
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
2562
1631
|
}
|
|
2563
|
-
}
|
|
1632
|
+
});
|
|
2564
1633
|
}
|
|
2565
1634
|
/** @internal For use in dev mode */
|
|
2566
1635
|
async onHmr(event) {
|
|
@@ -2657,10 +1726,14 @@ var EventDispatcher = class {
|
|
|
2657
1726
|
this.logger.debug(`Attaching ${_seedcord_logger.paint.mint.bold(eventName)} to the client with ${_seedcord_logger.paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
|
|
2658
1727
|
this.core.bot.client.on(eventName, (...args) => {
|
|
2659
1728
|
if (this.draining) return;
|
|
2660
|
-
this.core.
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
1729
|
+
this.core.bus[_seedcord_core_internal.PublishDefault]("anyEvent", {
|
|
1730
|
+
name: eventName,
|
|
1731
|
+
args
|
|
1732
|
+
});
|
|
1733
|
+
const run = this.processEvent(eventName, args).catch((caught) => {
|
|
1734
|
+
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
1735
|
+
this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
1736
|
+
this.core.bus[_seedcord_core_internal.PublishDefault]("unhandledEventError", { error });
|
|
2664
1737
|
});
|
|
2665
1738
|
this.inFlight.add(run);
|
|
2666
1739
|
run.finally(() => this.inFlight.delete(run));
|
|
@@ -2736,7 +1809,7 @@ const HARMLESS_API_CODES = new Set([
|
|
|
2736
1809
|
|
|
2737
1810
|
//#endregion
|
|
2738
1811
|
//#region src/bot/handleInteractionFault.ts
|
|
2739
|
-
const logger = new _seedcord_logger.Logger("InteractionBoundary");
|
|
1812
|
+
const logger = new _seedcord_logger.Logger("InteractionBoundary", { channel: "errors" });
|
|
2740
1813
|
/**
|
|
2741
1814
|
* The interaction controller boundary. Sorts a throw from any handler-lifecycle phase into the right
|
|
2742
1815
|
* user reply plus the right bus event. When a handler is built its sender is passed in, so the boundary
|
|
@@ -2745,33 +1818,45 @@ const logger = new _seedcord_logger.Logger("InteractionBoundary");
|
|
|
2745
1818
|
*
|
|
2746
1819
|
* @internal
|
|
2747
1820
|
*/
|
|
2748
|
-
async function handleInteractionFault(caught, interaction, core, sender) {
|
|
1821
|
+
async function handleInteractionFault(caught, interaction, core, routeId, sender) {
|
|
2749
1822
|
if (caught instanceof _seedcord_core.Silence) {
|
|
2750
1823
|
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
|
|
2751
1824
|
return;
|
|
2752
1825
|
}
|
|
2753
|
-
|
|
1826
|
+
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
2754
1827
|
const ignore = new Set(core.config.errors?.ignoreApiCodes ?? []);
|
|
2755
|
-
if (
|
|
2756
|
-
logger.debug(`swallowed api code ${
|
|
1828
|
+
if (error instanceof discord_js.DiscordAPIError && ignore.has(error.code)) {
|
|
1829
|
+
logger.debug(`swallowed api code ${error.code}`);
|
|
2757
1830
|
return;
|
|
2758
1831
|
}
|
|
2759
1832
|
if (interaction.isAutocomplete()) {
|
|
2760
|
-
extractErrorResponse(
|
|
1833
|
+
extractErrorResponse(error, core, {
|
|
2761
1834
|
guild: interaction.guild,
|
|
2762
1835
|
user: interaction.user,
|
|
2763
1836
|
metadata: interaction,
|
|
2764
1837
|
route: slashRouteOf(interaction)
|
|
2765
1838
|
});
|
|
1839
|
+
await sendEmptyChoices(interaction, core, routeId);
|
|
2766
1840
|
return;
|
|
2767
1841
|
}
|
|
2768
|
-
const { response } = extractErrorResponse(
|
|
1842
|
+
const { response } = extractErrorResponse(error, core, {
|
|
2769
1843
|
interaction,
|
|
2770
1844
|
guild: interaction.guild,
|
|
2771
1845
|
user: interaction.user,
|
|
2772
1846
|
metadata: interaction
|
|
2773
1847
|
});
|
|
2774
|
-
await sendGuarded(sender ?? new ReplySender(interaction,
|
|
1848
|
+
await sendGuarded(sender ?? new ReplySender(interaction, routeId, core.bus), response, error instanceof _seedcord_core.Notice ? error.ephemeral : true);
|
|
1849
|
+
}
|
|
1850
|
+
async function sendEmptyChoices(interaction, core, routeId) {
|
|
1851
|
+
const telemetry = {
|
|
1852
|
+
bus: core.bus,
|
|
1853
|
+
interactionId: interaction.id
|
|
1854
|
+
};
|
|
1855
|
+
try {
|
|
1856
|
+
await (0, _seedcord_core_internal.reportedWrite)(telemetry, routeId, "respond", () => interaction.respond([]));
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
logger.debug(`autocomplete empty-choices send failed: ${String(error)}`);
|
|
1859
|
+
}
|
|
2775
1860
|
}
|
|
2776
1861
|
async function sendGuarded(sender, response, ephemeral) {
|
|
2777
1862
|
try {
|
|
@@ -2795,7 +1880,7 @@ async function sendGuarded(sender, response, ephemeral) {
|
|
|
2795
1880
|
*/
|
|
2796
1881
|
var InteractionDispatcher = class {
|
|
2797
1882
|
core;
|
|
2798
|
-
logger = new _seedcord_logger.Logger("Interactions");
|
|
1883
|
+
logger = new _seedcord_logger.Logger("Interactions", { channel: "interactions" });
|
|
2799
1884
|
isInitialized = false;
|
|
2800
1885
|
slashMap = /* @__PURE__ */ new Map();
|
|
2801
1886
|
buttonMap = /* @__PURE__ */ new Map();
|
|
@@ -2808,6 +1893,7 @@ var InteractionDispatcher = class {
|
|
|
2808
1893
|
messageContextMenuMap = /* @__PURE__ */ new Map();
|
|
2809
1894
|
userContextMenuMap = /* @__PURE__ */ new Map();
|
|
2810
1895
|
autocompleteMap = /* @__PURE__ */ new Map();
|
|
1896
|
+
handlerFiles = /* @__PURE__ */ new Map();
|
|
2811
1897
|
keysToIgnore = /* @__PURE__ */ new Set();
|
|
2812
1898
|
middlewares = [];
|
|
2813
1899
|
inFlight = /* @__PURE__ */ new Set();
|
|
@@ -2850,21 +1936,9 @@ var InteractionDispatcher = class {
|
|
|
2850
1936
|
logger: this.logger
|
|
2851
1937
|
});
|
|
2852
1938
|
}
|
|
2853
|
-
/**
|
|
2854
|
-
* Warns for each command route with no registered `@SlashRoute` handler, which would fall through to
|
|
2855
|
-
* UnhandledRepliable at runtime. A bot may route commands outside the registry, so this does not throw.
|
|
2856
|
-
*
|
|
2857
|
-
* @internal
|
|
2858
|
-
*/
|
|
2859
1939
|
warnUnhandledRoutes(commandLeaves) {
|
|
2860
1940
|
for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${_seedcord_logger.paint.sky.bold(route)} has no registered ${chalk.default.bold("@SlashRoute")} handler.`);
|
|
2861
1941
|
}
|
|
2862
|
-
/**
|
|
2863
|
-
* Warns for each context-menu command with no matching handler, which would fall through to UnhandledRepliable
|
|
2864
|
-
* at runtime. Checked per kind since a user and a message command can share a name.
|
|
2865
|
-
*
|
|
2866
|
-
* @internal
|
|
2867
|
-
*/
|
|
2868
1942
|
warnUnhandledContextMenuRoutes(leaves) {
|
|
2869
1943
|
for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
2870
1944
|
for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
@@ -2926,7 +2000,7 @@ var InteractionDispatcher = class {
|
|
|
2926
2000
|
this.registerHandler(val, relativePath);
|
|
2927
2001
|
this.hmrHandler?.trackHandler(fullPath, val);
|
|
2928
2002
|
}
|
|
2929
|
-
}
|
|
2003
|
+
});
|
|
2930
2004
|
}
|
|
2931
2005
|
async loadMiddlewares(dir) {
|
|
2932
2006
|
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
@@ -2935,7 +2009,7 @@ var InteractionDispatcher = class {
|
|
|
2935
2009
|
this.registerMiddleware(val, relativePath);
|
|
2936
2010
|
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
2937
2011
|
}
|
|
2938
|
-
}
|
|
2012
|
+
});
|
|
2939
2013
|
}
|
|
2940
2014
|
registerMiddleware(middlewareCtor, relativePath) {
|
|
2941
2015
|
const metadata = Reflect.getMetadata(_seedcord_core_internal.MiddlewareMetadataKey, middlewareCtor);
|
|
@@ -2965,6 +2039,7 @@ var InteractionDispatcher = class {
|
|
|
2965
2039
|
return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(_seedcord_core_internal.MiddlewareMetadataKey, obj);
|
|
2966
2040
|
}
|
|
2967
2041
|
registerHandler(handlerClass, relativePath) {
|
|
2042
|
+
const from = (0, _seedcord_utils.formatFilePath)(relativePath);
|
|
2968
2043
|
const writes = [];
|
|
2969
2044
|
for (const [routeType, map] of this.routeTypes) {
|
|
2970
2045
|
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
@@ -2972,16 +2047,16 @@ var InteractionDispatcher = class {
|
|
|
2972
2047
|
for (const route of meta) {
|
|
2973
2048
|
const existing = map.get(route);
|
|
2974
2049
|
if (existing && existing !== handlerClass) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
2975
|
-
route
|
|
2976
|
-
existing.name
|
|
2977
|
-
handlerClass.name
|
|
2050
|
+
`${routeType}:${route}`,
|
|
2051
|
+
`${existing.name} (${this.handlerFiles.get(existing) ?? "unknown file"})`,
|
|
2052
|
+
`${handlerClass.name} (${from})`
|
|
2978
2053
|
]);
|
|
2979
2054
|
writes.push([map, route]);
|
|
2980
2055
|
}
|
|
2981
2056
|
}
|
|
2982
2057
|
if (writes.length === 0) return;
|
|
2983
2058
|
for (const [map, route] of writes) map.set(route, handlerClass);
|
|
2984
|
-
|
|
2059
|
+
this.handlerFiles.set(handlerClass, from);
|
|
2985
2060
|
if (this.loading) this.loadedHandlers.push({
|
|
2986
2061
|
name: handlerClass.name,
|
|
2987
2062
|
from
|
|
@@ -2993,6 +2068,7 @@ var InteractionDispatcher = class {
|
|
|
2993
2068
|
await this.hmrHandler?.handle(event);
|
|
2994
2069
|
}
|
|
2995
2070
|
unregisterHandler(handlerClass, artifacts) {
|
|
2071
|
+
this.handlerFiles.delete(handlerClass);
|
|
2996
2072
|
if (artifacts) {
|
|
2997
2073
|
for (const { routeType, routes } of artifacts) {
|
|
2998
2074
|
const map = this.routeTypes.find(([type]) => type === routeType)?.[1];
|
|
@@ -3013,10 +2089,11 @@ var InteractionDispatcher = class {
|
|
|
3013
2089
|
attachToClient() {
|
|
3014
2090
|
this.core.bot.client.on(discord_js.Events.InteractionCreate, (interaction) => {
|
|
3015
2091
|
if (this.draining) return;
|
|
3016
|
-
this.core.
|
|
3017
|
-
const run = this.handleInteraction(interaction).catch((
|
|
3018
|
-
|
|
3019
|
-
this.
|
|
2092
|
+
this.core.bus[_seedcord_core_internal.PublishDefault]("anyInteraction", { interaction });
|
|
2093
|
+
const run = this.handleInteraction(interaction).catch((caught) => {
|
|
2094
|
+
const error = (0, _seedcord_core_internal.asError)(caught);
|
|
2095
|
+
this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${error.name}`, error.stack);
|
|
2096
|
+
this.core.bus[_seedcord_core_internal.PublishDefault]("unhandledInteractionError", { error });
|
|
3020
2097
|
});
|
|
3021
2098
|
this.inFlight.add(run);
|
|
3022
2099
|
run.finally(() => this.inFlight.delete(run));
|
|
@@ -3028,37 +2105,75 @@ var InteractionDispatcher = class {
|
|
|
3028
2105
|
drain(timeoutMs) {
|
|
3029
2106
|
return (0, _seedcord_core_node_internal.settleWithin)(Promise.allSettled(this.inFlight), timeoutMs);
|
|
3030
2107
|
}
|
|
3031
|
-
async handleCustomIdInteraction(interaction,
|
|
2108
|
+
async handleCustomIdInteraction(interaction, kind, getMap) {
|
|
3032
2109
|
if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
|
|
3033
2110
|
const prefix = (0, _seedcord_core_internal.prefixOf)(interaction.customId);
|
|
3034
|
-
if (!prefix)
|
|
3035
|
-
await this.processInteraction(interaction, () => prefix, (key) => getMap().get(key));
|
|
2111
|
+
if (!prefix) this.logger.warn(`${_seedcord_logger.paint.sky.bold(kind)} has invalid customId: ${_seedcord_logger.paint.mute(interaction.customId)}`);
|
|
2112
|
+
await this.processInteraction(interaction, kind, () => prefix, (key) => getMap().get(key));
|
|
3036
2113
|
}
|
|
3037
|
-
async processInteraction(interaction, extractKey, getHandler, fallback = UnhandledRepliable) {
|
|
2114
|
+
async processInteraction(interaction, kind, extractKey, getHandler, fallback = UnhandledRepliable) {
|
|
3038
2115
|
const key = extractKey(interaction);
|
|
2116
|
+
const startedAt = performance.now();
|
|
2117
|
+
const queuedMs = (0, _seedcord_core_internal.queuedMsFor)(interaction.id);
|
|
2118
|
+
const matched = getHandler(key);
|
|
2119
|
+
let routeId = `${kind}:${key || "unrouted"}`;
|
|
2120
|
+
let reported = false;
|
|
2121
|
+
const report = (outcome) => {
|
|
2122
|
+
if (reported) return;
|
|
2123
|
+
reported = true;
|
|
2124
|
+
this.core.bus[_seedcord_core_internal.PublishDefault]("interactionDispatched", (0, _seedcord_core_internal.dispatchedPayload)({
|
|
2125
|
+
routeId,
|
|
2126
|
+
interactionId: interaction.id,
|
|
2127
|
+
kind,
|
|
2128
|
+
outcome,
|
|
2129
|
+
startedAt,
|
|
2130
|
+
fallback: !matched,
|
|
2131
|
+
queuedMs
|
|
2132
|
+
}));
|
|
2133
|
+
};
|
|
3039
2134
|
let sender;
|
|
3040
2135
|
try {
|
|
3041
|
-
if (!interaction.isAutocomplete())
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
}
|
|
3047
|
-
this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(key)} with ${_seedcord_logger.paint.mute(HandlerCtor.name)}`);
|
|
3048
|
-
const dispatch = new _seedcord_core.DispatchContext((0, _seedcord_core_internal.routeIdOf)(HandlerCtor));
|
|
3049
|
-
const handler = new HandlerCtor(interaction, this.core, dispatch);
|
|
2136
|
+
if (!interaction.isAutocomplete()) await this.runMiddlewares(interaction);
|
|
2137
|
+
const HandlerCtor = matched ?? fallback;
|
|
2138
|
+
const dispatch = new _seedcord_core.DispatchContext((0, _seedcord_core_internal.routeIdOf)(HandlerCtor) ?? routeId);
|
|
2139
|
+
routeId = dispatch.routeId ?? routeId;
|
|
2140
|
+
const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
|
|
3050
2141
|
if (handler instanceof RepliableHandler) sender = handler.getSender();
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
} finally {
|
|
3056
|
-
monitor?.report(dispatch.routeId);
|
|
3057
|
-
}
|
|
2142
|
+
const refusal = interaction.isAutocomplete() ? null : await this.gateRefusal(HandlerCtor, interaction, dispatch.routeId);
|
|
2143
|
+
if (refusal) {
|
|
2144
|
+
await this.answer(refusal.caught, interaction, routeId, sender, report);
|
|
2145
|
+
return;
|
|
3058
2146
|
}
|
|
3059
2147
|
await handler.execute();
|
|
2148
|
+
report("handled");
|
|
2149
|
+
} catch (caught) {
|
|
2150
|
+
await this.answer(caught, interaction, routeId, sender, report);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
async answer(caught, interaction, routeId, sender, report) {
|
|
2154
|
+
try {
|
|
2155
|
+
await handleInteractionFault(caught, interaction, this.core, routeId, sender);
|
|
2156
|
+
} finally {
|
|
2157
|
+
report((0, _seedcord_core_internal.outcomeFor)(caught));
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
|
|
2161
|
+
if (isFallback) this.logger.warn(`No handler found for key ${_seedcord_logger.paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
|
|
2162
|
+
this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(key)} with ${_seedcord_logger.paint.mute(HandlerCtor.name)}`);
|
|
2163
|
+
return new HandlerCtor(interaction, this.core, dispatch);
|
|
2164
|
+
}
|
|
2165
|
+
async runMiddlewares(interaction) {
|
|
2166
|
+
for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
|
|
2167
|
+
}
|
|
2168
|
+
async gateRefusal(HandlerCtor, interaction, routeId) {
|
|
2169
|
+
const monitor = (0, _seedcord_core_internal.slowGateMonitor)();
|
|
2170
|
+
try {
|
|
2171
|
+
await (0, _seedcord_core_internal.runHandlerGates)(HandlerCtor, interactionGateContext(interaction, this.core), routeId ?? void 0, monitor?.observe);
|
|
2172
|
+
return null;
|
|
3060
2173
|
} catch (caught) {
|
|
3061
|
-
|
|
2174
|
+
return { caught };
|
|
2175
|
+
} finally {
|
|
2176
|
+
monitor?.report(routeId);
|
|
3062
2177
|
}
|
|
3063
2178
|
}
|
|
3064
2179
|
async handleInteraction(interaction) {
|
|
@@ -3103,47 +2218,41 @@ var InteractionDispatcher = class {
|
|
|
3103
2218
|
}
|
|
3104
2219
|
async handleSlashCommand(interaction) {
|
|
3105
2220
|
const route = slashRouteOf(interaction);
|
|
3106
|
-
await this.processInteraction(interaction, () => route, (key) => this.slashMap.get(key));
|
|
2221
|
+
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Slash, () => route, (key) => this.slashMap.get(key));
|
|
3107
2222
|
}
|
|
3108
2223
|
async handleButton(interaction) {
|
|
3109
|
-
await this.handleCustomIdInteraction(interaction, () => this.buttonMap
|
|
2224
|
+
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Button, () => this.buttonMap);
|
|
3110
2225
|
}
|
|
3111
2226
|
async handleModal(interaction) {
|
|
3112
|
-
await this.handleCustomIdInteraction(interaction, () => this.modalMap
|
|
2227
|
+
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Modal, () => this.modalMap);
|
|
3113
2228
|
}
|
|
3114
2229
|
async handleStringSelectMenu(interaction) {
|
|
3115
|
-
await this.handleCustomIdInteraction(interaction, () => this.stringSelectMap
|
|
2230
|
+
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.StringMenu, () => this.stringSelectMap);
|
|
3116
2231
|
}
|
|
3117
2232
|
async handleUserSelectMenu(interaction) {
|
|
3118
|
-
await this.handleCustomIdInteraction(interaction, () => this.userSelectMap
|
|
2233
|
+
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.UserMenu, () => this.userSelectMap);
|
|
3119
2234
|
}
|
|
3120
2235
|
async handleRoleSelectMenu(interaction) {
|
|
3121
|
-
await this.handleCustomIdInteraction(interaction, () => this.roleSelectMap
|
|
2236
|
+
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.RoleMenu, () => this.roleSelectMap);
|
|
3122
2237
|
}
|
|
3123
2238
|
async handleChannelSelectMenu(interaction) {
|
|
3124
|
-
await this.handleCustomIdInteraction(interaction, () => this.channelSelectMap
|
|
2239
|
+
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.ChannelMenu, () => this.channelSelectMap);
|
|
3125
2240
|
}
|
|
3126
2241
|
async handleMentionableSelectMenu(interaction) {
|
|
3127
|
-
await this.handleCustomIdInteraction(interaction, () => this.mentionableSelectMap
|
|
2242
|
+
await this.handleCustomIdInteraction(interaction, _seedcord_core_internal.InteractionRoutes.MentionableMenu, () => this.mentionableSelectMap);
|
|
3128
2243
|
}
|
|
3129
2244
|
async handleMessageContextMenu(interaction) {
|
|
3130
|
-
await this.processInteraction(interaction, () => interaction.commandName, (key) => this.messageContextMenuMap.get(key));
|
|
2245
|
+
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.MessageContextMenu, () => interaction.commandName, (key) => this.messageContextMenuMap.get(key));
|
|
3131
2246
|
}
|
|
3132
2247
|
async handleUserContextMenu(interaction) {
|
|
3133
|
-
await this.processInteraction(interaction, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
|
|
2248
|
+
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.UserContextMenu, () => interaction.commandName, (key) => this.userContextMenuMap.get(key));
|
|
3134
2249
|
}
|
|
3135
2250
|
async handleAutocomplete(interaction) {
|
|
3136
2251
|
const route = slashRouteOf(interaction);
|
|
3137
|
-
await this.processInteraction(interaction, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
|
|
2252
|
+
await this.processInteraction(interaction, _seedcord_core_internal.InteractionRoutes.Autocomplete, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
|
|
3138
2253
|
}
|
|
3139
2254
|
};
|
|
3140
2255
|
|
|
3141
|
-
//#endregion
|
|
3142
|
-
//#region \0@oxc-project+runtime@0.135.0/helpers/esm/decorateMetadata.js
|
|
3143
|
-
function __decorateMetadata(k, v) {
|
|
3144
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
3145
|
-
}
|
|
3146
|
-
|
|
3147
2256
|
//#endregion
|
|
3148
2257
|
//#region \0@oxc-project+runtime@0.135.0/helpers/esm/decorate.js
|
|
3149
2258
|
function __decorate(decorators, target, key, desc) {
|
|
@@ -3162,38 +2271,47 @@ const LOGOUT_TIMEOUT_MS = 2e3;
|
|
|
3162
2271
|
/**
|
|
3163
2272
|
* The Discord client and its controllers. Access it through `core.bot`.
|
|
3164
2273
|
*/
|
|
3165
|
-
var Bot = class
|
|
2274
|
+
var Bot = class {
|
|
3166
2275
|
/** @internal */
|
|
3167
|
-
logger = new _seedcord_logger.Logger("Bot");
|
|
2276
|
+
logger = new _seedcord_logger.Logger("Bot", { channel: "bot" });
|
|
3168
2277
|
isInitialized = false;
|
|
3169
2278
|
_client;
|
|
3170
2279
|
interactions;
|
|
3171
2280
|
events;
|
|
3172
2281
|
emojiInjector;
|
|
3173
|
-
|
|
3174
|
-
emojis = Emojis;
|
|
3175
|
-
mentions = CommandMentions;
|
|
2282
|
+
commandRegistry;
|
|
3176
2283
|
/** @internal */
|
|
3177
2284
|
async onHmr(event) {
|
|
3178
2285
|
if (this.interactions) await this.interactions.onHmr(event);
|
|
3179
2286
|
if (this.events) await this.events.onHmr(event);
|
|
3180
|
-
if (this.
|
|
2287
|
+
if (this.commandRegistry) await this.commandRegistry.onHmr(event);
|
|
3181
2288
|
}
|
|
3182
2289
|
/** @internal */
|
|
3183
2290
|
constructor(core) {
|
|
3184
|
-
super();
|
|
3185
2291
|
this._client = new discord_js.Client(core.config.bot.clientOptions);
|
|
3186
2292
|
if (core.config.bot.interactions.path) this.interactions = new InteractionDispatcher(core);
|
|
3187
2293
|
if (core.config.bot.events.path) this.events = new EventDispatcher(core);
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
2294
|
+
const commandsDir = core.config.bot.commands.path;
|
|
2295
|
+
if (commandsDir) {
|
|
2296
|
+
const injector = new _seedcord_core_internal.CommandInjector();
|
|
2297
|
+
const registry = new _seedcord_core_node_internal.CommandRegistry({
|
|
2298
|
+
dir: commandsDir,
|
|
2299
|
+
rest: this._client.rest,
|
|
2300
|
+
applicationId: () => this.applicationId(),
|
|
2301
|
+
onDeployed: (result) => {
|
|
2302
|
+
injector.inject(result, registry.allCommands());
|
|
2303
|
+
}
|
|
3192
2304
|
});
|
|
2305
|
+
this.commandRegistry = registry;
|
|
3193
2306
|
}
|
|
3194
2307
|
this.emojiInjector = new EmojiInjector(core);
|
|
3195
2308
|
this.registerShutdownTasks(core);
|
|
3196
2309
|
}
|
|
2310
|
+
applicationId() {
|
|
2311
|
+
const { application } = this._client;
|
|
2312
|
+
if (!application) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreApplicationUnavailable);
|
|
2313
|
+
return application.id;
|
|
2314
|
+
}
|
|
3197
2315
|
registerShutdownTasks(core) {
|
|
3198
2316
|
core.shutdown.addTask(_seedcord_core_node_internal.ShutdownPhase.Unbind, "stop-dispatch", () => {
|
|
3199
2317
|
this.stopAccepting();
|
|
@@ -3218,11 +2336,11 @@ var Bot = class extends _seedcord_event_emitter.TypedEventEmitter {
|
|
|
3218
2336
|
if (this.events) await this.events.init();
|
|
3219
2337
|
await this.login(token);
|
|
3220
2338
|
await this.emojiInjector.init();
|
|
3221
|
-
if (this.
|
|
3222
|
-
await this.
|
|
3223
|
-
await this.
|
|
3224
|
-
this.interactions?.warnUnhandledRoutes(this.
|
|
3225
|
-
this.interactions?.warnUnhandledContextMenuRoutes(this.
|
|
2339
|
+
if (this.commandRegistry) {
|
|
2340
|
+
await this.commandRegistry.init();
|
|
2341
|
+
await this.commandRegistry.setCommands();
|
|
2342
|
+
this.interactions?.warnUnhandledRoutes(this.commandRegistry.routeLeaves());
|
|
2343
|
+
this.interactions?.warnUnhandledContextMenuRoutes(this.commandRegistry.contextMenuLeaves());
|
|
3226
2344
|
}
|
|
3227
2345
|
}
|
|
3228
2346
|
/** @internal */
|
|
@@ -3231,9 +2349,10 @@ var Bot = class extends _seedcord_event_emitter.TypedEventEmitter {
|
|
|
3231
2349
|
await this.logout();
|
|
3232
2350
|
}
|
|
3233
2351
|
async login(token) {
|
|
3234
|
-
|
|
2352
|
+
const ready = Promise.withResolvers();
|
|
2353
|
+
this._client.once(discord_js.Events.ClientReady, () => ready.resolve());
|
|
3235
2354
|
this._client.login(token);
|
|
3236
|
-
await
|
|
2355
|
+
await ready.promise;
|
|
3237
2356
|
this.logger.info(`Logged in as ${chalk.default.bold.magenta(this._client.user?.username)}!`);
|
|
3238
2357
|
return this;
|
|
3239
2358
|
}
|
|
@@ -3244,244 +2363,13 @@ var Bot = class extends _seedcord_event_emitter.TypedEventEmitter {
|
|
|
3244
2363
|
get client() {
|
|
3245
2364
|
return this._client;
|
|
3246
2365
|
}
|
|
3247
|
-
/**
|
|
3248
|
-
* The overloads enforce the key/args correlation at compile time only. At runtime this forwards
|
|
3249
|
-
* straight to the underlying TypedEventEmitter.
|
|
3250
|
-
*
|
|
3251
|
-
* @internal
|
|
3252
|
-
*/
|
|
3253
|
-
emit(event, ...args) {
|
|
3254
|
-
return super.emit(event, ...args);
|
|
3255
|
-
}
|
|
3256
|
-
emitSafe(event, ...args) {
|
|
3257
|
-
return super.emitSafe(event, ...args);
|
|
3258
|
-
}
|
|
3259
|
-
onListenerError(error, event) {
|
|
3260
|
-
this.logger.error(`listener for ${String(event)} threw`, error);
|
|
3261
|
-
}
|
|
3262
|
-
};
|
|
3263
|
-
__decorate([(0, envapt_legacy.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) => (0, _seedcord_errors_internal.validateDiscordToken)(raw) }), __decorateMetadata("design:type", String)], Bot.prototype, "botToken", void 0);
|
|
3264
|
-
|
|
3265
|
-
//#endregion
|
|
3266
|
-
//#region src/subscribers/default/HandledException.ts
|
|
3267
|
-
let HandledException = class HandledException extends WebhookLog {
|
|
3268
|
-
report() {
|
|
3269
|
-
return {
|
|
3270
|
-
username: "Handled Exception",
|
|
3271
|
-
components: [new HandledExceptionContainer(this.data).component],
|
|
3272
|
-
files: [jsonAttachment("source.json", "The raw source the fault came from", this.data.source.raw)]
|
|
3273
|
-
};
|
|
3274
|
-
}
|
|
3275
|
-
};
|
|
3276
|
-
HandledException = __decorate([Subscribe("handledException"), WebhookUrl("HANDLED_EXCEPTION_WEBHOOK_URL")], HandledException);
|
|
3277
|
-
var HandledExceptionContainer = class extends _seedcord_core.BuilderComponent {
|
|
3278
|
-
constructor(data) {
|
|
3279
|
-
super("container");
|
|
3280
|
-
const { denial, uuid, source } = data;
|
|
3281
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source))).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${neutralizeFences(causeStack(denial))}\`\`\``)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Source")).addFileComponents((file) => file.setURL("attachment://source.json"));
|
|
3282
|
-
}
|
|
3283
|
-
};
|
|
3284
|
-
function faultSummary(denial, source) {
|
|
3285
|
-
const head = (0, _seedcord_utils.stripAnsi)(`### A handled fault was reported: \`${denial.name}\`\n**Message:** ${denial.message}\n`);
|
|
3286
|
-
if (source.kind === "event") return `${head}**Event:** ${source.eventName}\n**Handler:** ${source.handler}\n**User ID:** \`${source.userId ?? "n/a"}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\``;
|
|
3287
|
-
return `${head}**Kind:** ${source.interactionKind}\n**Command:** ${source.command ?? "n/a"}\n**CustomId:** ${source.customId ?? "n/a"}\n**User ID:** \`${source.userId}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\`\n**Interaction ID:** \`${source.interactionId}\``;
|
|
3288
|
-
}
|
|
3289
|
-
function causeStack(denial) {
|
|
3290
|
-
const { cause } = denial;
|
|
3291
|
-
if (Error.isError(cause)) return (0, _seedcord_utils.stripAnsi)(cause.stack ?? cause.message);
|
|
3292
|
-
if (typeof cause === "string") return (0, _seedcord_utils.stripAnsi)(cause);
|
|
3293
|
-
if (typeof cause === "bigint" || typeof cause === "symbol" || typeof cause === "function") return cause.toString();
|
|
3294
|
-
if (cause !== void 0) try {
|
|
3295
|
-
return JSON.stringify(cause);
|
|
3296
|
-
} catch {
|
|
3297
|
-
return "[unserializable cause]";
|
|
3298
|
-
}
|
|
3299
|
-
return (0, _seedcord_utils.stripAnsi)(denial.stack ?? "No cause recorded.");
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
//#endregion
|
|
3303
|
-
//#region src/subscribers/default/UnknownException.ts
|
|
3304
|
-
let UnknownException = class UnknownException extends WebhookLog {
|
|
3305
|
-
report() {
|
|
3306
|
-
const { metadata } = this.data;
|
|
3307
|
-
return {
|
|
3308
|
-
username: "Unknown Exception",
|
|
3309
|
-
components: [new UnhandledErrorContainer(this.data).component],
|
|
3310
|
-
files: metadata ? [jsonAttachment("metadata.json", "Metadata associated with the error", metadata)] : void 0
|
|
3311
|
-
};
|
|
3312
|
-
}
|
|
3313
|
-
};
|
|
3314
|
-
UnknownException = __decorate([Subscribe("unknownException"), WebhookUrl("UNKNOWN_EXCEPTION_WEBHOOK_URL")], UnknownException);
|
|
3315
|
-
var UnhandledErrorContainer = class extends _seedcord_core.BuilderComponent {
|
|
3316
|
-
constructor(data) {
|
|
3317
|
-
super("container");
|
|
3318
|
-
const { uuid, error, guild, user, metadata } = data;
|
|
3319
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown\n**Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`\n**Guild Name:** ${guild?.name ?? "Not used in a guild"}\n**User ID:** \`${user?.id ?? "Missing user info"}\`\n**Username:** ${user?.username ?? "Missing user info"}\n`)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${errorReport(error)}\`\`\``));
|
|
3320
|
-
this.addTimestampsIfAvailable(error);
|
|
3321
|
-
this.addMetadataIfAvailable(metadata);
|
|
3322
|
-
}
|
|
3323
|
-
addTimestampsIfAvailable(error) {
|
|
3324
|
-
if (!(error instanceof _discordjs_rest.DiscordAPIError)) return;
|
|
3325
|
-
const now = Date.now();
|
|
3326
|
-
const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
|
|
3327
|
-
if (!snowflake) return void 0;
|
|
3328
|
-
const interactionTs = (0, _seedcord_utils.timestampFromSnowflake)(snowflake);
|
|
3329
|
-
const diff = now - interactionTs;
|
|
3330
|
-
const seconds = Math.floor(diff / 1e3);
|
|
3331
|
-
const millis = diff % 1e3;
|
|
3332
|
-
this.instance.addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### Timestamps\n- **\`Interaction sent\` :** ${new Date(interactionTs).toISOString()} (${interactionTs})\n- **\`Error logged \` :** ${new Date(now).toISOString()} (${now})\n- **\`Offset \` :** ${seconds}s ${millis}ms`));
|
|
3333
|
-
}
|
|
3334
|
-
addMetadataIfAvailable(metadata) {
|
|
3335
|
-
if (!metadata) return;
|
|
3336
|
-
this.instance.addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Metadata")).addFileComponents((file) => file.setURL("attachment://metadata.json"));
|
|
3337
|
-
}
|
|
3338
|
-
};
|
|
3339
|
-
|
|
3340
|
-
//#endregion
|
|
3341
|
-
//#region src/subscribers/Bus.ts
|
|
3342
|
-
/**
|
|
3343
|
-
* Registers and dispatches subscribers. Subscribers load from the configured directories and run
|
|
3344
|
-
* on a programmatic or framework-event publish. Accessed via `core.bus`. Do not construct it directly.
|
|
3345
|
-
*/
|
|
3346
|
-
var Bus = class extends _seedcord_event_emitter.TypedEventEmitter {
|
|
3347
|
-
core;
|
|
3348
|
-
logger = new _seedcord_logger.Logger("Subscribers");
|
|
3349
|
-
isInitialized = false;
|
|
3350
|
-
subscribersMap = /* @__PURE__ */ new Map();
|
|
3351
|
-
executedOnceHandlers = /* @__PURE__ */ new Set();
|
|
3352
|
-
webhookProbes = /* @__PURE__ */ new Map();
|
|
3353
|
-
hmrHandler;
|
|
3354
|
-
constructor(core) {
|
|
3355
|
-
super();
|
|
3356
|
-
this.core = core;
|
|
3357
|
-
if (this.core.config.subscribers.path) {
|
|
3358
|
-
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
3359
|
-
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
3360
|
-
handlersDir: this.core.config.subscribers.path,
|
|
3361
|
-
isHandler: this.isSubscriber.bind(this),
|
|
3362
|
-
registerHandler: this.registerSubscriber.bind(this),
|
|
3363
|
-
unregisterHandler: this.unregisterSubscriber.bind(this),
|
|
3364
|
-
getArtifacts: this.getArtifacts.bind(this),
|
|
3365
|
-
logger: this.logger
|
|
3366
|
-
});
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
getArtifacts(ctor) {
|
|
3370
|
-
return [Reflect.getMetadata(_seedcord_core_internal.SubscribeMetadataKey, ctor).subscriber];
|
|
3371
|
-
}
|
|
3372
|
-
/** @internal */
|
|
3373
|
-
async init() {
|
|
3374
|
-
if (this.isInitialized) return;
|
|
3375
|
-
this.isInitialized = true;
|
|
3376
|
-
this.registerSubscriber(UnknownException);
|
|
3377
|
-
this.registerSubscriber(HandledException);
|
|
3378
|
-
const subscribersDir = this.core.config.subscribers.path;
|
|
3379
|
-
if (subscribersDir) {
|
|
3380
|
-
this.logger.info(chalk.default.bold(subscribersDir));
|
|
3381
|
-
await this.loadSubscribers(subscribersDir);
|
|
3382
|
-
const totalSubscribers = [...this.subscribersMap.values()].reduce((acc, handlers) => acc + handlers.length, 0);
|
|
3383
|
-
this.logger.utils.list([`${chalk.default.bold.magenta(totalSubscribers)} subscribers`], chalk.default.bold.green("Loaded"));
|
|
3384
|
-
}
|
|
3385
|
-
if (!envapt.Envapter.isTest) await this.verifyWebhooks();
|
|
3386
|
-
}
|
|
3387
|
-
/** @internal Exposed for tests. */
|
|
3388
|
-
async verifyWebhooks() {
|
|
3389
|
-
const missing = [];
|
|
3390
|
-
await Promise.all([...this.webhookProbes].map(async ([url, envKeys]) => {
|
|
3391
|
-
const result = await WebhookLog.senderFor(url).verify();
|
|
3392
|
-
if (result === "missing") missing.push(...envKeys);
|
|
3393
|
-
else if (result === "unreachable") this.logger.warn(`could not verify webhook at ${_seedcord_logger.paint.sky.bold(envKeys.join(", "))}`);
|
|
3394
|
-
}));
|
|
3395
|
-
if (missing.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigWebhookNotFound, [missing.join(", ")]);
|
|
3396
|
-
}
|
|
3397
|
-
/** @internal */
|
|
3398
|
-
async onHmr(event) {
|
|
3399
|
-
if (this.hmrHandler) await this.hmrHandler.handle(event);
|
|
3400
|
-
}
|
|
3401
|
-
async loadSubscribers(dir) {
|
|
3402
|
-
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
3403
|
-
for (const exportName of Object.keys(imported)) {
|
|
3404
|
-
const val = imported[exportName];
|
|
3405
|
-
if (this.isSubscriber(val)) {
|
|
3406
|
-
this.registerSubscriber(val);
|
|
3407
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
3408
|
-
this.logger.utils.registration(val.name, relativePath);
|
|
3409
|
-
}
|
|
3410
|
-
}
|
|
3411
|
-
}, this.logger);
|
|
3412
|
-
}
|
|
3413
|
-
registerSubscriber(handler) {
|
|
3414
|
-
if (handler.prototype instanceof WebhookLog) {
|
|
3415
|
-
const envKey = WebhookLog.envKeyOf(handler);
|
|
3416
|
-
const url = WebhookLog.urlOf(envKey);
|
|
3417
|
-
if (url === null) {
|
|
3418
|
-
this.logger.warn(`${chalk.default.bold(handler.name)} disabled, ${_seedcord_logger.paint.sky.bold(envKey)} is not set`);
|
|
3419
|
-
return;
|
|
3420
|
-
}
|
|
3421
|
-
const envKeys = this.webhookProbes.get(url) ?? [];
|
|
3422
|
-
if (!envKeys.includes(envKey)) envKeys.push(envKey);
|
|
3423
|
-
this.webhookProbes.set(url, envKeys);
|
|
3424
|
-
}
|
|
3425
|
-
const options = Reflect.getMetadata(_seedcord_core_internal.SubscribeMetadataKey, handler);
|
|
3426
|
-
let handlers = this.subscribersMap.get(options.subscriber);
|
|
3427
|
-
if (!handlers) {
|
|
3428
|
-
handlers = [];
|
|
3429
|
-
this.subscribersMap.set(options.subscriber, handlers);
|
|
3430
|
-
}
|
|
3431
|
-
handlers.push({
|
|
3432
|
-
ctor: handler,
|
|
3433
|
-
frequency: options.frequency ?? "on"
|
|
3434
|
-
});
|
|
3435
|
-
}
|
|
3436
|
-
unregisterSubscriber(handler, artifacts) {
|
|
3437
|
-
const subscribers = artifacts ?? [...this.subscribersMap.keys()];
|
|
3438
|
-
for (const subscriber of subscribers) {
|
|
3439
|
-
const handlers = this.subscribersMap.get(subscriber);
|
|
3440
|
-
if (!handlers) continue;
|
|
3441
|
-
const index = handlers.findIndex((entry) => entry.ctor === handler);
|
|
3442
|
-
if (index !== -1) handlers.splice(index, 1);
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
3445
|
-
isSubscriber(obj) {
|
|
3446
|
-
if (typeof obj !== "function") return false;
|
|
3447
|
-
return obj.prototype instanceof Subscriber && Reflect.hasMetadata(_seedcord_core_internal.SubscribeMetadataKey, obj);
|
|
3448
|
-
}
|
|
3449
|
-
/**
|
|
3450
|
-
* Publishes an event to its subscribers and native listeners.
|
|
3451
|
-
*
|
|
3452
|
-
* Fire-and-forget, so subscriber handlers run asynchronously and this returns before they
|
|
3453
|
-
* complete, and callers must not assume side effects have landed. Errors thrown by a subscriber
|
|
3454
|
-
* are caught and logged, never surfaced here. A `'once'` subscriber is marked as executed when
|
|
3455
|
-
* it starts (even if it throws), so it never runs twice.
|
|
3456
|
-
*
|
|
3457
|
-
* @param event - The subscription key to publish
|
|
3458
|
-
* @param data - Payload passed to each subscriber
|
|
3459
|
-
* @returns Whether the underlying emitter had native listeners for the event
|
|
3460
|
-
*/
|
|
3461
|
-
publish(event, data) {
|
|
3462
|
-
this.processSubscriber(event, data);
|
|
3463
|
-
return super.emit(event, ...[data]);
|
|
3464
|
-
}
|
|
3465
|
-
async processSubscriber(subscriberName, data) {
|
|
3466
|
-
const handlers = this.subscribersMap.get(subscriberName);
|
|
3467
|
-
if (!handlers) return;
|
|
3468
|
-
for (const entry of handlers) {
|
|
3469
|
-
if (entry.frequency === "once" && this.executedOnceHandlers.has(entry.ctor)) continue;
|
|
3470
|
-
try {
|
|
3471
|
-
if (entry.frequency === "once") this.executedOnceHandlers.add(entry.ctor);
|
|
3472
|
-
const Ctor = entry.ctor;
|
|
3473
|
-
await new Ctor(data, this.core).execute();
|
|
3474
|
-
} catch (err) {
|
|
3475
|
-
this.logger.error(`Error in subscriber ${String(subscriberName)} handler ${entry.ctor.name}:`, err);
|
|
3476
|
-
}
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
2366
|
};
|
|
2367
|
+
__decorate([(0, envapt_legacy.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) => (0, _seedcord_errors_internal.validateDiscordToken)(raw) })], Bot.prototype, "botToken", void 0);
|
|
3480
2368
|
|
|
3481
2369
|
//#endregion
|
|
3482
2370
|
//#region src/version.ts
|
|
3483
2371
|
/** Package version */
|
|
3484
|
-
const version = "0.1.0-next.
|
|
2372
|
+
const version = "0.1.0-next.6";
|
|
3485
2373
|
|
|
3486
2374
|
//#endregion
|
|
3487
2375
|
//#region src/Seedcord.ts
|
|
@@ -3499,14 +2387,13 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3499
2387
|
version = version;
|
|
3500
2388
|
healthCheck;
|
|
3501
2389
|
hmrManager;
|
|
3502
|
-
/** @see {@link CoordinatedShutdown} */
|
|
3503
|
-
shutdown;
|
|
3504
|
-
/** @see {@link CoordinatedStartup} */
|
|
3505
|
-
startup;
|
|
3506
2390
|
/** @see {@link Bus} */
|
|
3507
2391
|
bus;
|
|
2392
|
+
subscribers;
|
|
3508
2393
|
/** @see {@link Bot} */
|
|
3509
2394
|
bot;
|
|
2395
|
+
/** The discord.js client's REST client. Its token is set during the Login phase. */
|
|
2396
|
+
rest;
|
|
3510
2397
|
/** @see {@link IRateLimiter} */
|
|
3511
2398
|
rateLimiter;
|
|
3512
2399
|
/**
|
|
@@ -3514,18 +2401,16 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3514
2401
|
* @throws A **SeedcordError** When attempting to create multiple instances (singleton)
|
|
3515
2402
|
*/
|
|
3516
2403
|
constructor(config) {
|
|
3517
|
-
|
|
3518
|
-
const startup = new _seedcord_core_node_internal.CoordinatedStartup();
|
|
3519
|
-
super(shutdown, startup);
|
|
2404
|
+
super(new _seedcord_core_node_internal.CoordinatedShutdown(), new _seedcord_core_node_internal.CoordinatedStartup());
|
|
3520
2405
|
this.config = config;
|
|
3521
2406
|
(0, _seedcord_logger_node.installNodeDefaults)(config.logger);
|
|
3522
2407
|
(0, _seedcord_core_internal.setBotColor)(config.botColor);
|
|
3523
|
-
this.shutdown = shutdown;
|
|
3524
|
-
this.startup = startup;
|
|
3525
2408
|
this.hmrManager = new _seedcord_core_internal.HmrManager();
|
|
3526
2409
|
this.hmrManager.init();
|
|
3527
|
-
this.bus = new Bus(this);
|
|
2410
|
+
this.bus = new _seedcord_core.Bus(this);
|
|
2411
|
+
this.subscribers = new _seedcord_core_node_internal.SubscriberLoader(this.bus, config.subscribers.path);
|
|
3528
2412
|
this.bot = new Bot(this);
|
|
2413
|
+
this.rest = this.bot.client.rest;
|
|
3529
2414
|
this.rateLimiter = config.store ?? new _seedcord_rate_limiter.MemoryRateLimiter();
|
|
3530
2415
|
this.healthCheck = _seedcord_core_node_internal.HealthCheck.fromOption(this.shutdown, config.healthCheck);
|
|
3531
2416
|
this.registerStartupTasks();
|
|
@@ -3534,31 +2419,24 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3534
2419
|
get username() {
|
|
3535
2420
|
return this.bot.client.user?.username;
|
|
3536
2421
|
}
|
|
3537
|
-
pluginCapabilities() {
|
|
3538
|
-
return {
|
|
3539
|
-
client: this.bot.client,
|
|
3540
|
-
token: this.bot.botToken,
|
|
3541
|
-
rest: this.bot.client.rest
|
|
3542
|
-
};
|
|
3543
|
-
}
|
|
3544
2422
|
static reset() {
|
|
3545
2423
|
super.reset();
|
|
3546
2424
|
_seedcord_logger.LoggerChannelRegistry.instance.reset();
|
|
3547
2425
|
}
|
|
3548
2426
|
registerStartupTasks() {
|
|
3549
2427
|
if (envapt.Envapter.isDevelopment || envapt.Envapter.isTest) this.registerHmrAwareModules();
|
|
3550
|
-
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "
|
|
2428
|
+
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "bus-initialization", async () => {
|
|
3551
2429
|
this.bus.logger.utils.initialization("Subscribers", "start");
|
|
3552
|
-
await this.
|
|
2430
|
+
await this.subscribers.init();
|
|
3553
2431
|
this.bus.logger.utils.initialization("Subscribers", "end");
|
|
3554
2432
|
});
|
|
3555
|
-
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Login, "
|
|
2433
|
+
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Login, "bot-initialization", async () => {
|
|
3556
2434
|
this.bot.logger.utils.initialization("Bot", "start");
|
|
3557
2435
|
await this.bot.init();
|
|
3558
2436
|
this.bot.logger.utils.initialization("Bot", "end");
|
|
3559
2437
|
});
|
|
3560
2438
|
const { healthCheck } = this;
|
|
3561
|
-
if (healthCheck) this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Ready, "
|
|
2439
|
+
if (healthCheck) this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Ready, "health-check", async () => {
|
|
3562
2440
|
healthCheck.logger.utils.initialization("HealthCheck", "start");
|
|
3563
2441
|
await healthCheck.init();
|
|
3564
2442
|
healthCheck.logger.utils.initialization("HealthCheck", "end");
|
|
@@ -3580,9 +2458,9 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3580
2458
|
return this;
|
|
3581
2459
|
}
|
|
3582
2460
|
registerHmrAwareModules() {
|
|
3583
|
-
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "
|
|
2461
|
+
this.startup.addTask(_seedcord_core_node_internal.StartupPhase.Configuration, "hmr-registration", async () => {
|
|
3584
2462
|
this.hmrManager.register(this.bot);
|
|
3585
|
-
this.hmrManager.register(this.
|
|
2463
|
+
this.hmrManager.register(this.subscribers);
|
|
3586
2464
|
for (const plugin of this.plugins) this.hmrManager.register(plugin);
|
|
3587
2465
|
await Promise.resolve();
|
|
3588
2466
|
});
|
|
@@ -3593,7 +2471,6 @@ var Seedcord = class Seedcord extends _seedcord_core_node_internal.Pluggable {
|
|
|
3593
2471
|
exports.ArraySource = ArraySource;
|
|
3594
2472
|
exports.AutocompleteHandler = AutocompleteHandler;
|
|
3595
2473
|
exports.ButtonHandler = ButtonHandler;
|
|
3596
|
-
exports.CommandMentions = CommandMentions;
|
|
3597
2474
|
exports.ContextMenuHandler = ContextMenuHandler;
|
|
3598
2475
|
exports.CursorSource = CursorSource;
|
|
3599
2476
|
exports.Emojis = Emojis;
|
|
@@ -3608,27 +2485,18 @@ exports.MiddlewareType = MiddlewareType;
|
|
|
3608
2485
|
exports.ModalHandler = ModalHandler;
|
|
3609
2486
|
exports.Nsfw = Nsfw;
|
|
3610
2487
|
exports.Paginator = Paginator;
|
|
2488
|
+
exports.Plugin = Plugin;
|
|
3611
2489
|
exports.RegisterEvent = RegisterEvent;
|
|
3612
2490
|
exports.RepliableHandler = RepliableHandler;
|
|
3613
2491
|
exports.Seedcord = Seedcord;
|
|
3614
2492
|
exports.SelectMenuHandler = SelectMenuHandler;
|
|
3615
2493
|
exports.SlashHandler = SlashHandler;
|
|
3616
|
-
exports.Subscribe = Subscribe;
|
|
3617
2494
|
exports.Subscriber = Subscriber;
|
|
3618
2495
|
exports.WebhookLog = WebhookLog;
|
|
3619
|
-
exports.WebhookUrl = WebhookUrl;
|
|
3620
2496
|
exports.checkBotPermissions = checkBotPermissions;
|
|
3621
2497
|
exports.checkPermissions = checkPermissions;
|
|
3622
|
-
exports.fetchGuildMember = fetchGuildMember;
|
|
3623
|
-
exports.fetchManyGuildMembers = fetchManyGuildMembers;
|
|
3624
|
-
exports.fetchManyUsers = fetchManyUsers;
|
|
3625
|
-
exports.fetchRole = fetchRole;
|
|
3626
|
-
exports.fetchText = fetchText;
|
|
3627
|
-
exports.fetchUser = fetchUser;
|
|
3628
|
-
exports.getBotRole = getBotRole;
|
|
3629
2498
|
exports.getConfirmation = getConfirmation;
|
|
3630
2499
|
exports.hasPermsToAssign = hasPermsToAssign;
|
|
3631
|
-
exports.updateMemberRoles = updateMemberRoles;
|
|
3632
2500
|
exports.version = version;
|
|
3633
2501
|
Object.keys(_seedcord_core).forEach(function (k) {
|
|
3634
2502
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
@@ -3644,6 +2512,7 @@ Object.keys(_seedcord_errors).forEach(function (k) {
|
|
|
3644
2512
|
});
|
|
3645
2513
|
});
|
|
3646
2514
|
|
|
2515
|
+
var _seedcord_event_emitter = require("@seedcord/event-emitter");
|
|
3647
2516
|
Object.keys(_seedcord_event_emitter).forEach(function (k) {
|
|
3648
2517
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
3649
2518
|
enumerable: true,
|