@seedcord/gateway 0.9.0-next.0 → 0.9.0-next.2
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/README.md +2 -2
- package/dist/index.d.mts +190 -216
- package/dist/index.mjs +503 -938
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -10
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { DRAIN_TASK_TIMEOUT_MS, DRAIN_WINDOW_MS, HealthCheck, InFlight, ShutdownPhase, StartupPhase, setEnvaptBaseDir, shutdownOf } from "@seedcord/core/node/internal";
|
|
3
3
|
import { WinstonConsoleSink, WinstonFileSink, installNodeConsole } from "@seedcord/logger/node";
|
|
4
|
-
import { COMMAND_DEPLOY_TIMEOUT_MS, CommandRegistry, EventMetadataKey, EventMiddlewareMetadataKey,
|
|
4
|
+
import { AutocompleteResponder, COMMAND_DEPLOY_TIMEOUT_MS, CommandRegistry, DispatchContext, EventMetadataKey, EventMiddlewareMetadataKey, FaultBoundary, FixedScope, GatedMetadataKey, HandlerLoader, HmrManager, InteractionMetadataKey, InteractionMiddlewareMetadataKey, InteractionRun, MiddlewareRegistry, NoticeCard, OnceTracker, PublishDefault, RouteRegistry, SubscriberLoader, accessorStore, assertUniqueKeys, attachmentsOf, bindBotColor, busLoggerOf, clearStore, componentKindOf, connectDevChannel, decodeComponentRoute, deferFlags, eventResultFor, guardedAccessor, interactionFaultSource, interactionMiddleware, isEmojiTuple, matchRoute, readBotToken, resultFor, runAfter, runCase, runHandlerGates, sendFlags } from "@seedcord/core/internal";
|
|
5
5
|
import { SeedcordErrorCode, paint } from "@seedcord/errors";
|
|
6
6
|
import { SeedcordError, SeedcordTypeError, throwSingleOrAggregate } from "@seedcord/errors/internal";
|
|
7
|
-
import { ArraySourceBase, BaseHandler as BaseHandler$1, BaseReplySender, BuilderComponent, CursorSourceBase, CustomId,
|
|
7
|
+
import { ArraySourceBase, BaseHandler as BaseHandler$1, BaseReplySender, BuilderComponent, CursorSourceBase, CustomId, InteractionKind, Notice, PaginatorBase, RepliableHandler as RepliableHandler$1, ResolvedEmoji, RowComponent, Silence, Subscriber as Subscriber$1, WebhookLog as WebhookLog$1, assertPermissions, defineGate } from "@seedcord/core";
|
|
8
|
+
import { ButtonStyle, Client, ComponentType, DiscordAPIError, Events, FormattingPatterns, GatewayIntentBits, Guild, GuildMember, IntentsBitField, InteractionContextType, Message, MessageFlags, PermissionFlagsBits, Role, User } from "discord.js";
|
|
8
9
|
import { Logger } from "@seedcord/logger";
|
|
9
|
-
import { ButtonStyle, Client, ComponentType, DiscordAPIError, Events, GatewayIntentBits, Guild, GuildMember, IntentsBitField, InteractionContextType, Message, MessageFlags, PermissionFlagsBits, RESTJSONErrorCodes, Role, User } from "discord.js";
|
|
10
10
|
import { ButtonBuilder, TextDisplayBuilder } from "@discordjs/builders";
|
|
11
|
-
import { asError, buildSlashRoute, isStringArray } from "@seedcord/utils/internal";
|
|
12
11
|
import { Plugin as Plugin$1 } from "@seedcord/core/plugin";
|
|
13
12
|
import { CoordinatedShutdown, CoordinatedStartup, ServerHost } from "@seedcord/core/node";
|
|
14
13
|
import { HostAugmentTarget, HostVersion, SeedcordBrand } from "@seedcord/types/internal";
|
|
15
14
|
import { Envapter } from "envapt";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import * as crypto from "node:crypto";
|
|
15
|
+
import { hasKeys } from "@seedcord/utils";
|
|
16
|
+
import { asError, buildSlashRoute } from "@seedcord/utils/internal";
|
|
19
17
|
import { prefixOf } from "@seedcord/custom-id";
|
|
18
|
+
import { HmrModuleHandler } from "@seedcord/core/hmr";
|
|
20
19
|
|
|
21
20
|
export * from "@seedcord/errors"
|
|
22
21
|
|
|
@@ -79,12 +78,12 @@ installNodeConsole();
|
|
|
79
78
|
*/
|
|
80
79
|
function RegisterEvent(...defs) {
|
|
81
80
|
return function(constructor) {
|
|
82
|
-
const existing = Reflect.getMetadata(EventMetadataKey, constructor) ?? [];
|
|
83
81
|
const entries = defs.map(([event, options]) => ({
|
|
84
82
|
event,
|
|
85
83
|
frequency: options?.frequency ?? "on"
|
|
86
84
|
}));
|
|
87
|
-
|
|
85
|
+
assertUniqueKeys("@RegisterEvent", constructor, entries.map((entry) => entry.event));
|
|
86
|
+
Reflect.defineMetadata(EventMetadataKey, entries, constructor);
|
|
88
87
|
};
|
|
89
88
|
}
|
|
90
89
|
|
|
@@ -97,7 +96,7 @@ function RegisterEvent(...defs) {
|
|
|
97
96
|
* handler) is a compile error at this line, and the error message shows the gate and the handler kind. Place it
|
|
98
97
|
* above the route decorator.
|
|
99
98
|
*
|
|
100
|
-
* Combine gates with {@link and} or {@link or}
|
|
99
|
+
* Combine gates into a single gate with {@link and} or {@link or}.
|
|
101
100
|
*
|
|
102
101
|
* @example
|
|
103
102
|
* ```ts
|
|
@@ -190,19 +189,55 @@ const IgnoreBots = defineGate("IgnoreBots", (ctx) => {
|
|
|
190
189
|
if (ctx.user?.bot) throw new Silence("actor is a bot");
|
|
191
190
|
});
|
|
192
191
|
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/bot/gates/catalog/MentionPrefix.ts
|
|
194
|
+
function startsWithBotMention(message) {
|
|
195
|
+
const match = FormattingPatterns.User.exec(message.content?.trimStart() ?? "");
|
|
196
|
+
return match?.index === 0 && match.groups?.id === message.client.user.id;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Passes only a message that starts with a mention of the bot, like `@bot ping`. It ignores every other message
|
|
200
|
+
* with a {@link Silence}, including a reply that pings the bot, a role mention, and a mention later in the text.
|
|
201
|
+
*
|
|
202
|
+
* On `messageUpdate` it passes only when the edit adds the mention. An edit that keeps the mention doesn't run the
|
|
203
|
+
* handler again. With `Partials.Message` on, discord.js also emits an edit to a message it didn't cache. That edit
|
|
204
|
+
* runs the handler again. Attach the gate without calling it, since it doesn't take options.
|
|
205
|
+
*
|
|
206
|
+
* A handler behind this gate can read `content` without the Message Content intent, because Discord sends the text of
|
|
207
|
+
* a message that mentions the bot.
|
|
208
|
+
*
|
|
209
|
+
* @see {@link Gated}
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* import { Events } from 'discord.js';
|
|
214
|
+
*
|
|
215
|
+
* \@Gated(IgnoreBots, MentionPrefix)
|
|
216
|
+
* \@RegisterEvent([Events.MessageCreate], [Events.MessageUpdate])
|
|
217
|
+
* class MentionCommands extends EventHandler<Events.MessageCreate | Events.MessageUpdate> {
|
|
218
|
+
* async execute() {
|
|
219
|
+
* // ...
|
|
220
|
+
* }
|
|
221
|
+
* }
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
const MentionPrefix = defineGate("MentionPrefix", ({ payload }) => {
|
|
225
|
+
if (!(payload.length === 1 ? startsWithBotMention(payload[0]) : !startsWithBotMention(payload[0]) && startsWithBotMention(payload[1]))) throw new Silence("message does not start with a mention of the bot");
|
|
226
|
+
});
|
|
227
|
+
|
|
193
228
|
//#endregion
|
|
194
229
|
//#region src/bot/notices/index.ts
|
|
195
|
-
var NotNsfw = class extends
|
|
196
|
-
constructor(
|
|
197
|
-
super(
|
|
230
|
+
var NotNsfw = class extends Notice {
|
|
231
|
+
constructor() {
|
|
232
|
+
super("This can only be used in an age-restricted channel.");
|
|
198
233
|
this.summary = "be in an age-restricted channel";
|
|
199
234
|
}
|
|
200
235
|
};
|
|
201
236
|
var RoleHigherThanMe = class extends Notice {
|
|
202
237
|
role;
|
|
203
238
|
botRole;
|
|
204
|
-
constructor(
|
|
205
|
-
super(
|
|
239
|
+
constructor(role, botRole) {
|
|
240
|
+
super("Role is higher than me");
|
|
206
241
|
this.role = role;
|
|
207
242
|
this.botRole = botRole;
|
|
208
243
|
}
|
|
@@ -211,11 +246,13 @@ var RoleHigherThanMe = class extends Notice {
|
|
|
211
246
|
}
|
|
212
247
|
};
|
|
213
248
|
var CannotAssignBotRole = class extends Notice {
|
|
214
|
-
|
|
215
|
-
|
|
249
|
+
role;
|
|
250
|
+
constructor(role) {
|
|
251
|
+
super(`Cannot assign managed role ${role.name}`);
|
|
252
|
+
this.role = role;
|
|
216
253
|
}
|
|
217
254
|
render() {
|
|
218
|
-
return { components: [new NoticeCard(
|
|
255
|
+
return { components: [new NoticeCard(`I cannot assign <@&${this.role.id}>, a managed role.`).component] };
|
|
219
256
|
}
|
|
220
257
|
};
|
|
221
258
|
|
|
@@ -227,11 +264,11 @@ function channelIsNsfw(channel) {
|
|
|
227
264
|
return "nsfw" in channel ? channel.nsfw : false;
|
|
228
265
|
}
|
|
229
266
|
/**
|
|
230
|
-
* Requires an age-restricted channel, else refuses. Pass
|
|
267
|
+
* Requires an age-restricted channel, else refuses. Pass a Notice class as `notice` to replace the refusal.
|
|
231
268
|
*
|
|
232
269
|
* A thread inherits its parent channel's nsfw flag. ModalSubmit has no reliable channel, so it is excluded.
|
|
233
270
|
*
|
|
234
|
-
* @param options -
|
|
271
|
+
* @param options - Replace the refusal with a Notice class as `notice`.
|
|
235
272
|
*
|
|
236
273
|
* @see {@link Gated}
|
|
237
274
|
*
|
|
@@ -249,7 +286,7 @@ function channelIsNsfw(channel) {
|
|
|
249
286
|
function Nsfw(options) {
|
|
250
287
|
return defineGate("Nsfw", (ctx) => {
|
|
251
288
|
if (channelIsNsfw(ctx.interaction.channel)) return;
|
|
252
|
-
throw
|
|
289
|
+
throw new ((options?.notice) ?? NotNsfw)();
|
|
253
290
|
});
|
|
254
291
|
}
|
|
255
292
|
|
|
@@ -350,57 +387,68 @@ function mentionFor(subject) {
|
|
|
350
387
|
|
|
351
388
|
//#endregion
|
|
352
389
|
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
390
|
+
/**
|
|
391
|
+
* Checks a {@link Role} or a {@link GuildMember} against the base permission set from roles, or against a
|
|
392
|
+
* channel's set when `in` is given. Refuses when a permission in `scope` is missing, or with `inverse` when
|
|
393
|
+
* one is present.
|
|
394
|
+
*
|
|
395
|
+
* @throws A {@link Notice} when the check refuses. It lists the missing or present permissions.
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* ```ts
|
|
399
|
+
* import { PermissionFlagsBits } from 'discord.js';
|
|
400
|
+
* import { checkPermissions } from '@seedcord/gateway';
|
|
401
|
+
*
|
|
402
|
+
* checkPermissions({ for: member, scope: [PermissionFlagsBits.KickMembers] });
|
|
403
|
+
* checkPermissions({ for: role, in: channel, scope: [PermissionFlagsBits.SendMessages] });
|
|
404
|
+
* ```
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* // refuse when the member holds Administrator, with your own Notice class
|
|
409
|
+
* checkPermissions({ for: member, scope: [PermissionFlagsBits.Administrator], inverse: true, notice: TooPowerful });
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
function checkPermissions({ for: target, in: channel, scope, inverse = false, notice }) {
|
|
413
|
+
const held = channel ? channel.permissionsFor(target, true) : target.permissions;
|
|
362
414
|
assertPermissions({
|
|
363
|
-
subject: mentionFor(
|
|
364
|
-
permissions:
|
|
415
|
+
subject: mentionFor(target),
|
|
416
|
+
permissions: held.bitfield,
|
|
365
417
|
scope,
|
|
366
418
|
inverse,
|
|
367
|
-
|
|
368
|
-
...dangerousNotice && { dangerousNotice }
|
|
419
|
+
notice
|
|
369
420
|
});
|
|
370
421
|
}
|
|
371
422
|
|
|
372
423
|
//#endregion
|
|
373
424
|
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
425
|
+
function botMemberOf(guild) {
|
|
426
|
+
const me = guild.members.me;
|
|
427
|
+
if (!me) throw new SeedcordError(SeedcordErrorCode.CoreBotMemberMissing, [guild.id]);
|
|
428
|
+
return me;
|
|
429
|
+
}
|
|
374
430
|
/**
|
|
375
|
-
* Checks the bot's own permissions in a {@link Guild} or
|
|
376
|
-
* `scope` is missing, or with `inverse` when one is present.
|
|
377
|
-
*
|
|
431
|
+
* Checks the bot's own permissions in a {@link Guild}, or in a channel or thread. Refuses when a permission
|
|
432
|
+
* in `scope` is missing, or with `inverse` when one is present.
|
|
433
|
+
*
|
|
434
|
+
* @throws A {@link Notice} when the check refuses. It lists the missing or present permissions.
|
|
435
|
+
* @throws A **SeedcordError** when the guild's member cache doesn't hold the bot's member.
|
|
378
436
|
*
|
|
379
437
|
* @example
|
|
380
438
|
* ```ts
|
|
381
439
|
* import { PermissionFlagsBits } from 'discord.js';
|
|
382
440
|
* import { checkBotPermissions } from '@seedcord/gateway';
|
|
383
441
|
*
|
|
384
|
-
* checkBotPermissions(
|
|
442
|
+
* checkBotPermissions({ in: channel, scope: [PermissionFlagsBits.SendMessages, PermissionFlagsBits.ViewChannel] });
|
|
385
443
|
* ```
|
|
386
444
|
*/
|
|
387
|
-
function checkBotPermissions(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
checkPermissions(me, target, scope, inverse, errors);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
const me = target.guild.members.me;
|
|
398
|
-
if (!me) {
|
|
399
|
-
new Logger("checkBotPermissions", { channel: "errors" }).warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
400
|
-
const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
401
|
-
throw new ((errors?.missingNotice) ?? MissingPermissions)(void 0, "The bot", names);
|
|
402
|
-
}
|
|
403
|
-
checkPermissions(me, target, scope, inverse, errors);
|
|
445
|
+
function checkBotPermissions({ in: where, ...check }) {
|
|
446
|
+
const [guild, channel] = where instanceof Guild ? [where, void 0] : [where.guild, where];
|
|
447
|
+
checkPermissions({
|
|
448
|
+
for: botMemberOf(guild),
|
|
449
|
+
in: channel,
|
|
450
|
+
...check
|
|
451
|
+
});
|
|
404
452
|
}
|
|
405
453
|
|
|
406
454
|
//#endregion
|
|
@@ -411,42 +459,31 @@ function botRoleOf(guild) {
|
|
|
411
459
|
return botRole;
|
|
412
460
|
}
|
|
413
461
|
/**
|
|
414
|
-
* Validates that the bot can assign a
|
|
462
|
+
* Validates that the bot can assign a role. Refuses when the role is at or above the bot's, when an
|
|
463
|
+
* integration manages it, or when the bot lacks Manage Roles.
|
|
415
464
|
*
|
|
416
|
-
* @param roleOrOptions - Target role or complete options for the check
|
|
417
465
|
* @throws A {@link Notice} when the check refuses.
|
|
418
|
-
* @throws A **SeedcordError** when the
|
|
466
|
+
* @throws A **SeedcordError** when the guild's caches don't hold the bot's role or member.
|
|
419
467
|
*
|
|
420
468
|
* @example
|
|
421
469
|
* ```ts
|
|
422
470
|
* import { hasPermsToAssign } from '@seedcord/gateway';
|
|
423
471
|
*
|
|
424
|
-
*
|
|
425
|
-
* hasPermsToAssign(role);
|
|
472
|
+
* hasPermsToAssign({ role });
|
|
426
473
|
*
|
|
427
|
-
* //
|
|
428
|
-
* hasPermsToAssign({
|
|
429
|
-
* targetRole: role,
|
|
430
|
-
* noticeOverrides: {
|
|
431
|
-
* higherNotice: CustomHigherNotice,
|
|
432
|
-
* managedNotice: CustomManagedNotice,
|
|
433
|
-
* missingNotice: CustomMissingPermissionsNotice
|
|
434
|
-
* }
|
|
435
|
-
* });
|
|
474
|
+
* // with your own Notice classes
|
|
475
|
+
* hasPermsToAssign({ role, higher: RoleTooHigh, managed: RoleIsManaged, missing: CannotManageRoles });
|
|
436
476
|
* ```
|
|
437
477
|
*/
|
|
438
|
-
function hasPermsToAssign(
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
|
|
448
|
-
if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
|
|
449
|
-
checkBotPermissions(targetRole.guild, [PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
|
|
478
|
+
function hasPermsToAssign({ role, higher, managed, missing }) {
|
|
479
|
+
const botRole = botRoleOf(role.guild);
|
|
480
|
+
if (role.comparePositionTo(botRole) >= 0) throw new (higher ?? RoleHigherThanMe)(role, botRole);
|
|
481
|
+
if (role.managed) throw new (managed ?? CannotAssignBotRole)(role);
|
|
482
|
+
checkBotPermissions({
|
|
483
|
+
in: role.guild,
|
|
484
|
+
scope: [PermissionFlagsBits.ManageRoles],
|
|
485
|
+
notice: missing
|
|
486
|
+
});
|
|
450
487
|
}
|
|
451
488
|
|
|
452
489
|
//#endregion
|
|
@@ -511,126 +548,6 @@ async function getConfirmation(handler, prompt, options) {
|
|
|
511
548
|
return confirmed;
|
|
512
549
|
}
|
|
513
550
|
|
|
514
|
-
//#endregion
|
|
515
|
-
//#region src/bot/ReplySender.ts
|
|
516
|
-
function isReplyFile(file) {
|
|
517
|
-
return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
|
|
518
|
-
}
|
|
519
|
-
function toDjsFile(file) {
|
|
520
|
-
if (!isReplyFile(file)) return file;
|
|
521
|
-
return {
|
|
522
|
-
attachment: Buffer.from(file.data),
|
|
523
|
-
name: file.name,
|
|
524
|
-
...file.description !== void 0 && { description: file.description },
|
|
525
|
-
...file.title !== void 0 && { title: file.title }
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
function seedState(interaction) {
|
|
529
|
-
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
530
|
-
if (interaction.replied) return "replied";
|
|
531
|
-
if (interaction.deferred) return "deferred-reply";
|
|
532
|
-
return "unacked";
|
|
533
|
-
}
|
|
534
|
-
/** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
|
|
535
|
-
var ReplySender = class extends BaseReplySender {
|
|
536
|
-
interaction;
|
|
537
|
-
constructor(interaction, dispatch, bus) {
|
|
538
|
-
super({
|
|
539
|
-
bus,
|
|
540
|
-
dispatch,
|
|
541
|
-
interactionId: interaction.id
|
|
542
|
-
}, seedState(interaction));
|
|
543
|
-
this.interaction = interaction;
|
|
544
|
-
}
|
|
545
|
-
async writeReply(response, opts) {
|
|
546
|
-
const result = await this.interaction.reply({
|
|
547
|
-
...this.replyOptions(response, sendFlags(opts)),
|
|
548
|
-
withResponse: true
|
|
549
|
-
});
|
|
550
|
-
return this.createdMessage(result);
|
|
551
|
-
}
|
|
552
|
-
async writeDefer(opts) {
|
|
553
|
-
await this.interaction.deferReply({ flags: deferFlags(opts) });
|
|
554
|
-
}
|
|
555
|
-
async writeDeferUpdate() {
|
|
556
|
-
await this.sourceInteraction("deferUpdate").deferUpdate();
|
|
557
|
-
}
|
|
558
|
-
async writeUpdate(response) {
|
|
559
|
-
const result = await this.sourceInteraction("update").update({
|
|
560
|
-
...this.updateOptions(response),
|
|
561
|
-
withResponse: true
|
|
562
|
-
});
|
|
563
|
-
return this.createdMessage(result);
|
|
564
|
-
}
|
|
565
|
-
async writeFollowUp(response, opts) {
|
|
566
|
-
return await this.interaction.followUp(this.replyOptions(response, sendFlags(opts)));
|
|
567
|
-
}
|
|
568
|
-
async writeEditOriginal(response) {
|
|
569
|
-
return await this.interaction.editReply(this.editOptions(response));
|
|
570
|
-
}
|
|
571
|
-
async writeEditTarget(targetId, response) {
|
|
572
|
-
return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
|
|
573
|
-
}
|
|
574
|
-
async writeDeleteOriginal() {
|
|
575
|
-
await this.interaction.deleteReply();
|
|
576
|
-
}
|
|
577
|
-
async writeDeleteTarget(targetId) {
|
|
578
|
-
await this.interaction.deleteReply(targetId);
|
|
579
|
-
}
|
|
580
|
-
async writeModal(data) {
|
|
581
|
-
const { interaction } = this;
|
|
582
|
-
if (interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
583
|
-
await interaction.showModal(data);
|
|
584
|
-
}
|
|
585
|
-
guardModalSource() {
|
|
586
|
-
if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
587
|
-
}
|
|
588
|
-
rejectModalSubmit() {
|
|
589
|
-
throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
|
|
590
|
-
"showModal",
|
|
591
|
-
"this interaction is a modal submit",
|
|
592
|
-
"A modal cannot open another modal.",
|
|
593
|
-
this.routeId
|
|
594
|
-
]);
|
|
595
|
-
}
|
|
596
|
-
sourceInteraction(method) {
|
|
597
|
-
const { interaction } = this;
|
|
598
|
-
if (interaction.isMessageComponent()) return interaction;
|
|
599
|
-
if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
|
|
600
|
-
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
601
|
-
}
|
|
602
|
-
replyOptions(response, flags) {
|
|
603
|
-
const reply = this.serialize(response);
|
|
604
|
-
return {
|
|
605
|
-
components: reply.components,
|
|
606
|
-
flags,
|
|
607
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
608
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
editOptions(response) {
|
|
612
|
-
const reply = this.serialize(response);
|
|
613
|
-
return {
|
|
614
|
-
components: reply.components,
|
|
615
|
-
flags: MessageFlags.IsComponentsV2,
|
|
616
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
617
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
updateOptions(response) {
|
|
621
|
-
const reply = this.serialize(response);
|
|
622
|
-
return {
|
|
623
|
-
components: reply.components,
|
|
624
|
-
flags: MessageFlags.IsComponentsV2,
|
|
625
|
-
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
626
|
-
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
createdMessage(result) {
|
|
630
|
-
return result.resource?.message ?? void 0;
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
|
|
634
551
|
//#endregion
|
|
635
552
|
//#region src/handlers/RepliableHandler.ts
|
|
636
553
|
/**
|
|
@@ -647,8 +564,9 @@ var RepliableHandler = class extends RepliableHandler$1 {
|
|
|
647
564
|
getEvent() {
|
|
648
565
|
return this.event;
|
|
649
566
|
}
|
|
650
|
-
|
|
651
|
-
|
|
567
|
+
/** The user who sent this interaction. */
|
|
568
|
+
get user() {
|
|
569
|
+
return this.event.user;
|
|
652
570
|
}
|
|
653
571
|
};
|
|
654
572
|
|
|
@@ -691,8 +609,8 @@ var InteractionMiddleware = class extends RepliableHandler {
|
|
|
691
609
|
* @typeParam Repliable - The interaction type this handler processes
|
|
692
610
|
*/
|
|
693
611
|
var InteractionHandler = class extends RepliableHandler {
|
|
694
|
-
constructor(event, core, dispatch) {
|
|
695
|
-
super(event, core, dispatch);
|
|
612
|
+
constructor(event, core, dispatch, sender) {
|
|
613
|
+
super(event, core, dispatch, sender);
|
|
696
614
|
}
|
|
697
615
|
/**
|
|
698
616
|
* Open a modal. Must be the initial response to this interaction. The modal kind rejects this call at
|
|
@@ -703,14 +621,6 @@ var InteractionHandler = class extends RepliableHandler {
|
|
|
703
621
|
}
|
|
704
622
|
};
|
|
705
623
|
|
|
706
|
-
//#endregion
|
|
707
|
-
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
708
|
-
function slashRouteOf(interaction) {
|
|
709
|
-
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
710
|
-
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
711
|
-
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
624
|
//#endregion
|
|
715
625
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
716
626
|
/**
|
|
@@ -718,7 +628,7 @@ function slashRouteOf(interaction) {
|
|
|
718
628
|
*
|
|
719
629
|
* Pass the route from the generated registry as the generic, the same string as `@SlashRoute`, then read
|
|
720
630
|
* `this.options` for a single command or `this.match` for several. Command authoring stays plain
|
|
721
|
-
* discord.js
|
|
631
|
+
* discord.js. `seedcord codegen` reads its `toJSON()` to type these options.
|
|
722
632
|
*
|
|
723
633
|
* @typeParam Route - One or more route keys from {@link SlashRegistry}, e.g. `'ban'` or `'ban' | 'kick'`.
|
|
724
634
|
* @typeParam Cache - The interaction cache state. The command's contexts set it.
|
|
@@ -735,36 +645,32 @@ function slashRouteOf(interaction) {
|
|
|
735
645
|
* ```
|
|
736
646
|
*/
|
|
737
647
|
var SlashHandler = class extends InteractionHandler {
|
|
738
|
-
/** The selected command's path, such as `search/docs`. */
|
|
739
|
-
get route() {
|
|
740
|
-
return slashRouteOf(this.event);
|
|
741
|
-
}
|
|
742
648
|
/**
|
|
743
649
|
* The typed options for this command's route. Only the getters for kinds this command declares appear.
|
|
744
650
|
* Each getter's name argument is restricted to that command's options of its kind.
|
|
745
651
|
*
|
|
746
|
-
* - A required option
|
|
652
|
+
* - A required option's getter never returns `null`.
|
|
747
653
|
*
|
|
748
654
|
* - Choices narrow to their literal union.
|
|
749
655
|
*
|
|
750
656
|
* - A channel option declared with `addChannelTypes` narrows to those types.
|
|
751
657
|
*
|
|
752
658
|
* Use `this.event.options` directly for anything outside this view, such as `getSubcommand()`.
|
|
753
|
-
* However, for the common case, the methods in this would suffice.
|
|
754
659
|
*/
|
|
755
660
|
get options() {
|
|
756
661
|
return this.event.options;
|
|
757
662
|
}
|
|
758
663
|
/**
|
|
759
|
-
* Run the
|
|
664
|
+
* Run the case for the selected command. Use this only when the handler is registered for several
|
|
760
665
|
* routes.
|
|
761
666
|
*
|
|
762
|
-
* Provide one
|
|
763
|
-
* own narrowed options. The
|
|
667
|
+
* Provide one case per registered route, keyed by its route string, and each case receives that route's
|
|
668
|
+
* own narrowed options. The cases must cover every route in the generic. A missing route or an unknown
|
|
764
669
|
* key is a compile error.
|
|
765
670
|
*
|
|
766
|
-
* @param
|
|
767
|
-
* @
|
|
671
|
+
* @param cases - One handler per registered route, keyed by route string.
|
|
672
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
673
|
+
* @returns The result of the case that ran.
|
|
768
674
|
*
|
|
769
675
|
* @example
|
|
770
676
|
* ```ts
|
|
@@ -779,29 +685,13 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
779
685
|
* }
|
|
780
686
|
* ```
|
|
781
687
|
*/
|
|
782
|
-
async match(
|
|
783
|
-
|
|
784
|
-
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
785
|
-
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
786
|
-
this.constructor.name,
|
|
787
|
-
"command route",
|
|
788
|
-
route
|
|
789
|
-
]);
|
|
790
|
-
return await arm(this.options);
|
|
688
|
+
async match(cases) {
|
|
689
|
+
return await runCase(this, cases, "command route", this.route, this.options);
|
|
791
690
|
}
|
|
792
691
|
};
|
|
793
692
|
|
|
794
693
|
//#endregion
|
|
795
694
|
//#region src/handlers/interaction/ContextMenuHandler.ts
|
|
796
|
-
function armFor(handler, arms, name) {
|
|
797
|
-
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
798
|
-
if (typeof arm !== "function") throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
799
|
-
handler.constructor.name,
|
|
800
|
-
"context menu command",
|
|
801
|
-
name
|
|
802
|
-
]);
|
|
803
|
-
return arm;
|
|
804
|
-
}
|
|
805
695
|
/**
|
|
806
696
|
* Base class for a user context-menu command handler (right-click a user).
|
|
807
697
|
*
|
|
@@ -829,21 +719,18 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
829
719
|
get targetMember() {
|
|
830
720
|
return this.event.targetMember;
|
|
831
721
|
}
|
|
832
|
-
/** The selected context-menu command's name, without the interaction-kind prefix. */
|
|
833
|
-
get route() {
|
|
834
|
-
return this.event.commandName;
|
|
835
|
-
}
|
|
836
722
|
/**
|
|
837
|
-
* Run the
|
|
723
|
+
* Run the case for the selected command. Use this only when the handler is registered for several
|
|
838
724
|
* names.
|
|
839
725
|
*
|
|
840
|
-
* Provide one
|
|
841
|
-
*
|
|
842
|
-
* one command. The
|
|
726
|
+
* Provide one case per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
727
|
+
* case receives the clicked user and that user's guild member, both narrowed to the cache state of that
|
|
728
|
+
* one command. The cases must cover every name in the generic. A missing name or an unknown key is a
|
|
843
729
|
* compile error.
|
|
844
730
|
*
|
|
845
|
-
* @param
|
|
846
|
-
* @
|
|
731
|
+
* @param cases - One handler per registered command name.
|
|
732
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
733
|
+
* @returns The result of the case that ran.
|
|
847
734
|
*
|
|
848
735
|
* @example
|
|
849
736
|
* ```ts
|
|
@@ -858,8 +745,8 @@ var UserContextMenuHandler = class extends InteractionHandler {
|
|
|
858
745
|
* }
|
|
859
746
|
* ```
|
|
860
747
|
*/
|
|
861
|
-
async match(
|
|
862
|
-
return await
|
|
748
|
+
async match(cases) {
|
|
749
|
+
return await runCase(this, cases, "context menu command", this.route, this.target, this.targetMember);
|
|
863
750
|
}
|
|
864
751
|
};
|
|
865
752
|
/**
|
|
@@ -885,20 +772,17 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
885
772
|
get target() {
|
|
886
773
|
return this.event.targetMessage;
|
|
887
774
|
}
|
|
888
|
-
/** The selected context-menu command's name, without the interaction-kind prefix. */
|
|
889
|
-
get route() {
|
|
890
|
-
return this.event.commandName;
|
|
891
|
-
}
|
|
892
775
|
/**
|
|
893
|
-
* Run the
|
|
776
|
+
* Run the case for the selected command. Use this only when the handler is registered for several
|
|
894
777
|
* names.
|
|
895
778
|
*
|
|
896
|
-
* Provide one
|
|
897
|
-
*
|
|
779
|
+
* Provide one case per registered name, keyed by the name Discord shows in the right-click menu. Each
|
|
780
|
+
* case receives the clicked message, narrowed to the cache state of that one command. The cases must
|
|
898
781
|
* cover every name in the generic. A missing name or an unknown key is a compile error.
|
|
899
782
|
*
|
|
900
|
-
* @param
|
|
901
|
-
* @
|
|
783
|
+
* @param cases - One handler per registered command name.
|
|
784
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
785
|
+
* @returns The result of the case that ran.
|
|
902
786
|
*
|
|
903
787
|
* @example
|
|
904
788
|
* ```ts
|
|
@@ -913,8 +797,8 @@ var MessageContextMenuHandler = class extends InteractionHandler {
|
|
|
913
797
|
* }
|
|
914
798
|
* ```
|
|
915
799
|
*/
|
|
916
|
-
async match(
|
|
917
|
-
return await
|
|
800
|
+
async match(cases) {
|
|
801
|
+
return await runCase(this, cases, "context menu command", this.route, this.target);
|
|
918
802
|
}
|
|
919
803
|
};
|
|
920
804
|
|
|
@@ -955,8 +839,14 @@ var BaseHandler = class extends BaseHandler$1 {
|
|
|
955
839
|
* ```
|
|
956
840
|
*/
|
|
957
841
|
var AutocompleteHandler = class extends BaseHandler {
|
|
958
|
-
constructor(event, core, dispatch) {
|
|
842
|
+
constructor(event, core, dispatch, responder) {
|
|
959
843
|
super(event, core, dispatch, "interactions");
|
|
844
|
+
this.#responder = responder;
|
|
845
|
+
}
|
|
846
|
+
#responder;
|
|
847
|
+
/** The user who sent this interaction. */
|
|
848
|
+
get user() {
|
|
849
|
+
return this.event.user;
|
|
960
850
|
}
|
|
961
851
|
decodedFocused;
|
|
962
852
|
/**
|
|
@@ -974,16 +864,17 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
974
864
|
return focused;
|
|
975
865
|
}
|
|
976
866
|
/**
|
|
977
|
-
* Run the
|
|
867
|
+
* Run the case for the focused field. Because an autocomplete always dispatches by which field is
|
|
978
868
|
* focused, match is how you read it. There is no single-field shortcut the way slash/component handlers have one.
|
|
979
869
|
*
|
|
980
|
-
* Provide one
|
|
981
|
-
*
|
|
982
|
-
*
|
|
870
|
+
* Provide one case per autocompletable field across the registered commands, keyed by field name. Each
|
|
871
|
+
* case receives the focused partial value and a `respond` typed to that field's choice value. The
|
|
872
|
+
* cases must cover every autocompletable field. A missing field or an unknown key is a compile error.
|
|
983
873
|
*
|
|
984
|
-
* @param
|
|
985
|
-
* @
|
|
986
|
-
* @
|
|
874
|
+
* @param cases - One handler per autocompletable field, keyed by field name.
|
|
875
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
876
|
+
* @returns The result of the case that ran.
|
|
877
|
+
* @throws A **SeedcordTypeError** with `MatchCaseMissing` when the focused field doesn't have a case. Only a stale-deployed command can send one.
|
|
987
878
|
*
|
|
988
879
|
* @example
|
|
989
880
|
* ```ts
|
|
@@ -998,28 +889,14 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
998
889
|
* }
|
|
999
890
|
* ```
|
|
1000
891
|
*/
|
|
1001
|
-
async match(
|
|
892
|
+
async match(cases) {
|
|
1002
893
|
const { name, value } = this.focused;
|
|
1003
894
|
const respond = (choices) => this.respond(choices);
|
|
1004
|
-
|
|
1005
|
-
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
1006
|
-
this.constructor.name,
|
|
1007
|
-
"focused field",
|
|
1008
|
-
name
|
|
1009
|
-
]);
|
|
1010
|
-
return await arm(value, respond);
|
|
895
|
+
return await runCase(this, cases, "focused field", name, value, respond);
|
|
1011
896
|
}
|
|
1012
897
|
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
bus: this.core.bus,
|
|
1016
|
-
dispatch: this.dispatch,
|
|
1017
|
-
interactionId: this.event.id
|
|
1018
|
-
}, "respond", () => this.event.respond(choices));
|
|
1019
|
-
}
|
|
1020
|
-
/** The selected command's path, such as `search/docs`, without the interaction-kind prefix. */
|
|
1021
|
-
get route() {
|
|
1022
|
-
return slashRouteOf(this.event);
|
|
898
|
+
respond(choices) {
|
|
899
|
+
return this.#responder.respond(choices);
|
|
1023
900
|
}
|
|
1024
901
|
/**
|
|
1025
902
|
* The already-entered options on this command, restricted to the kinds Discord resolves during autocomplete
|
|
@@ -1057,15 +934,6 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1057
934
|
return this.decoded;
|
|
1058
935
|
}
|
|
1059
936
|
/**
|
|
1060
|
-
* The matched custom-ID prefix, such as `approve`, without encoded params.
|
|
1061
|
-
*
|
|
1062
|
-
* @throws A **StaleCustomId** when the wire was encoded under an older shape.
|
|
1063
|
-
* @throws An **InvalidCustomId** when the wire is corrupt.
|
|
1064
|
-
*/
|
|
1065
|
-
get route() {
|
|
1066
|
-
return this.decodedRoute.prefix;
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
937
|
* The decoded params of the single route this handler is registered for.
|
|
1070
938
|
*
|
|
1071
939
|
* Reading this decodes `this.event.customId` once and caches the result. On a handler registered
|
|
@@ -1078,17 +946,18 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1078
946
|
return this.decodedRoute.params;
|
|
1079
947
|
}
|
|
1080
948
|
/**
|
|
1081
|
-
* Run the
|
|
1082
|
-
* registered for several routes.
|
|
949
|
+
* Run the case for whichever route the component was minted from. Use this only when the handler is
|
|
950
|
+
* registered for several routes. On a single-route handler, read `this.params` directly. On a multi-route
|
|
1083
951
|
* handler `this.params` is `never`, leaving match as the only way to read the decoded params.
|
|
1084
952
|
*
|
|
1085
|
-
* Provide one
|
|
1086
|
-
* decoded params. The compiler checks that the
|
|
1087
|
-
* decodes the wire before it runs
|
|
953
|
+
* Provide one case per registered route, keyed by its prefix, and each case receives that route's own
|
|
954
|
+
* decoded params. The compiler checks that the cases cover every registered route prefix. `match`
|
|
955
|
+
* decodes the wire before it runs a case.
|
|
1088
956
|
*
|
|
1089
|
-
* @param
|
|
1090
|
-
* @
|
|
1091
|
-
* @
|
|
957
|
+
* @param cases - One callback per registered route, keyed by prefix.
|
|
958
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
959
|
+
* @returns The result of the case that ran.
|
|
960
|
+
* @throws A **SeedcordTypeError** with `MatchCaseMissing` when the wire's prefix doesn't have a case.
|
|
1092
961
|
* @throws A **StaleCustomId** when the wire was encoded under an older shape.
|
|
1093
962
|
* @throws An **InvalidCustomId** when the wire is corrupt.
|
|
1094
963
|
*
|
|
@@ -1105,15 +974,9 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1105
974
|
* }
|
|
1106
975
|
* ```
|
|
1107
976
|
*/
|
|
1108
|
-
async match(
|
|
977
|
+
async match(cases) {
|
|
1109
978
|
const { prefix, params } = this.decodedRoute;
|
|
1110
|
-
|
|
1111
|
-
if (!arm) throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
1112
|
-
this.constructor.name,
|
|
1113
|
-
"component route",
|
|
1114
|
-
prefix
|
|
1115
|
-
]);
|
|
1116
|
-
return await arm(params);
|
|
979
|
+
return await runCase(this, cases, "component route", prefix, params);
|
|
1117
980
|
}
|
|
1118
981
|
};
|
|
1119
982
|
|
|
@@ -1348,8 +1211,8 @@ var MentionableMenuHandler = class extends SelectMenuHandler {
|
|
|
1348
1211
|
/**
|
|
1349
1212
|
* Base class for a Discord client event handler.
|
|
1350
1213
|
*
|
|
1351
|
-
* Pass the event name(s) as the generic, the same one(s) as `@RegisterEvent`.
|
|
1352
|
-
* `this.event` (the payload tuple) directly.
|
|
1214
|
+
* Pass the event name(s) as the generic, the same one(s) as `@RegisterEvent`. On a single-event handler, read
|
|
1215
|
+
* `this.event` (the payload tuple) directly. On a handler registered for several events, branch with `this.match`,
|
|
1353
1216
|
* keyed by event name, since the union of payload tuples is not directly readable.
|
|
1354
1217
|
*
|
|
1355
1218
|
* @typeParam Names - One or more `ClientEvents` keys, e.g. `Events.MessageCreate` or `Events.MessageCreate | Events.MessageUpdate`.
|
|
@@ -1368,22 +1231,21 @@ var MentionableMenuHandler = class extends SelectMenuHandler {
|
|
|
1368
1231
|
* ```
|
|
1369
1232
|
*/
|
|
1370
1233
|
var EventHandler = class extends BaseHandler {
|
|
1371
|
-
|
|
1372
|
-
constructor(event, core, dispatch, eventName) {
|
|
1234
|
+
constructor(event, core, dispatch) {
|
|
1373
1235
|
super(event, core, dispatch, "events");
|
|
1374
|
-
this.firedEvent = eventName;
|
|
1375
1236
|
}
|
|
1376
1237
|
/**
|
|
1377
|
-
* Run the
|
|
1378
|
-
* events.
|
|
1238
|
+
* Run the case for the current event. Use this only when the handler is registered for several
|
|
1239
|
+
* events. On a single-event handler, read `this.event` directly. On a multi-event handler `this.event` is
|
|
1379
1240
|
* `never`, so match is the only way to read the payload.
|
|
1380
1241
|
*
|
|
1381
|
-
* Provide one
|
|
1382
|
-
* payload tuple. The
|
|
1242
|
+
* Provide one case per registered event, keyed by its name, and each case receives that event's own
|
|
1243
|
+
* payload tuple. The cases must cover every event in the generic. A missing event or an unknown key is a
|
|
1383
1244
|
* compile error.
|
|
1384
1245
|
*
|
|
1385
|
-
* @param
|
|
1386
|
-
* @
|
|
1246
|
+
* @param cases - One handler per registered event, keyed by event name.
|
|
1247
|
+
* @typeParam Ret - The type that every case must return. Leave it out to get the union of what the cases return.
|
|
1248
|
+
* @returns The result of the case that ran.
|
|
1387
1249
|
*
|
|
1388
1250
|
* @example
|
|
1389
1251
|
* ```ts
|
|
@@ -1398,16 +1260,8 @@ var EventHandler = class extends BaseHandler {
|
|
|
1398
1260
|
* }
|
|
1399
1261
|
* ```
|
|
1400
1262
|
*/
|
|
1401
|
-
async match(
|
|
1402
|
-
|
|
1403
|
-
if (name === void 0) throw new SeedcordError(SeedcordErrorCode.EventNameUnavailable, [this.constructor.name, "match()"]);
|
|
1404
|
-
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
1405
|
-
if (typeof arm !== "function") throw new SeedcordTypeError(SeedcordErrorCode.MatchArmMissing, [
|
|
1406
|
-
this.constructor.name,
|
|
1407
|
-
"event",
|
|
1408
|
-
name
|
|
1409
|
-
]);
|
|
1410
|
-
return await arm(...this.getEvent());
|
|
1263
|
+
async match(cases) {
|
|
1264
|
+
return await runCase(this, cases, "event", this.route, ...this.getEvent());
|
|
1411
1265
|
}
|
|
1412
1266
|
};
|
|
1413
1267
|
|
|
@@ -1418,26 +1272,135 @@ var EventHandler = class extends BaseHandler {
|
|
|
1418
1272
|
*
|
|
1419
1273
|
* Middleware runs before event handlers and can stop the event by throwing a `Silence`. Unlike `EventHandler`, it
|
|
1420
1274
|
* runs the SAME for every event it is registered for, so it has no `match`. Specify a single event in the
|
|
1421
|
-
* generic and `{ events }` to read `this.event` fully typed.
|
|
1422
|
-
* catchall)
|
|
1423
|
-
* payload shape.
|
|
1275
|
+
* generic and `{ events }` to read `this.event` fully typed. If you span several events (or omit `{ events }` for
|
|
1276
|
+
* a catchall), `this.event` narrows to `never`. Read `this.route` there, for work that doesn't depend on the
|
|
1277
|
+
* payload shape. Write one class per event for a middleware that needs each event's payload.
|
|
1424
1278
|
*
|
|
1425
1279
|
* @typeParam EventName - One or more `ClientEvents` keys. Defaults to every event (catchall).
|
|
1426
1280
|
*/
|
|
1427
1281
|
var EventMiddleware = class extends BaseHandler {
|
|
1428
|
-
|
|
1429
|
-
constructor(event, core, dispatch, eventName) {
|
|
1282
|
+
constructor(event, core, dispatch) {
|
|
1430
1283
|
super(event, core, dispatch, "events");
|
|
1431
|
-
this.firedEvent = eventName;
|
|
1432
1284
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
//#endregion
|
|
1288
|
+
//#region src/bot/ReplySender.ts
|
|
1289
|
+
function isReplyFile(file) {
|
|
1290
|
+
return typeof file === "object" && "data" in file && ArrayBuffer.isView(file.data);
|
|
1291
|
+
}
|
|
1292
|
+
function toDjsFile(file) {
|
|
1293
|
+
if (!isReplyFile(file)) return file;
|
|
1294
|
+
return {
|
|
1295
|
+
attachment: Buffer.from(file.data),
|
|
1296
|
+
name: file.name,
|
|
1297
|
+
...file.description !== void 0 && { description: file.description },
|
|
1298
|
+
...file.title !== void 0 && { title: file.title }
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
function seedState(interaction) {
|
|
1302
|
+
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
1303
|
+
if (interaction.replied) return "replied";
|
|
1304
|
+
if (interaction.deferred) return "deferred-reply";
|
|
1305
|
+
return "unacked";
|
|
1306
|
+
}
|
|
1307
|
+
/** Writes interaction responses through discord.js. The repliable handler bases and the dispatcher construct it. */
|
|
1308
|
+
var ReplySender = class extends BaseReplySender {
|
|
1309
|
+
interaction;
|
|
1310
|
+
constructor(interaction, dispatch, bus) {
|
|
1311
|
+
super({
|
|
1312
|
+
bus,
|
|
1313
|
+
dispatch,
|
|
1314
|
+
interactionId: interaction.id
|
|
1315
|
+
}, seedState(interaction));
|
|
1316
|
+
this.interaction = interaction;
|
|
1317
|
+
}
|
|
1318
|
+
async writeReply(response, opts) {
|
|
1319
|
+
const result = await this.interaction.reply({
|
|
1320
|
+
...this.replyOptions(response, sendFlags(opts)),
|
|
1321
|
+
withResponse: true
|
|
1322
|
+
});
|
|
1323
|
+
return this.createdMessage(result);
|
|
1324
|
+
}
|
|
1325
|
+
async writeDefer(opts) {
|
|
1326
|
+
await this.interaction.deferReply({ flags: deferFlags(opts) });
|
|
1327
|
+
}
|
|
1328
|
+
async writeDeferUpdate() {
|
|
1329
|
+
await this.sourceInteraction("deferUpdate").deferUpdate();
|
|
1330
|
+
}
|
|
1331
|
+
async writeUpdate(response) {
|
|
1332
|
+
const result = await this.sourceInteraction("update").update({
|
|
1333
|
+
...this.updateOptions(response),
|
|
1334
|
+
withResponse: true
|
|
1335
|
+
});
|
|
1336
|
+
return this.createdMessage(result);
|
|
1337
|
+
}
|
|
1338
|
+
async writeFollowUp(response, opts) {
|
|
1339
|
+
return await this.interaction.followUp(this.replyOptions(response, sendFlags(opts)));
|
|
1340
|
+
}
|
|
1341
|
+
async writeEditOriginal(response) {
|
|
1342
|
+
return await this.interaction.editReply(this.editOptions(response));
|
|
1343
|
+
}
|
|
1344
|
+
async writeEditTarget(targetId, response) {
|
|
1345
|
+
return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
|
|
1346
|
+
}
|
|
1347
|
+
async writeDeleteOriginal() {
|
|
1348
|
+
await this.interaction.deleteReply();
|
|
1349
|
+
}
|
|
1350
|
+
async writeDeleteTarget(targetId) {
|
|
1351
|
+
await this.interaction.deleteReply(targetId);
|
|
1352
|
+
}
|
|
1353
|
+
async writeModal(data) {
|
|
1354
|
+
const { interaction } = this;
|
|
1355
|
+
if (interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
1356
|
+
await interaction.showModal(data);
|
|
1357
|
+
}
|
|
1358
|
+
guardModalSource() {
|
|
1359
|
+
if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
1360
|
+
}
|
|
1361
|
+
rejectModalSubmit() {
|
|
1362
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyIllegalAckState, [
|
|
1363
|
+
"showModal",
|
|
1364
|
+
"this interaction is a modal submit",
|
|
1365
|
+
"A modal cannot open another modal.",
|
|
1366
|
+
this.routeId
|
|
1367
|
+
]);
|
|
1368
|
+
}
|
|
1369
|
+
sourceInteraction(method) {
|
|
1370
|
+
const { interaction } = this;
|
|
1371
|
+
if (interaction.isMessageComponent()) return interaction;
|
|
1372
|
+
if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
|
|
1373
|
+
throw new SeedcordError(SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
1374
|
+
}
|
|
1375
|
+
replyOptions(response, flags) {
|
|
1376
|
+
const reply = this.serialize(response);
|
|
1377
|
+
return {
|
|
1378
|
+
components: reply.components,
|
|
1379
|
+
flags,
|
|
1380
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
1381
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
editOptions(response) {
|
|
1385
|
+
const reply = this.serialize(response);
|
|
1386
|
+
return {
|
|
1387
|
+
components: reply.components,
|
|
1388
|
+
flags: MessageFlags.IsComponentsV2,
|
|
1389
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
1390
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
updateOptions(response) {
|
|
1394
|
+
const reply = this.serialize(response);
|
|
1395
|
+
return {
|
|
1396
|
+
components: reply.components,
|
|
1397
|
+
flags: MessageFlags.IsComponentsV2,
|
|
1398
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
1399
|
+
...reply.files && { files: reply.files.map(toDjsFile) }
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
createdMessage(result) {
|
|
1403
|
+
return result.resource?.message ?? void 0;
|
|
1441
1404
|
}
|
|
1442
1405
|
};
|
|
1443
1406
|
|
|
@@ -1530,14 +1493,14 @@ var CursorSource = class extends CursorSourceBase {};
|
|
|
1530
1493
|
/**
|
|
1531
1494
|
* Base class for a gateway subscriber, binding `this.core` to the gateway {@link Core}.
|
|
1532
1495
|
*
|
|
1533
|
-
* @typeParam KeyOfSubscribers - The subscription key this handler receives
|
|
1496
|
+
* @typeParam KeyOfSubscribers - The subscription key or keys that this handler receives, the same ones as `@Subscribe`
|
|
1534
1497
|
*/
|
|
1535
1498
|
var Subscriber = class extends Subscriber$1 {};
|
|
1536
1499
|
/**
|
|
1537
1500
|
* Base class for a gateway subscriber that delivers its event to a Discord webhook, binding
|
|
1538
1501
|
* `this.core` to the gateway {@link Core}.
|
|
1539
1502
|
*
|
|
1540
|
-
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
1503
|
+
* @typeParam KeyOfSubscribers - The subscription key or keys that this reporter receives, the same ones as `@Subscribe`
|
|
1541
1504
|
*/
|
|
1542
1505
|
var WebhookLog = class extends WebhookLog$1 {};
|
|
1543
1506
|
|
|
@@ -1550,8 +1513,8 @@ const GATEWAY_SCOPE = {
|
|
|
1550
1513
|
/**
|
|
1551
1514
|
* Base class for a gateway plugin, binding `this.core` to the gateway {@link Core}.
|
|
1552
1515
|
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1516
|
+
* To run on either transport, extend the base from `@seedcord/core/plugin`. Its `this.core` carries
|
|
1517
|
+
* only the shared members.
|
|
1555
1518
|
*
|
|
1556
1519
|
* @typeParam Opts - The transport and runtime the plugin declares.
|
|
1557
1520
|
*/
|
|
@@ -1562,15 +1525,23 @@ var Plugin = class extends Plugin$1 {
|
|
|
1562
1525
|
|
|
1563
1526
|
//#endregion
|
|
1564
1527
|
//#region src/miscellaneous/deriveEventActor.ts
|
|
1528
|
+
function actorOf(message) {
|
|
1529
|
+
return {
|
|
1530
|
+
guild: message.guild,
|
|
1531
|
+
member: message.member,
|
|
1532
|
+
user: message.author,
|
|
1533
|
+
channelId: message.channelId
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1565
1536
|
function deriveEventActor(args) {
|
|
1566
1537
|
const tuple = Array.isArray(args) ? args : [args];
|
|
1538
|
+
let partial;
|
|
1567
1539
|
for (const arg of tuple) {
|
|
1568
|
-
if (arg instanceof Message)
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
};
|
|
1540
|
+
if (arg instanceof Message) {
|
|
1541
|
+
const message = arg;
|
|
1542
|
+
if (!message.partial) return actorOf(message);
|
|
1543
|
+
partial ??= message;
|
|
1544
|
+
}
|
|
1574
1545
|
if (arg instanceof GuildMember) return {
|
|
1575
1546
|
guild: arg.guild,
|
|
1576
1547
|
member: arg,
|
|
@@ -1584,7 +1555,7 @@ function deriveEventActor(args) {
|
|
|
1584
1555
|
channelId: null
|
|
1585
1556
|
};
|
|
1586
1557
|
}
|
|
1587
|
-
return {
|
|
1558
|
+
return partial ? actorOf(partial) : {
|
|
1588
1559
|
guild: null,
|
|
1589
1560
|
member: null,
|
|
1590
1561
|
user: null,
|
|
@@ -1593,7 +1564,8 @@ function deriveEventActor(args) {
|
|
|
1593
1564
|
}
|
|
1594
1565
|
|
|
1595
1566
|
//#endregion
|
|
1596
|
-
//#region src/bot/gates/
|
|
1567
|
+
//#region src/bot/gates/context.ts
|
|
1568
|
+
const NO_PERMISSIONS = 0n;
|
|
1597
1569
|
function interactionGateContext(interaction, core, dispatch) {
|
|
1598
1570
|
const rawMember = interaction.member;
|
|
1599
1571
|
const memberRoleIds = rawMember instanceof GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
|
|
@@ -1612,8 +1584,7 @@ function interactionGateContext(interaction, core, dispatch) {
|
|
|
1612
1584
|
memberPermissions: interaction.memberPermissions?.bitfield ?? null,
|
|
1613
1585
|
appPermissions: interaction.appPermissions.bitfield,
|
|
1614
1586
|
memberGuildPermissions: rawMember instanceof GuildMember ? rawMember.permissions.bitfield : null,
|
|
1615
|
-
appGuildPermissions: interaction.guild
|
|
1616
|
-
declaredRoute: null
|
|
1587
|
+
appGuildPermissions: interaction.guild ? interaction.guild.members.me?.permissions.bitfield ?? null : NO_PERMISSIONS
|
|
1617
1588
|
};
|
|
1618
1589
|
}
|
|
1619
1590
|
function eventGateContext(eventName, args, core, dispatch) {
|
|
@@ -1632,161 +1603,76 @@ function eventGateContext(eventName, args, core, dispatch) {
|
|
|
1632
1603
|
channelId: actor.channelId,
|
|
1633
1604
|
memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
|
|
1634
1605
|
memberPermissions: actor.member?.permissions.bitfield ?? null,
|
|
1635
|
-
appPermissions: null,
|
|
1636
1606
|
memberGuildPermissions: actor.member?.permissions.bitfield ?? null,
|
|
1637
|
-
appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null
|
|
1638
|
-
declaredRoute: null
|
|
1607
|
+
appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null
|
|
1639
1608
|
};
|
|
1640
1609
|
}
|
|
1641
1610
|
|
|
1642
1611
|
//#endregion
|
|
1643
|
-
//#region src/miscellaneous/
|
|
1644
|
-
|
|
1645
|
-
function extractErrorResponse(error, core, fault) {
|
|
1646
|
-
const uuid = crypto.randomUUID();
|
|
1647
|
-
const { dispatch } = fault;
|
|
1648
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
1649
|
-
const ctx = developerUsername === void 0 ? {
|
|
1650
|
-
uuid,
|
|
1651
|
-
dispatch
|
|
1652
|
-
} : {
|
|
1653
|
-
uuid,
|
|
1654
|
-
developerUsername,
|
|
1655
|
-
dispatch
|
|
1656
|
-
};
|
|
1657
|
-
if (error instanceof Notice) {
|
|
1658
|
-
if (error.report) reportFault(error, core, fault, uuid);
|
|
1659
|
-
return {
|
|
1660
|
-
uuid,
|
|
1661
|
-
response: error.render(ctx)
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
reportRawFault(error, core, fault, uuid);
|
|
1665
|
-
const Override = core.config.errors?.defaultError;
|
|
1612
|
+
//#region src/miscellaneous/faultReports.ts
|
|
1613
|
+
function interactionFaultReport(interaction, dispatch) {
|
|
1666
1614
|
return {
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
source: buildInteractionSource(fault.interaction)
|
|
1679
|
-
});
|
|
1680
|
-
else if (fault.event) core.bus[PublishDefault]("handledException", {
|
|
1681
|
-
denial,
|
|
1682
|
-
uuid,
|
|
1683
|
-
dispatchId: fault.dispatch.id,
|
|
1684
|
-
origin: fault.origin,
|
|
1685
|
-
source: buildEventSource(fault.event, fault)
|
|
1686
|
-
});
|
|
1687
|
-
else core.bus[PublishDefault]("unknownException", {
|
|
1688
|
-
uuid,
|
|
1689
|
-
dispatchId: fault.dispatch.id,
|
|
1690
|
-
error: denial,
|
|
1691
|
-
origin: fault.origin,
|
|
1692
|
-
...scalarActors(fault),
|
|
1693
|
-
metadata: metadataFor(fault)
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
function reportRawFault(error, core, fault, uuid) {
|
|
1697
|
-
logUnknownFault(logger$2, uuid, error, core.config.errors);
|
|
1698
|
-
core.bus[PublishDefault]("unknownException", {
|
|
1699
|
-
uuid,
|
|
1700
|
-
dispatchId: fault.dispatch.id,
|
|
1701
|
-
error,
|
|
1702
|
-
origin: fault.origin,
|
|
1703
|
-
...scalarActors(fault),
|
|
1704
|
-
metadata: metadataFor(fault)
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
function scalarActors(fault) {
|
|
1708
|
-
return {
|
|
1709
|
-
guild: fault.guild ? {
|
|
1710
|
-
id: fault.guild.id,
|
|
1711
|
-
name: fault.guild.name
|
|
1712
|
-
} : void 0,
|
|
1713
|
-
user: fault.user ? {
|
|
1714
|
-
id: fault.user.id,
|
|
1715
|
-
username: fault.user.username
|
|
1716
|
-
} : void 0
|
|
1717
|
-
};
|
|
1718
|
-
}
|
|
1719
|
-
function metadataFor(fault) {
|
|
1720
|
-
if (fault.event) return {
|
|
1721
|
-
eventName: fault.event.name,
|
|
1722
|
-
handler: fault.event.handler,
|
|
1723
|
-
args: fault.event.args
|
|
1615
|
+
origin: dispatch.routeId,
|
|
1616
|
+
source: interactionFaultSource(dispatch, {
|
|
1617
|
+
customId: "customId" in interaction ? interaction.customId : null,
|
|
1618
|
+
userId: interaction.user.id,
|
|
1619
|
+
guildId: interaction.guildId,
|
|
1620
|
+
channelId: interaction.channelId,
|
|
1621
|
+
interactionId: interaction.id,
|
|
1622
|
+
raw: interaction
|
|
1623
|
+
}),
|
|
1624
|
+
...scalarActors(interaction.guild, interaction.user),
|
|
1625
|
+
metadata: interaction
|
|
1724
1626
|
};
|
|
1725
|
-
return fault.metadata;
|
|
1726
1627
|
}
|
|
1727
|
-
function
|
|
1628
|
+
function eventFaultReport(event) {
|
|
1728
1629
|
return {
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1630
|
+
origin: `event:${event.name}:${event.handler}`,
|
|
1631
|
+
source: {
|
|
1632
|
+
kind: "event",
|
|
1633
|
+
eventName: event.name,
|
|
1634
|
+
handler: event.handler,
|
|
1635
|
+
userId: event.user?.id ?? null,
|
|
1636
|
+
guildId: event.guild?.id ?? null,
|
|
1637
|
+
channelId: event.channelId,
|
|
1638
|
+
raw: event.args
|
|
1639
|
+
},
|
|
1640
|
+
...scalarActors(event.guild, event.user),
|
|
1641
|
+
metadata: {
|
|
1642
|
+
eventName: event.name,
|
|
1643
|
+
handler: event.handler,
|
|
1644
|
+
args: event.args
|
|
1645
|
+
}
|
|
1736
1646
|
};
|
|
1737
1647
|
}
|
|
1738
|
-
function
|
|
1739
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1740
|
-
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1648
|
+
function scalarActors(guild, user) {
|
|
1741
1649
|
return {
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
raw: interaction
|
|
1650
|
+
guild: guild ? {
|
|
1651
|
+
id: guild.id,
|
|
1652
|
+
name: guild.name
|
|
1653
|
+
} : void 0,
|
|
1654
|
+
user: user ? {
|
|
1655
|
+
id: user.id,
|
|
1656
|
+
username: user.username
|
|
1657
|
+
} : void 0
|
|
1751
1658
|
};
|
|
1752
1659
|
}
|
|
1753
|
-
function interactionKind(interaction) {
|
|
1754
|
-
if (interaction.isChatInputCommand()) return "slash";
|
|
1755
|
-
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1756
|
-
if (interaction.isButton()) return "button";
|
|
1757
|
-
if (interaction.isAnySelectMenu()) return "select";
|
|
1758
|
-
return "modal";
|
|
1759
|
-
}
|
|
1760
1660
|
|
|
1761
1661
|
//#endregion
|
|
1762
1662
|
//#region src/bot/handleEventFault.ts
|
|
1763
|
-
const logger$1 = new Logger("Faults", { channel: "errors" });
|
|
1764
1663
|
function handleEventFault(caught, fault, core) {
|
|
1765
|
-
const {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
return
|
|
1769
|
-
|
|
1770
|
-
const error = asError(caught);
|
|
1771
|
-
const ignore = new Set(core.config.errors?.ignoreEventApiCodes ?? []);
|
|
1772
|
-
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
1773
|
-
logger$1.debug(`swallowed api code ${error.code}`);
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
if (error instanceof Notice && !error.report) return;
|
|
1777
|
-
const actor = deriveEventActor(args);
|
|
1778
|
-
extractErrorResponse(error, core, {
|
|
1779
|
-
event: {
|
|
1780
|
-
name: eventName,
|
|
1664
|
+
const { handlerName, args, dispatch } = fault;
|
|
1665
|
+
new FaultBoundary(core, core.config.errors?.ignoreEventApiCodes, DiscordAPIError, dispatch, () => {
|
|
1666
|
+
const { channelId, guild, user } = deriveEventActor(args);
|
|
1667
|
+
return eventFaultReport({
|
|
1668
|
+
name: dispatch.route,
|
|
1781
1669
|
handler: handlerName,
|
|
1782
1670
|
args,
|
|
1783
|
-
channelId
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
user: actor.user
|
|
1789
|
-
});
|
|
1671
|
+
channelId,
|
|
1672
|
+
guild,
|
|
1673
|
+
user
|
|
1674
|
+
});
|
|
1675
|
+
}).report(caught);
|
|
1790
1676
|
}
|
|
1791
1677
|
|
|
1792
1678
|
//#endregion
|
|
@@ -1835,27 +1721,22 @@ var EventDispatcher = class {
|
|
|
1835
1721
|
middlewares = new MiddlewareRegistry(eventMiddleware);
|
|
1836
1722
|
onceRuns = new OnceTracker();
|
|
1837
1723
|
attachedEvents = /* @__PURE__ */ new Set();
|
|
1838
|
-
|
|
1839
|
-
loadedHandlers = [];
|
|
1840
|
-
loadedMiddlewares = [];
|
|
1841
|
-
hmrHandler;
|
|
1724
|
+
loader;
|
|
1842
1725
|
constructor(core, inFlight) {
|
|
1843
1726
|
this.core = core;
|
|
1844
1727
|
this.inFlight = inFlight;
|
|
1845
1728
|
const eventsDir = this.core.config.bot.events.path;
|
|
1846
1729
|
if (!eventsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["EventDispatcher", "events"]);
|
|
1847
|
-
|
|
1848
|
-
this.hmrHandler = new HmrModuleHandler$1({
|
|
1730
|
+
this.loader = new HandlerLoader({
|
|
1849
1731
|
handlersDir: eventsDir,
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
logger: this.logger
|
|
1732
|
+
middlewaresDir: hasKeys(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0,
|
|
1733
|
+
middlewares: this.middlewares,
|
|
1734
|
+
logger: this.logger,
|
|
1735
|
+
isHandler: (value) => this.isEventHandlerClass(value),
|
|
1736
|
+
isMiddleware: (value) => this.isMiddlewareClass(value),
|
|
1737
|
+
storeHandler: (ctor) => this.storeHandler(ctor),
|
|
1738
|
+
removeHandler: (ctor, events) => this.unregisterHandler(ctor, events),
|
|
1739
|
+
artifactsOf: (ctor) => this.getArtifacts(ctor)
|
|
1859
1740
|
});
|
|
1860
1741
|
}
|
|
1861
1742
|
getArtifacts(ctor) {
|
|
@@ -1866,49 +1747,25 @@ var EventDispatcher = class {
|
|
|
1866
1747
|
async init() {
|
|
1867
1748
|
if (this.isInitialized) return;
|
|
1868
1749
|
this.isInitialized = true;
|
|
1869
|
-
|
|
1870
|
-
if (!handlersDir) return;
|
|
1871
|
-
const middlewareDir = hasKeys(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0;
|
|
1872
|
-
this.loading = true;
|
|
1873
|
-
this.loadedHandlers.length = 0;
|
|
1874
|
-
this.loadedMiddlewares.length = 0;
|
|
1875
|
-
try {
|
|
1876
|
-
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
1877
|
-
await this.loadHandlers(handlersDir);
|
|
1878
|
-
} finally {
|
|
1879
|
-
this.loading = false;
|
|
1880
|
-
}
|
|
1750
|
+
await this.loader.load();
|
|
1881
1751
|
this.attachToClient();
|
|
1882
1752
|
this.reportLoad();
|
|
1883
1753
|
}
|
|
1884
1754
|
reportLoad() {
|
|
1885
1755
|
const { utils } = this.logger;
|
|
1756
|
+
const { loadedHandlers, loadedMiddlewares } = this.loader;
|
|
1886
1757
|
utils.summary("Loaded", {
|
|
1887
|
-
"event handlers":
|
|
1888
|
-
middlewares:
|
|
1758
|
+
"event handlers": loadedHandlers.length,
|
|
1759
|
+
middlewares: loadedMiddlewares.length
|
|
1889
1760
|
}, "debug");
|
|
1890
|
-
if (
|
|
1761
|
+
if (loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(loadedMiddlewares), "debug");
|
|
1891
1762
|
const perEvent = {};
|
|
1892
1763
|
for (const [event, handlers] of this.eventMap) perEvent[event] = handlers.length;
|
|
1893
|
-
utils.block("Loaded event handlers", [...utils.entries(
|
|
1894
|
-
}
|
|
1895
|
-
async loadHandlers(dir) {
|
|
1896
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
1897
|
-
if (!this.isEventHandlerClass(val)) continue;
|
|
1898
|
-
this.registerHandler(val, relativePath);
|
|
1899
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
async loadMiddlewares(dir) {
|
|
1903
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
1904
|
-
if (!this.isMiddlewareClass(val)) continue;
|
|
1905
|
-
this.registerMiddleware(val, relativePath);
|
|
1906
|
-
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
1907
|
-
}
|
|
1764
|
+
utils.block("Loaded event handlers", [...utils.entries(loadedHandlers), ...utils.counts(perEvent)], "debug");
|
|
1908
1765
|
}
|
|
1909
1766
|
/** @internal */
|
|
1910
1767
|
async onHmr(event) {
|
|
1911
|
-
await this.
|
|
1768
|
+
await this.loader.onHmr(event);
|
|
1912
1769
|
}
|
|
1913
1770
|
unregisterHandler(handlerClass, artifacts) {
|
|
1914
1771
|
const events = artifacts ?? [...this.eventMap.keys()];
|
|
@@ -1921,26 +1778,13 @@ var EventDispatcher = class {
|
|
|
1921
1778
|
if (handlers.length === 0) this.eventMap.delete(event);
|
|
1922
1779
|
}
|
|
1923
1780
|
}
|
|
1924
|
-
unregisterMiddleware(middlewareCtor) {
|
|
1925
|
-
this.middlewares.unregister(middlewareCtor);
|
|
1926
|
-
}
|
|
1927
|
-
registerMiddleware(middlewareCtor, relativePath) {
|
|
1928
|
-
const registration = this.middlewares.register(middlewareCtor);
|
|
1929
|
-
if (!registration || !this.loading) return;
|
|
1930
|
-
const scope = registration.keys ? `${registration.priority}, ${registration.keys.join(", ")}` : registration.priority;
|
|
1931
|
-
this.loadedMiddlewares.push({
|
|
1932
|
-
name: `${middlewareCtor.name} (${String(scope)})`,
|
|
1933
|
-
from: formatFilePath(relativePath)
|
|
1934
|
-
});
|
|
1935
|
-
}
|
|
1936
1781
|
async runMiddlewares(eventName, args, dispatch, ran) {
|
|
1937
1782
|
for (const Middleware of this.middlewares.chainFor(eventName)) try {
|
|
1938
|
-
const middleware = new Middleware(args, this.core, dispatch
|
|
1783
|
+
const middleware = new Middleware(args, this.core, dispatch);
|
|
1939
1784
|
ran.push(middleware);
|
|
1940
1785
|
await middleware.execute();
|
|
1941
1786
|
} catch (caught) {
|
|
1942
1787
|
handleEventFault(caught, {
|
|
1943
|
-
eventName: String(eventName),
|
|
1944
1788
|
handlerName: Middleware.name,
|
|
1945
1789
|
args,
|
|
1946
1790
|
dispatch
|
|
@@ -1957,8 +1801,8 @@ var EventDispatcher = class {
|
|
|
1957
1801
|
if (typeof obj !== "function") return false;
|
|
1958
1802
|
return obj.prototype instanceof EventMiddleware && Reflect.hasMetadata(EventMiddlewareMetadataKey, obj);
|
|
1959
1803
|
}
|
|
1960
|
-
|
|
1961
|
-
const
|
|
1804
|
+
storeHandler(handlerClass) {
|
|
1805
|
+
const entries = Reflect.getMetadata(EventMetadataKey, handlerClass);
|
|
1962
1806
|
const register = (key, frequency) => {
|
|
1963
1807
|
let handlers = this.eventMap.get(key);
|
|
1964
1808
|
if (!handlers) {
|
|
@@ -1971,16 +1815,8 @@ var EventDispatcher = class {
|
|
|
1971
1815
|
});
|
|
1972
1816
|
if (this.isInitialized && !this.attachedEvents.has(key)) this.attachListener(key);
|
|
1973
1817
|
};
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
const names = isStringArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
1977
|
-
for (const name of names) register(name, "on");
|
|
1978
|
-
}
|
|
1979
|
-
const from = formatFilePath(relativePath);
|
|
1980
|
-
if (this.loading) this.loadedHandlers.push({
|
|
1981
|
-
name: handlerClass.name,
|
|
1982
|
-
from
|
|
1983
|
-
});
|
|
1818
|
+
for (const entry of entries) register(entry.event, entry.frequency);
|
|
1819
|
+
return true;
|
|
1984
1820
|
}
|
|
1985
1821
|
attachToClient() {
|
|
1986
1822
|
for (const [eventName] of this.eventMap) this.attachListener(eventName);
|
|
@@ -2005,7 +1841,7 @@ var EventDispatcher = class {
|
|
|
2005
1841
|
if (!handlerEntries || handlerEntries.length === 0) return;
|
|
2006
1842
|
const handlersToExecute = handlerEntries.filter((entry) => !this.onceRuns.isSpent(entry, eventName));
|
|
2007
1843
|
if (handlersToExecute.length === 0) return;
|
|
2008
|
-
const dispatch = new DispatchContext(
|
|
1844
|
+
const dispatch = new DispatchContext("event", eventName);
|
|
2009
1845
|
reportEventDispatching(this.core, dispatch.id, eventName, args);
|
|
2010
1846
|
const ran = [];
|
|
2011
1847
|
const handlers = [];
|
|
@@ -2024,7 +1860,7 @@ var EventDispatcher = class {
|
|
|
2024
1860
|
async processHandler(eventName, Ctor, args, dispatch) {
|
|
2025
1861
|
try {
|
|
2026
1862
|
this.logger.trace(`Processing ${paint.sky.bold(eventName)} with ${paint.mute(Ctor.name)}`);
|
|
2027
|
-
const handler = new Ctor(args, this.core, dispatch
|
|
1863
|
+
const handler = new Ctor(args, this.core, dispatch);
|
|
2028
1864
|
const eventCtx = eventGateContext(eventName, args, this.core, dispatch);
|
|
2029
1865
|
await runHandlerGates(Ctor, eventCtx);
|
|
2030
1866
|
await handler.execute();
|
|
@@ -2033,16 +1869,11 @@ var EventDispatcher = class {
|
|
|
2033
1869
|
outcome: "handled"
|
|
2034
1870
|
};
|
|
2035
1871
|
} catch (caught) {
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
dispatch
|
|
2042
|
-
}, this.core);
|
|
2043
|
-
} catch (reportFailed) {
|
|
2044
|
-
this.reportUnhandled(reportFailed);
|
|
2045
|
-
}
|
|
1872
|
+
handleEventFault(caught, {
|
|
1873
|
+
handlerName: Ctor.name,
|
|
1874
|
+
args,
|
|
1875
|
+
dispatch
|
|
1876
|
+
}, this.core);
|
|
2046
1877
|
return {
|
|
2047
1878
|
handler: Ctor.name,
|
|
2048
1879
|
...resultFor(caught)
|
|
@@ -2051,6 +1882,40 @@ var EventDispatcher = class {
|
|
|
2051
1882
|
}
|
|
2052
1883
|
};
|
|
2053
1884
|
|
|
1885
|
+
//#endregion
|
|
1886
|
+
//#region src/bot/autocompleteResponder.ts
|
|
1887
|
+
function autocompleteResponder(interaction, core, dispatch) {
|
|
1888
|
+
return new AutocompleteResponder({
|
|
1889
|
+
bus: core.bus,
|
|
1890
|
+
dispatch,
|
|
1891
|
+
interactionId: interaction.id
|
|
1892
|
+
}, (choices) => interaction.respond(choices));
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
//#endregion
|
|
1896
|
+
//#region src/bot/controllers/gatewayRunConfig.ts
|
|
1897
|
+
function gatewayRunConfig(run) {
|
|
1898
|
+
const { interaction, core, dispatch, HandlerCtor, middlewares, logger } = run;
|
|
1899
|
+
return {
|
|
1900
|
+
dispatch,
|
|
1901
|
+
core,
|
|
1902
|
+
event: interaction,
|
|
1903
|
+
HandlerCtor,
|
|
1904
|
+
middlewares,
|
|
1905
|
+
apiError: DiscordAPIError,
|
|
1906
|
+
logger,
|
|
1907
|
+
interactionId: interaction.id,
|
|
1908
|
+
userId: interaction.user.id,
|
|
1909
|
+
guildId: interaction.guildId,
|
|
1910
|
+
customId: "customId" in interaction ? interaction.customId : null,
|
|
1911
|
+
faultDetails: () => interactionFaultReport(interaction, dispatch),
|
|
1912
|
+
target: interaction.isAutocomplete() ? { responder: autocompleteResponder(interaction, core, dispatch) } : {
|
|
1913
|
+
gateContext: () => interactionGateContext(interaction, core, dispatch),
|
|
1914
|
+
sender: new ReplySender(interaction, dispatch, core.bus)
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
|
|
2054
1919
|
//#endregion
|
|
2055
1920
|
//#region src/bot/defaults/UnhandledRepliable.ts
|
|
2056
1921
|
var UnhandledRepliable = class extends InteractionHandler {
|
|
@@ -2068,103 +1933,31 @@ var UnhandledAutocomplete = class extends AutocompleteHandler {
|
|
|
2068
1933
|
};
|
|
2069
1934
|
|
|
2070
1935
|
//#endregion
|
|
2071
|
-
//#region src/bot/
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
* @internal
|
|
2077
|
-
*/
|
|
2078
|
-
const HARMLESS_API_CODES = /* @__PURE__ */ new Set([
|
|
2079
|
-
RESTJSONErrorCodes.UnknownInteraction,
|
|
2080
|
-
RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
2081
|
-
RESTJSONErrorCodes.UnknownMessage
|
|
2082
|
-
]);
|
|
2083
|
-
|
|
2084
|
-
//#endregion
|
|
2085
|
-
//#region src/bot/handleInteractionFault.ts
|
|
2086
|
-
const logger = new Logger("Faults", { channel: "errors" });
|
|
2087
|
-
async function handleInteractionFault(caught, interaction, core, dispatch, sender) {
|
|
2088
|
-
const { routeId } = dispatch;
|
|
2089
|
-
if (caught instanceof Silence) {
|
|
2090
|
-
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
|
|
2091
|
-
return;
|
|
2092
|
-
}
|
|
2093
|
-
const error = asError(caught);
|
|
2094
|
-
const ignore = new Set(core.config.errors?.ignoreApiCodes ?? []);
|
|
2095
|
-
if (error instanceof DiscordAPIError && ignore.has(error.code)) {
|
|
2096
|
-
logger.debug(`swallowed api code ${error.code}`);
|
|
2097
|
-
return;
|
|
2098
|
-
}
|
|
2099
|
-
if (interaction.isAutocomplete()) {
|
|
2100
|
-
extractErrorResponse(error, core, {
|
|
2101
|
-
origin: routeId,
|
|
2102
|
-
dispatch,
|
|
2103
|
-
guild: interaction.guild,
|
|
2104
|
-
user: interaction.user,
|
|
2105
|
-
metadata: interaction
|
|
2106
|
-
});
|
|
2107
|
-
await sendEmptyChoices(interaction, core, dispatch);
|
|
2108
|
-
return;
|
|
2109
|
-
}
|
|
2110
|
-
const { response } = extractErrorResponse(error, core, {
|
|
2111
|
-
interaction,
|
|
2112
|
-
origin: routeId,
|
|
2113
|
-
dispatch,
|
|
2114
|
-
guild: interaction.guild,
|
|
2115
|
-
user: interaction.user,
|
|
2116
|
-
metadata: interaction
|
|
2117
|
-
});
|
|
2118
|
-
const liveSender = sender ?? new ReplySender(interaction, dispatch, core.bus);
|
|
2119
|
-
await sendGuarded(() => liveSender.send(response, { ephemeral: error instanceof Notice ? error.ephemeral : true }));
|
|
2120
|
-
}
|
|
2121
|
-
async function sendEmptyChoices(interaction, core, dispatch) {
|
|
2122
|
-
const telemetry = {
|
|
2123
|
-
bus: core.bus,
|
|
2124
|
-
dispatch,
|
|
2125
|
-
interactionId: interaction.id
|
|
2126
|
-
};
|
|
2127
|
-
await sendGuarded(() => reportedWrite(telemetry, "respond", () => interaction.respond([])));
|
|
2128
|
-
}
|
|
2129
|
-
async function sendGuarded(send) {
|
|
2130
|
-
try {
|
|
2131
|
-
await send();
|
|
2132
|
-
} catch (error) {
|
|
2133
|
-
if (error instanceof DiscordAPIError && HARMLESS_API_CODES.has(error.code)) {
|
|
2134
|
-
logger.debug(`reply send hit harmless code ${error.code}`);
|
|
2135
|
-
return;
|
|
2136
|
-
}
|
|
2137
|
-
logger.error("fault response send failed", error);
|
|
2138
|
-
}
|
|
1936
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
1937
|
+
function slashRouteOf(interaction) {
|
|
1938
|
+
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
1939
|
+
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
1940
|
+
return buildSlashRoute(interaction.commandName, subcommand, group);
|
|
2139
1941
|
}
|
|
2140
1942
|
|
|
2141
1943
|
//#endregion
|
|
2142
1944
|
//#region src/bot/controllers/InteractionDispatcher.ts
|
|
1945
|
+
const UNHANDLED = {
|
|
1946
|
+
repliable: UnhandledRepliable,
|
|
1947
|
+
autocomplete: UnhandledAutocomplete
|
|
1948
|
+
};
|
|
2143
1949
|
var InteractionDispatcher = class {
|
|
2144
1950
|
core;
|
|
2145
1951
|
inFlight;
|
|
2146
1952
|
logger = new Logger("Interactions", { channel: "interactions" });
|
|
2147
1953
|
isInitialized = false;
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
[InteractionKind.StringMenu]: /* @__PURE__ */ new Map(),
|
|
2153
|
-
[InteractionKind.UserMenu]: /* @__PURE__ */ new Map(),
|
|
2154
|
-
[InteractionKind.RoleMenu]: /* @__PURE__ */ new Map(),
|
|
2155
|
-
[InteractionKind.ChannelMenu]: /* @__PURE__ */ new Map(),
|
|
2156
|
-
[InteractionKind.MentionableMenu]: /* @__PURE__ */ new Map(),
|
|
2157
|
-
[InteractionKind.MessageContextMenu]: /* @__PURE__ */ new Map(),
|
|
2158
|
-
[InteractionKind.UserContextMenu]: /* @__PURE__ */ new Map(),
|
|
2159
|
-
[InteractionKind.Autocomplete]: /* @__PURE__ */ new Map()
|
|
2160
|
-
};
|
|
2161
|
-
handlerFiles = /* @__PURE__ */ new Map();
|
|
1954
|
+
routes = new RouteRegistry();
|
|
1955
|
+
get maps() {
|
|
1956
|
+
return this.routes.maps;
|
|
1957
|
+
}
|
|
2162
1958
|
keysToIgnore = /* @__PURE__ */ new Set();
|
|
2163
1959
|
middlewares = new MiddlewareRegistry(interactionMiddleware);
|
|
2164
|
-
|
|
2165
|
-
loadedHandlers = [];
|
|
2166
|
-
loadedMiddlewares = [];
|
|
2167
|
-
hmrHandler;
|
|
1960
|
+
loader;
|
|
2168
1961
|
constructor(core, inFlight) {
|
|
2169
1962
|
this.core = core;
|
|
2170
1963
|
this.inFlight = inFlight;
|
|
@@ -2173,59 +1966,40 @@ var InteractionDispatcher = class {
|
|
|
2173
1966
|
this.keysToIgnore.add(CONFIRM_DEF);
|
|
2174
1967
|
const interactionsDir = this.core.config.bot.interactions.path;
|
|
2175
1968
|
if (!interactionsDir) throw new SeedcordError(SeedcordErrorCode.CoreControllerPathMissing, ["InteractionDispatcher", "interactions"]);
|
|
2176
|
-
|
|
2177
|
-
this.hmrHandler = new HmrModuleHandler$1({
|
|
1969
|
+
this.loader = new HandlerLoader({
|
|
2178
1970
|
handlersDir: interactionsDir,
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
logger: this.logger
|
|
1971
|
+
middlewaresDir: hasKeys(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0,
|
|
1972
|
+
middlewares: this.middlewares,
|
|
1973
|
+
logger: this.logger,
|
|
1974
|
+
isHandler: (value) => this.isHandlerClass(value),
|
|
1975
|
+
isMiddleware: (value) => this.isMiddlewareClass(value),
|
|
1976
|
+
storeHandler: (ctor, from) => this.routes.register(ctor, from),
|
|
1977
|
+
removeHandler: (ctor, routes) => this.routes.unregister(ctor, routes),
|
|
1978
|
+
artifactsOf: (ctor) => this.routes.routesOf(ctor)
|
|
2188
1979
|
});
|
|
2189
1980
|
}
|
|
2190
1981
|
warnUnhandledRoutes(commandLeaves) {
|
|
2191
|
-
|
|
1982
|
+
this.routes.warnUnhandledRoutes(this.logger, commandLeaves);
|
|
2192
1983
|
}
|
|
2193
1984
|
warnUnhandledContextMenuRoutes(leaves) {
|
|
2194
|
-
|
|
2195
|
-
for (const name of leaves.message) if (!this.maps[InteractionKind.MessageContextMenu].has(name)) this.logger.warn(`Message context menu ${paint.sky.bold(name)} has no registered ${paint.bold("@ContextMenuRoute")} handler.`);
|
|
2196
|
-
}
|
|
2197
|
-
getArtifacts(handlerClass) {
|
|
2198
|
-
return interactionRoutesOf(handlerClass).map(([routeType, routes]) => ({
|
|
2199
|
-
routeType,
|
|
2200
|
-
routes
|
|
2201
|
-
}));
|
|
1985
|
+
this.routes.warnUnhandledContextMenuRoutes(this.logger, leaves);
|
|
2202
1986
|
}
|
|
2203
1987
|
async init() {
|
|
2204
1988
|
if (this.isInitialized) return;
|
|
2205
1989
|
this.isInitialized = true;
|
|
2206
|
-
|
|
2207
|
-
if (!handlersDir) return;
|
|
2208
|
-
const middlewareDir = hasKeys(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0;
|
|
2209
|
-
this.loading = true;
|
|
2210
|
-
this.loadedHandlers.length = 0;
|
|
2211
|
-
this.loadedMiddlewares.length = 0;
|
|
2212
|
-
try {
|
|
2213
|
-
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
2214
|
-
await this.loadHandlers(handlersDir);
|
|
2215
|
-
} finally {
|
|
2216
|
-
this.loading = false;
|
|
2217
|
-
}
|
|
1990
|
+
await this.loader.load();
|
|
2218
1991
|
this.attachToClient();
|
|
2219
1992
|
this.reportLoad();
|
|
2220
1993
|
}
|
|
2221
1994
|
reportLoad() {
|
|
2222
1995
|
const { utils } = this.logger;
|
|
1996
|
+
const { loadedHandlers, loadedMiddlewares } = this.loader;
|
|
2223
1997
|
utils.summary("Loaded", {
|
|
2224
|
-
"interaction handlers":
|
|
2225
|
-
middlewares:
|
|
1998
|
+
"interaction handlers": loadedHandlers.length,
|
|
1999
|
+
middlewares: loadedMiddlewares.length
|
|
2226
2000
|
}, "debug");
|
|
2227
|
-
if (
|
|
2228
|
-
utils.block("Loaded interaction handlers", [...utils.entries(
|
|
2001
|
+
if (loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(loadedMiddlewares), "debug");
|
|
2002
|
+
utils.block("Loaded interaction handlers", [...utils.entries(loadedHandlers), ...utils.counts({
|
|
2229
2003
|
slash: this.maps[InteractionKind.Slash].size,
|
|
2230
2004
|
buttons: this.maps[InteractionKind.Button].size,
|
|
2231
2005
|
modals: this.maps[InteractionKind.Modal].size,
|
|
@@ -2239,29 +2013,6 @@ var InteractionDispatcher = class {
|
|
|
2239
2013
|
autocomplete: this.maps[InteractionKind.Autocomplete].size
|
|
2240
2014
|
})], "debug");
|
|
2241
2015
|
}
|
|
2242
|
-
async loadHandlers(dir) {
|
|
2243
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
2244
|
-
if (!this.isHandlerClass(val)) continue;
|
|
2245
|
-
this.registerHandler(val, relativePath);
|
|
2246
|
-
this.hmrHandler?.trackHandler(fullPath, val);
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
async loadMiddlewares(dir) {
|
|
2250
|
-
for await (const { fullPath, relativePath, imported } of importFiles(dir)) for (const val of Object.values(imported)) {
|
|
2251
|
-
if (!this.isMiddlewareClass(val)) continue;
|
|
2252
|
-
this.registerMiddleware(val, relativePath);
|
|
2253
|
-
this.hmrHandler?.trackMiddleware(fullPath, val);
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
registerMiddleware(middlewareCtor, relativePath) {
|
|
2257
|
-
const metadata = this.middlewares.register(middlewareCtor);
|
|
2258
|
-
if (!metadata || !this.loading) return;
|
|
2259
|
-
const scope = metadata.keys ? `${metadata.priority}, ${metadata.keys.join(", ")}` : metadata.priority;
|
|
2260
|
-
this.loadedMiddlewares.push({
|
|
2261
|
-
name: `${middlewareCtor.name} (${String(scope)})`,
|
|
2262
|
-
from: formatFilePath(relativePath)
|
|
2263
|
-
});
|
|
2264
|
-
}
|
|
2265
2016
|
isHandlerClass(obj) {
|
|
2266
2017
|
if (typeof obj !== "function") return false;
|
|
2267
2018
|
return obj.prototype instanceof InteractionHandler && Reflect.hasMetadata(InteractionMetadataKey, obj) || obj.prototype instanceof AutocompleteHandler && Reflect.hasMetadata(InteractionMetadataKey, obj);
|
|
@@ -2270,38 +2021,8 @@ var InteractionDispatcher = class {
|
|
|
2270
2021
|
if (typeof obj !== "function") return false;
|
|
2271
2022
|
return obj.prototype instanceof InteractionMiddleware && Reflect.hasMetadata(InteractionMiddlewareMetadataKey, obj);
|
|
2272
2023
|
}
|
|
2273
|
-
registerHandler(handlerClass, relativePath) {
|
|
2274
|
-
const from = formatFilePath(relativePath);
|
|
2275
|
-
const writes = [];
|
|
2276
|
-
for (const [kind, routes] of interactionRoutesOf(handlerClass)) for (const route of routes) {
|
|
2277
|
-
const existing = this.maps[kind].get(route);
|
|
2278
|
-
if (existing && existing !== handlerClass) throw new SeedcordTypeError(SeedcordErrorCode.CoreDuplicateName, [
|
|
2279
|
-
"route",
|
|
2280
|
-
`${kind}:${route}`,
|
|
2281
|
-
[`${paint.bold(existing.name)} (${paint.path(this.handlerFiles.get(existing) ?? "unknown file")})`, `${paint.bold(handlerClass.name)} (${paint.path(from)})`]
|
|
2282
|
-
]);
|
|
2283
|
-
writes.push({
|
|
2284
|
-
kind,
|
|
2285
|
-
route
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2288
|
-
if (writes.length === 0) return;
|
|
2289
|
-
for (const { kind, route } of writes) this.maps[kind].set(route, handlerClass);
|
|
2290
|
-
this.handlerFiles.set(handlerClass, from);
|
|
2291
|
-
if (this.loading) this.loadedHandlers.push({
|
|
2292
|
-
name: handlerClass.name,
|
|
2293
|
-
from
|
|
2294
|
-
});
|
|
2295
|
-
}
|
|
2296
2024
|
async onHmr(event) {
|
|
2297
|
-
await this.
|
|
2298
|
-
}
|
|
2299
|
-
unregisterHandler(handlerClass, artifacts) {
|
|
2300
|
-
this.handlerFiles.delete(handlerClass);
|
|
2301
|
-
for (const { routeType, routes } of artifacts ?? this.getArtifacts(handlerClass)) for (const route of routes) this.maps[routeType].delete(route);
|
|
2302
|
-
}
|
|
2303
|
-
unregisterMiddleware(middlewareCtor) {
|
|
2304
|
-
this.middlewares.unregister(middlewareCtor);
|
|
2025
|
+
await this.loader.onHmr(event);
|
|
2305
2026
|
}
|
|
2306
2027
|
attachToClient() {
|
|
2307
2028
|
this.core.bot.client.on(Events.InteractionCreate, (interaction) => {
|
|
@@ -2317,186 +2038,30 @@ var InteractionDispatcher = class {
|
|
|
2317
2038
|
}
|
|
2318
2039
|
async handleCustomIdInteraction(interaction, kind) {
|
|
2319
2040
|
if ([...this.keysToIgnore].some((matcher) => matcher.owns(interaction.customId))) return;
|
|
2320
|
-
|
|
2321
|
-
if (!prefix) this.logger.warn(`${paint.sky.bold(kind)} has invalid customId: ${paint.mute(interaction.customId)}`);
|
|
2322
|
-
await this.processInteraction(interaction, kind, () => prefix);
|
|
2041
|
+
await this.processInteraction(interaction, kind, prefixOf(interaction.customId));
|
|
2323
2042
|
}
|
|
2324
|
-
async processInteraction(interaction, kind,
|
|
2325
|
-
const
|
|
2326
|
-
const
|
|
2327
|
-
const queuedMs = queuedMsFor(interaction.id);
|
|
2328
|
-
const matched = this.maps[kind].get(key);
|
|
2329
|
-
const HandlerCtor = matched ?? fallback;
|
|
2330
|
-
const dispatch = new DispatchContext(`${kind}:${key || "unrouted"}`);
|
|
2331
|
-
const report = this.reporterFor({
|
|
2043
|
+
async processInteraction(interaction, kind, key) {
|
|
2044
|
+
const { fallback, ctor: HandlerCtor } = matchRoute(this.maps, kind, key, UNHANDLED);
|
|
2045
|
+
const config = gatewayRunConfig({
|
|
2332
2046
|
interaction,
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
routeId: dispatch.routeId
|
|
2047
|
+
core: this.core,
|
|
2048
|
+
dispatch: new DispatchContext(kind, key, fallback),
|
|
2049
|
+
HandlerCtor,
|
|
2050
|
+
middlewares: this.middlewares,
|
|
2051
|
+
logger: this.logger
|
|
2339
2052
|
});
|
|
2340
|
-
|
|
2341
|
-
const ran = [];
|
|
2342
|
-
let result = { outcome: "handled" };
|
|
2343
|
-
try {
|
|
2344
|
-
const handler = this.buildHandler(HandlerCtor, interaction, dispatch, key, !matched);
|
|
2345
|
-
if (handler instanceof RepliableHandler) sender = handler.sender;
|
|
2346
|
-
const refusal = await this.refusalBeforeHandler({
|
|
2347
|
-
HandlerCtor,
|
|
2348
|
-
kind,
|
|
2349
|
-
interaction,
|
|
2350
|
-
dispatch,
|
|
2351
|
-
sender,
|
|
2352
|
-
ran,
|
|
2353
|
-
declaredRoute: matched ? dispatch.routeId : void 0
|
|
2354
|
-
});
|
|
2355
|
-
if (refusal) {
|
|
2356
|
-
result = resultFor(refusal.caught);
|
|
2357
|
-
await this.answer(refusal.caught, interaction, dispatch, sender, report);
|
|
2358
|
-
return;
|
|
2359
|
-
}
|
|
2360
|
-
await handler.execute();
|
|
2361
|
-
report("handled");
|
|
2362
|
-
} catch (caught) {
|
|
2363
|
-
if (result.outcome === "handled") result = resultFor(caught);
|
|
2364
|
-
await this.answer(caught, interaction, dispatch, sender, report);
|
|
2365
|
-
} finally {
|
|
2366
|
-
await runAfter(ran, result, this.logger);
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
async answer(caught, interaction, dispatch, sender, report) {
|
|
2370
|
-
try {
|
|
2371
|
-
await handleInteractionFault(caught, interaction, this.core, dispatch, sender);
|
|
2372
|
-
} finally {
|
|
2373
|
-
report(outcomeFor(caught));
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
buildHandler(HandlerCtor, interaction, dispatch, key, isFallback) {
|
|
2377
|
-
if (isFallback) this.logger.warn(`No handler found for key ${paint.sky.bold(key)}. Falling back to ${HandlerCtor.name}.`);
|
|
2378
|
-
this.logger.trace(`Processing ${paint.sky.bold(key)} with ${paint.mute(HandlerCtor.name)}`);
|
|
2379
|
-
return new HandlerCtor(interaction, this.core, dispatch);
|
|
2380
|
-
}
|
|
2381
|
-
reporterFor(row) {
|
|
2382
|
-
let reported = false;
|
|
2383
|
-
return (outcome) => {
|
|
2384
|
-
if (reported) return;
|
|
2385
|
-
reported = true;
|
|
2386
|
-
reportDispatch(this.core.bus, {
|
|
2387
|
-
dispatchId: row.dispatchId,
|
|
2388
|
-
routeId: row.routeId,
|
|
2389
|
-
interactionId: row.interaction.id,
|
|
2390
|
-
kind: row.kind,
|
|
2391
|
-
outcome,
|
|
2392
|
-
startedAt: row.startedAt,
|
|
2393
|
-
fallback: row.fallback,
|
|
2394
|
-
userId: row.interaction.user.id,
|
|
2395
|
-
guildId: row.interaction.guildId,
|
|
2396
|
-
queuedMs: row.queuedMs
|
|
2397
|
-
});
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
async refusalBeforeHandler(step) {
|
|
2401
|
-
const { HandlerCtor, kind, interaction, dispatch, sender, ran, declaredRoute } = step;
|
|
2402
|
-
if (kind !== InteractionKind.Autocomplete && sender) try {
|
|
2403
|
-
await this.runMiddlewares(kind, interaction, dispatch, sender, ran);
|
|
2404
|
-
} catch (caught) {
|
|
2405
|
-
return { caught };
|
|
2406
|
-
}
|
|
2407
|
-
if (interaction.isAutocomplete()) return null;
|
|
2408
|
-
return this.gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute);
|
|
2409
|
-
}
|
|
2410
|
-
async runMiddlewares(kind, interaction, dispatch, sender, ran) {
|
|
2411
|
-
for (const Middleware of this.middlewares.chainFor(kind)) {
|
|
2412
|
-
const middleware = new Middleware(interaction, this.core, dispatch, sender);
|
|
2413
|
-
ran.push(middleware);
|
|
2414
|
-
await middleware.execute();
|
|
2415
|
-
}
|
|
2416
|
-
}
|
|
2417
|
-
async gateRefusal(HandlerCtor, interaction, dispatch, declaredRoute) {
|
|
2418
|
-
const monitor = slowGateMonitor();
|
|
2419
|
-
try {
|
|
2420
|
-
await runHandlerGates(HandlerCtor, interactionGateContext(interaction, this.core, dispatch), declaredRoute, monitor?.observe);
|
|
2421
|
-
return null;
|
|
2422
|
-
} catch (caught) {
|
|
2423
|
-
return { caught };
|
|
2424
|
-
} finally {
|
|
2425
|
-
monitor?.report(declaredRoute ?? null);
|
|
2426
|
-
}
|
|
2053
|
+
await (await new InteractionRun(config).prepare())?.();
|
|
2427
2054
|
}
|
|
2428
2055
|
async handleInteraction(interaction) {
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
break;
|
|
2439
|
-
case interaction.isStringSelectMenu():
|
|
2440
|
-
await this.handleStringSelectMenu(interaction);
|
|
2441
|
-
break;
|
|
2442
|
-
case interaction.isUserSelectMenu():
|
|
2443
|
-
await this.handleUserSelectMenu(interaction);
|
|
2444
|
-
break;
|
|
2445
|
-
case interaction.isRoleSelectMenu():
|
|
2446
|
-
await this.handleRoleSelectMenu(interaction);
|
|
2447
|
-
break;
|
|
2448
|
-
case interaction.isChannelSelectMenu():
|
|
2449
|
-
await this.handleChannelSelectMenu(interaction);
|
|
2450
|
-
break;
|
|
2451
|
-
case interaction.isMentionableSelectMenu():
|
|
2452
|
-
await this.handleMentionableSelectMenu(interaction);
|
|
2453
|
-
break;
|
|
2454
|
-
case interaction.isMessageContextMenuCommand():
|
|
2455
|
-
await this.handleMessageContextMenu(interaction);
|
|
2456
|
-
break;
|
|
2457
|
-
case interaction.isUserContextMenuCommand():
|
|
2458
|
-
await this.handleUserContextMenu(interaction);
|
|
2459
|
-
break;
|
|
2460
|
-
case interaction.isAutocomplete():
|
|
2461
|
-
await this.handleAutocomplete(interaction);
|
|
2462
|
-
break;
|
|
2463
|
-
default: this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
async handleSlashCommand(interaction) {
|
|
2467
|
-
const route = slashRouteOf(interaction);
|
|
2468
|
-
await this.processInteraction(interaction, InteractionKind.Slash, () => route);
|
|
2469
|
-
}
|
|
2470
|
-
async handleButton(interaction) {
|
|
2471
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.Button);
|
|
2472
|
-
}
|
|
2473
|
-
async handleModal(interaction) {
|
|
2474
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.Modal);
|
|
2475
|
-
}
|
|
2476
|
-
async handleStringSelectMenu(interaction) {
|
|
2477
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.StringMenu);
|
|
2478
|
-
}
|
|
2479
|
-
async handleUserSelectMenu(interaction) {
|
|
2480
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.UserMenu);
|
|
2481
|
-
}
|
|
2482
|
-
async handleRoleSelectMenu(interaction) {
|
|
2483
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.RoleMenu);
|
|
2484
|
-
}
|
|
2485
|
-
async handleChannelSelectMenu(interaction) {
|
|
2486
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.ChannelMenu);
|
|
2487
|
-
}
|
|
2488
|
-
async handleMentionableSelectMenu(interaction) {
|
|
2489
|
-
await this.handleCustomIdInteraction(interaction, InteractionKind.MentionableMenu);
|
|
2490
|
-
}
|
|
2491
|
-
async handleMessageContextMenu(interaction) {
|
|
2492
|
-
await this.processInteraction(interaction, InteractionKind.MessageContextMenu, () => interaction.commandName);
|
|
2493
|
-
}
|
|
2494
|
-
async handleUserContextMenu(interaction) {
|
|
2495
|
-
await this.processInteraction(interaction, InteractionKind.UserContextMenu, () => interaction.commandName);
|
|
2496
|
-
}
|
|
2497
|
-
async handleAutocomplete(interaction) {
|
|
2498
|
-
const route = slashRouteOf(interaction);
|
|
2499
|
-
await this.processInteraction(interaction, InteractionKind.Autocomplete, () => route, UnhandledAutocomplete);
|
|
2056
|
+
if (interaction.isChatInputCommand()) await this.processInteraction(interaction, InteractionKind.Slash, slashRouteOf(interaction));
|
|
2057
|
+
else if (interaction.isAutocomplete()) await this.processInteraction(interaction, InteractionKind.Autocomplete, slashRouteOf(interaction));
|
|
2058
|
+
else if (interaction.isModalSubmit()) await this.handleCustomIdInteraction(interaction, InteractionKind.Modal);
|
|
2059
|
+
else if (interaction.isButton() || interaction.isAnySelectMenu()) {
|
|
2060
|
+
const kind = componentKindOf(interaction.componentType);
|
|
2061
|
+
if (kind) await this.handleCustomIdInteraction(interaction, kind);
|
|
2062
|
+
} else if (interaction.isMessageContextMenuCommand()) await this.processInteraction(interaction, InteractionKind.MessageContextMenu, interaction.commandName);
|
|
2063
|
+
else if (interaction.isUserContextMenuCommand()) await this.processInteraction(interaction, InteractionKind.UserContextMenu, interaction.commandName);
|
|
2064
|
+
else this.logger.warn(`Unhandled interaction type: ${interaction.type}`);
|
|
2500
2065
|
}
|
|
2501
2066
|
};
|
|
2502
2067
|
|
|
@@ -2638,7 +2203,7 @@ var Bot = class {
|
|
|
2638
2203
|
//#endregion
|
|
2639
2204
|
//#region src/version.ts
|
|
2640
2205
|
/** Package version */
|
|
2641
|
-
const version = "0.9.0-next.
|
|
2206
|
+
const version = "0.9.0-next.2";
|
|
2642
2207
|
|
|
2643
2208
|
//#endregion
|
|
2644
2209
|
//#region src/Seedcord.ts
|
|
@@ -2721,5 +2286,5 @@ var Seedcord = class extends ServerHost {
|
|
|
2721
2286
|
};
|
|
2722
2287
|
|
|
2723
2288
|
//#endregion
|
|
2724
|
-
export { ArraySource, AutocompleteHandler, BaseHandler, ButtonHandler, ChannelMenuHandler, ComponentHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, GatewayEmoji, HmrModuleHandler, IgnoreBots, InteractionHandler, InteractionMiddleware, MentionableMenuHandler, MessageContextMenuHandler, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RegisterEventMiddleware, RepliableHandler, ReplySender, RoleMenuHandler, Seedcord, SelectMenuHandler, SlashHandler, StringMenuHandler, Subscriber, UserContextMenuHandler, UserMenuHandler, WebhookLog, WinstonConsoleSink, WinstonFileSink, checkBotPermissions, checkPermissions, getConfirmation, hasPermsToAssign, version };
|
|
2289
|
+
export { ArraySource, AutocompleteHandler, BaseHandler, ButtonHandler, ChannelMenuHandler, ComponentHandler, CursorSource, Emojis, EventHandler, EventMiddleware, Gated, GatewayEmoji, HmrModuleHandler, IgnoreBots, InteractionHandler, InteractionMiddleware, MentionPrefix, MentionableMenuHandler, MessageContextMenuHandler, ModalHandler, Nsfw, Paginator, Plugin, RegisterEvent, RegisterEventMiddleware, RepliableHandler, ReplySender, RoleMenuHandler, Seedcord, SelectMenuHandler, SlashHandler, StringMenuHandler, Subscriber, UserContextMenuHandler, UserMenuHandler, WebhookLog, WinstonConsoleSink, WinstonFileSink, checkBotPermissions, checkPermissions, getConfirmation, hasPermsToAssign, version };
|
|
2725
2290
|
//# sourceMappingURL=index.mjs.map
|