@seedcord/gateway 0.1.0-next.1 → 0.1.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +989 -1268
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +373 -723
- package/dist/index.mjs +914 -1189
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -12
package/dist/index.cjs
CHANGED
|
@@ -27,27 +27,36 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
29
29
|
require("reflect-metadata");
|
|
30
|
-
let
|
|
30
|
+
let _seedcord_logger_node = require("@seedcord/logger/node");
|
|
31
31
|
let _seedcord_core_internal = require("@seedcord/core/internal");
|
|
32
|
-
let discord_js = require("discord.js");
|
|
33
32
|
let _seedcord_errors = require("@seedcord/errors");
|
|
34
33
|
let _seedcord_errors_internal = require("@seedcord/errors/internal");
|
|
35
|
-
let
|
|
36
|
-
let
|
|
37
|
-
let chalk = require("chalk");
|
|
38
|
-
chalk = __toESM(chalk, 1);
|
|
34
|
+
let _seedcord_core = require("@seedcord/core");
|
|
35
|
+
let _seedcord_logger = require("@seedcord/logger");
|
|
39
36
|
let _discordjs_builders = require("@discordjs/builders");
|
|
40
37
|
let _seedcord_utils_internal = require("@seedcord/utils/internal");
|
|
38
|
+
let discord_js = require("discord.js");
|
|
39
|
+
let node_crypto = require("node:crypto");
|
|
40
|
+
node_crypto = __toESM(node_crypto, 1);
|
|
41
41
|
let _seedcord_event_emitter = require("@seedcord/event-emitter");
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
let
|
|
42
|
+
let chalk = require("chalk");
|
|
43
|
+
chalk = __toESM(chalk, 1);
|
|
44
|
+
let envapt = require("envapt");
|
|
45
|
+
let discord_api_types_v10 = require("discord-api-types/v10");
|
|
46
|
+
let _seedcord_utils = require("@seedcord/utils");
|
|
47
|
+
let _discordjs_rest = require("@discordjs/rest");
|
|
48
|
+
let _seedcord_core_node = require("@seedcord/core/node");
|
|
45
49
|
let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
|
|
46
50
|
let _seedcord_types_internal = require("@seedcord/types/internal");
|
|
47
|
-
let
|
|
48
|
-
let
|
|
49
|
-
|
|
51
|
+
let envapt_legacy = require("envapt/legacy");
|
|
52
|
+
let node_path = require("node:path");
|
|
53
|
+
let _seedcord_core_hmr = require("@seedcord/core/hmr");
|
|
54
|
+
let _seedcord_utils_node = require("@seedcord/utils/node");
|
|
50
55
|
|
|
56
|
+
//#region src/logger-bootstrap.ts
|
|
57
|
+
(0, _seedcord_logger_node.installNodeDefaults)();
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
51
60
|
//#region src/bot/decorators/Events.ts
|
|
52
61
|
/**
|
|
53
62
|
* Registers an event handler class with one or more Discord.js events.
|
|
@@ -140,190 +149,6 @@ function Gated(...gates) {
|
|
|
140
149
|
};
|
|
141
150
|
}
|
|
142
151
|
|
|
143
|
-
//#endregion
|
|
144
|
-
//#region src/miscellaneous/areRoutes.ts
|
|
145
|
-
/**
|
|
146
|
-
* Checks if the provided value is an array of strings.
|
|
147
|
-
*
|
|
148
|
-
* @internal
|
|
149
|
-
*/
|
|
150
|
-
function areRoutes(routes) {
|
|
151
|
-
return Array.isArray(routes) && routes.every((r) => typeof r === "string");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
//#endregion
|
|
155
|
-
//#region src/bot/decorators/Interactions.ts
|
|
156
|
-
/**
|
|
157
|
-
* Types of select menus supported for routing
|
|
158
|
-
*/
|
|
159
|
-
let SelectMenuKind = /* @__PURE__ */ function(SelectMenuKind) {
|
|
160
|
-
SelectMenuKind["String"] = "string";
|
|
161
|
-
SelectMenuKind["User"] = "user";
|
|
162
|
-
SelectMenuKind["Role"] = "role";
|
|
163
|
-
SelectMenuKind["Channel"] = "channel";
|
|
164
|
-
SelectMenuKind["Mentionable"] = "mentionable";
|
|
165
|
-
return SelectMenuKind;
|
|
166
|
-
}({});
|
|
167
|
-
/**
|
|
168
|
-
* Routes one or more slash commands to a {@link SlashHandler}.
|
|
169
|
-
*
|
|
170
|
-
* Pass the same route string(s) as the handler's generic. A single command reads `this.options`, several
|
|
171
|
-
* commands branch with `this.match`. The decorator routes and the generic must match exactly, so listing
|
|
172
|
-
* fewer or more than the handler declares is a compile error. Subcommands use a slash path.
|
|
173
|
-
*
|
|
174
|
-
* @param routes - The route string(s) this handler serves, e.g. `'ban'`, `'ban', 'kick'`, or `'demo/setup'`.
|
|
175
|
-
* @decorator
|
|
176
|
-
* @example
|
|
177
|
-
* ```ts
|
|
178
|
-
* \@SlashRoute('ban')
|
|
179
|
-
* class BanHandler extends SlashHandler<'ban'> {
|
|
180
|
-
* async execute() {
|
|
181
|
-
* const target = this.options.getUser('target');
|
|
182
|
-
* }
|
|
183
|
-
* }
|
|
184
|
-
* ```
|
|
185
|
-
*
|
|
186
|
-
* @example
|
|
187
|
-
* ```ts
|
|
188
|
-
* \@SlashRoute('ban', 'kick')
|
|
189
|
-
* class ModerationHandler extends SlashHandler<'ban' | 'kick'> {
|
|
190
|
-
* async execute() {
|
|
191
|
-
* await this.match({ ban: (o) => o.getUser('target'), kick: (o) => o.getUser('member') });
|
|
192
|
-
* }
|
|
193
|
-
* }
|
|
194
|
-
* ```
|
|
195
|
-
*/
|
|
196
|
-
function SlashRoute(...routes) {
|
|
197
|
-
return function(constructor) {
|
|
198
|
-
storeMetadata(_seedcord_core_internal.InteractionRoutes.Slash, routes, constructor);
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Routes button interactions to handler classes.
|
|
203
|
-
*
|
|
204
|
-
* Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
|
|
205
|
-
* generic. Passing different definitions to the decorator and the generic is a compile error. Routing
|
|
206
|
-
* matches the stable prefix, so a wire minted from an older shape still reaches the handler, where
|
|
207
|
-
* reading this.params throws StaleCustomId and the controller boundary turns it into a reply.
|
|
208
|
-
*
|
|
209
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
210
|
-
* @decorator
|
|
211
|
-
*/
|
|
212
|
-
function ButtonRoute(...defs) {
|
|
213
|
-
return function(constructor) {
|
|
214
|
-
storeComponentRoute(_seedcord_core_internal.InteractionRoutes.Button, defs, constructor);
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Routes modal submissions to handler classes.
|
|
219
|
-
*
|
|
220
|
-
* Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
|
|
221
|
-
* generic. Passing different definitions to the decorator and the generic is a compile error. Routing
|
|
222
|
-
* matches the stable prefix, so an older-shape wire still reaches the handler and throws StaleCustomId
|
|
223
|
-
* on read, the same as a button route.
|
|
224
|
-
*
|
|
225
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
226
|
-
* @decorator
|
|
227
|
-
*/
|
|
228
|
-
function ModalRoute(...defs) {
|
|
229
|
-
return function(constructor) {
|
|
230
|
-
storeComponentRoute(_seedcord_core_internal.InteractionRoutes.Modal, defs, constructor);
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Routes one or more context-menu commands to a {@link ContextMenuHandler}.
|
|
235
|
-
*
|
|
236
|
-
* Pass the kind (`ApplicationCommandType.User` or `ApplicationCommandType.Message`) and the command name(s),
|
|
237
|
-
* each checked against that kind's registry, so a typo is a compile error. The same kind must sit on the
|
|
238
|
-
* handler's generic, cross-checked both directions. Context menus carry no options, so a multi-name handler
|
|
239
|
-
* reads `this.target` uniformly with no branch.
|
|
240
|
-
*
|
|
241
|
-
* @param kind - The context-menu kind, `ApplicationCommandType.User` or `ApplicationCommandType.Message`.
|
|
242
|
-
* @param names - The command name(s) this handler serves, keyed off the matching registry.
|
|
243
|
-
* @decorator
|
|
244
|
-
* @example
|
|
245
|
-
* ```ts
|
|
246
|
-
* \@ContextMenuRoute(ApplicationCommandType.User, 'View Profile')
|
|
247
|
-
* class ViewProfile extends ContextMenuHandler<ApplicationCommandType.User> {
|
|
248
|
-
* async execute() {
|
|
249
|
-
* const user = this.target;
|
|
250
|
-
* }
|
|
251
|
-
* }
|
|
252
|
-
* ```
|
|
253
|
-
*/
|
|
254
|
-
function ContextMenuRoute(kind, ...names) {
|
|
255
|
-
return function(constructor) {
|
|
256
|
-
storeMetadata(kind === discord_js.ApplicationCommandType.User ? _seedcord_core_internal.InteractionRoutes.UserContextMenu : _seedcord_core_internal.InteractionRoutes.MessageContextMenu, names, constructor);
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Routes one or more commands' autocomplete to an {@link AutocompleteHandler}.
|
|
261
|
-
*
|
|
262
|
-
* Pass the same command route string(s) as the handler's generic. One command branches with `this.match`
|
|
263
|
-
* over its autocompletable fields, several commands share one handler whose arms span every command's
|
|
264
|
-
* fields. The decorator routes and the generic must match exactly, so listing fewer or more than the
|
|
265
|
-
* handler declares is a compile error.
|
|
266
|
-
*
|
|
267
|
-
* @param routes - The command route string(s) this handler serves, e.g. `'search'` or `'search', 'find'`.
|
|
268
|
-
* @decorator
|
|
269
|
-
* @example
|
|
270
|
-
* ```ts
|
|
271
|
-
* \@AutocompleteRoute('search')
|
|
272
|
-
* class SearchAutocomplete extends AutocompleteHandler<'search'> {
|
|
273
|
-
* async execute() {
|
|
274
|
-
* await this.match({ query: (value, respond) => respond([{ name: value, value }]) });
|
|
275
|
-
* }
|
|
276
|
-
* }
|
|
277
|
-
* ```
|
|
278
|
-
*/
|
|
279
|
-
function AutocompleteRoute(...routes) {
|
|
280
|
-
return function(constructor) {
|
|
281
|
-
storeMetadata(_seedcord_core_internal.InteractionRoutes.Autocomplete, routes, constructor);
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Routes select menu interactions to handler classes.
|
|
286
|
-
*
|
|
287
|
-
* Pass the select kind and the {@link CustomId} definition(s) this handler decodes. The handler's
|
|
288
|
-
* generic must list the same definitions, and its second generic argument must be the matching select
|
|
289
|
-
* interaction type, or it is a compile error.
|
|
290
|
-
*
|
|
291
|
-
* @param type - Select menu kind from {@link SelectMenuKind}.
|
|
292
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
293
|
-
* @decorator
|
|
294
|
-
*
|
|
295
|
-
* @example
|
|
296
|
-
* ```typescript
|
|
297
|
-
* \@SelectMenuRoute(SelectMenuKind.User, AssignId)
|
|
298
|
-
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
299
|
-
* // handles user select menus minted from AssignId
|
|
300
|
-
* }
|
|
301
|
-
* ```
|
|
302
|
-
*/
|
|
303
|
-
function SelectMenuRoute(type, ...defs) {
|
|
304
|
-
return function(constructor) {
|
|
305
|
-
storeComponentRoute({
|
|
306
|
-
["string"]: _seedcord_core_internal.InteractionRoutes.StringMenu,
|
|
307
|
-
["user"]: _seedcord_core_internal.InteractionRoutes.UserMenu,
|
|
308
|
-
["role"]: _seedcord_core_internal.InteractionRoutes.RoleMenu,
|
|
309
|
-
["channel"]: _seedcord_core_internal.InteractionRoutes.ChannelMenu,
|
|
310
|
-
["mentionable"]: _seedcord_core_internal.InteractionRoutes.MentionableMenu
|
|
311
|
-
}[type], defs, constructor);
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
function storeComponentRoute(route, defs, constructor) {
|
|
315
|
-
storeMetadata(route, defs.map((def) => def.prefix), constructor);
|
|
316
|
-
Reflect.defineMetadata(_seedcord_core_internal.ComponentDefsKey, defs, constructor);
|
|
317
|
-
}
|
|
318
|
-
function storeMetadata(route, routes, constructor) {
|
|
319
|
-
const key = _seedcord_core_internal.InteractionRouteKeys[route];
|
|
320
|
-
const savedRoutes = Reflect.getMetadata(key, constructor);
|
|
321
|
-
const existing = areRoutes(savedRoutes) ? savedRoutes : [];
|
|
322
|
-
const toStore = Array.isArray(routes) ? routes : [routes];
|
|
323
|
-
Reflect.defineMetadata(key, [...existing, ...toStore], constructor);
|
|
324
|
-
Reflect.defineMetadata(_seedcord_core_internal.InteractionMetadataKey, true, constructor);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
152
|
//#endregion
|
|
328
153
|
//#region src/bot/decorators/Middlewares.ts
|
|
329
154
|
/**
|
|
@@ -392,21 +217,6 @@ const IgnoreBots = (0, _seedcord_core.defineGate)("IgnoreBots", (ctx) => {
|
|
|
392
217
|
if (ctx.user?.bot) throw new _seedcord_core.Silence("actor is a bot");
|
|
393
218
|
});
|
|
394
219
|
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/bot/notices/utils.ts
|
|
397
|
-
function mentionFor(subject) {
|
|
398
|
-
if (subject instanceof discord_js.Role) return `<@&${subject.id}>`;
|
|
399
|
-
if (subject instanceof discord_js.TextChannel) return `<#${subject.id}>`;
|
|
400
|
-
if (subject instanceof discord_js.GuildMember) return `<@${subject.id}>`;
|
|
401
|
-
return `\`${subject.name}\``;
|
|
402
|
-
}
|
|
403
|
-
function labelFor(subject) {
|
|
404
|
-
if (subject instanceof discord_js.Role) return "role";
|
|
405
|
-
if (subject instanceof discord_js.TextChannel) return "channel";
|
|
406
|
-
if (subject instanceof discord_js.GuildMember) return "member";
|
|
407
|
-
return "guild";
|
|
408
|
-
}
|
|
409
|
-
|
|
410
220
|
//#endregion
|
|
411
221
|
//#region src/bot/notices/index.ts
|
|
412
222
|
var NotNsfw = class extends _seedcord_core_internal.GateNotice {
|
|
@@ -414,13 +224,6 @@ var NotNsfw = class extends _seedcord_core_internal.GateNotice {
|
|
|
414
224
|
super(message);
|
|
415
225
|
}
|
|
416
226
|
};
|
|
417
|
-
var MissingRole = class extends _seedcord_core_internal.GateNotice {
|
|
418
|
-
role;
|
|
419
|
-
constructor(message, role) {
|
|
420
|
-
super(message ?? (role ? `You need the ${role.name} role to use this.` : "You do not have the required role."));
|
|
421
|
-
this.role = role;
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
227
|
var RoleHigherThanMe = class extends _seedcord_core.Notice {
|
|
425
228
|
role;
|
|
426
229
|
botRole;
|
|
@@ -441,30 +244,19 @@ var CannotAssignBotRole = class extends _seedcord_core.Notice {
|
|
|
441
244
|
return { components: [new _seedcord_core_internal.NoticeCard("I cannot assign a managed role.").component] };
|
|
442
245
|
}
|
|
443
246
|
};
|
|
444
|
-
var MissingPermissions = class extends _seedcord_core.Notice {
|
|
445
|
-
where;
|
|
446
|
-
missingPerms;
|
|
447
|
-
constructor(message, where, missingPerms) {
|
|
448
|
-
super(message);
|
|
449
|
-
this.where = where;
|
|
450
|
-
this.missingPerms = missingPerms;
|
|
451
|
-
}
|
|
452
|
-
render() {
|
|
453
|
-
const bullets = this.missingPerms.map((perm) => `• ${perm}`).join("\n");
|
|
454
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`The ${labelFor(this.where)} ${mentionFor(this.where)} is missing the following permission entries:\n\n${bullets}`).component] };
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
247
|
var HasDangerousPermissions = class extends _seedcord_core.Notice {
|
|
458
|
-
|
|
248
|
+
subject;
|
|
459
249
|
dangerousPerms;
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
250
|
+
customLead;
|
|
251
|
+
constructor(message, subject, dangerousPerms) {
|
|
252
|
+
super(message ?? "A dangerous permission is enabled.");
|
|
253
|
+
this.subject = subject;
|
|
463
254
|
this.dangerousPerms = dangerousPerms;
|
|
255
|
+
this.customLead = message;
|
|
464
256
|
}
|
|
465
257
|
render() {
|
|
466
258
|
const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
|
|
467
|
-
return { components: [new _seedcord_core_internal.NoticeCard(
|
|
259
|
+
return { components: [new _seedcord_core_internal.NoticeCard(`${this.customLead ?? `${this.subject} has the following permission entries that must not be enabled:`}\n\n${bullets}`).component] };
|
|
468
260
|
}
|
|
469
261
|
};
|
|
470
262
|
var UserNotFound = class extends _seedcord_core.Notice {
|
|
@@ -540,129 +332,6 @@ function Nsfw(options) {
|
|
|
540
332
|
});
|
|
541
333
|
}
|
|
542
334
|
|
|
543
|
-
//#endregion
|
|
544
|
-
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
545
|
-
/**
|
|
546
|
-
* Map of permission bits to their prettified human readable names.
|
|
547
|
-
*/
|
|
548
|
-
const PermissionNames = new Map(Object.entries(discord_js.PermissionFlagsBits).map(([key, bit]) => [bit, (0, _seedcord_utils.prettify)(key)]));
|
|
549
|
-
function checkPermissions(a, b, c, d, e) {
|
|
550
|
-
const { for: pFor, in: pIn, scope, inverse = false, missing: missingCtor, dangerous: dangerousCtor } = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
|
|
551
|
-
for: a,
|
|
552
|
-
in: b,
|
|
553
|
-
scope: c,
|
|
554
|
-
inverse: d ?? false,
|
|
555
|
-
...e
|
|
556
|
-
} : a;
|
|
557
|
-
const Missing = missingCtor ?? MissingPermissions;
|
|
558
|
-
const Dangerous = dangerousCtor ?? HasDangerousPermissions;
|
|
559
|
-
const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
|
|
560
|
-
const names = (bits) => bits.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
561
|
-
const present = scope.filter((bit) => perms.has(bit, true));
|
|
562
|
-
const presentNames = names(present);
|
|
563
|
-
if (inverse) {
|
|
564
|
-
if (present.length > 0) throw new Dangerous(`${pFor instanceof discord_js.Role ? "Role" : "Member"} has dangerous permissions in this ${pIn instanceof discord_js.Guild ? "guild" : "channel"}`, pFor, presentNames);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const missingBits = scope.filter((bit) => !perms.has(bit, true));
|
|
568
|
-
if (missingBits.length > 0) throw new Missing("Missing Any/All/No Permissions", pFor, names(missingBits));
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
//#endregion
|
|
572
|
-
//#region src/bot/gates/catalog/permissions.ts
|
|
573
|
-
const UNCACHED_MEMBER = "Your server member data could not be resolved. Try again.";
|
|
574
|
-
/**
|
|
575
|
-
* Requires the caller to hold every permission in `scope`, via `checkPermissions`. Refuses outside a guild.
|
|
576
|
-
*
|
|
577
|
-
* Interaction-only and excludes ModalSubmit, since a modal lacks a reliable cached caller member.
|
|
578
|
-
*
|
|
579
|
-
* @param scope - The permission flag bits the caller must all hold.
|
|
580
|
-
* @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-permissions one with `missing`.
|
|
581
|
-
*
|
|
582
|
-
* @see {@link Gated}
|
|
583
|
-
*
|
|
584
|
-
* @example
|
|
585
|
-
* ```ts
|
|
586
|
-
* import { PermissionFlagsBits } from 'discord.js';
|
|
587
|
-
*
|
|
588
|
-
* \@Gated(RequirePermissions([PermissionFlagsBits.BanMembers]))
|
|
589
|
-
* \@SlashRoute('ban')
|
|
590
|
-
* class BanHandler extends SlashHandler<'ban'> {
|
|
591
|
-
* async execute() {
|
|
592
|
-
* const target = this.options.getUser('target');
|
|
593
|
-
* }
|
|
594
|
-
* }
|
|
595
|
-
* ```
|
|
596
|
-
*/
|
|
597
|
-
function RequirePermissions(scope, options) {
|
|
598
|
-
return (0, _seedcord_core.defineGate)("RequirePermissions", (ctx) => {
|
|
599
|
-
if (!ctx.guild) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message));
|
|
600
|
-
if (!ctx.member) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message ?? UNCACHED_MEMBER));
|
|
601
|
-
checkPermissions(ctx.member, ctx.guild, scope, false, options?.missing ? { missing: options.missing } : void 0);
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Requires the bot to hold every permission in `scope`, via `checkPermissions`. Refuses outside a guild.
|
|
606
|
-
*
|
|
607
|
-
* It reads the bot's own member, so it attaches to modal handlers as well.
|
|
608
|
-
*
|
|
609
|
-
* @param scope - The permission flag bits the bot must all hold.
|
|
610
|
-
* @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-permissions one with `missing`.
|
|
611
|
-
*
|
|
612
|
-
* @see {@link Gated}
|
|
613
|
-
*
|
|
614
|
-
* @example
|
|
615
|
-
* ```ts
|
|
616
|
-
* import { PermissionFlagsBits } from 'discord.js';
|
|
617
|
-
*
|
|
618
|
-
* \@Gated(RequireBotPermissions([PermissionFlagsBits.BanMembers]))
|
|
619
|
-
* \@ModalRoute(MyModalId)
|
|
620
|
-
* class MyModalHandler extends ModalHandler<[typeof MyModalId]> {
|
|
621
|
-
* async execute() {
|
|
622
|
-
* // ...
|
|
623
|
-
* }
|
|
624
|
-
* }
|
|
625
|
-
* ```
|
|
626
|
-
*/
|
|
627
|
-
function RequireBotPermissions(scope, options) {
|
|
628
|
-
return (0, _seedcord_core.defineGate)("RequireBotPermissions", (ctx) => {
|
|
629
|
-
const botMember = ctx.guild?.members.me;
|
|
630
|
-
if (!ctx.guild || !botMember) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message));
|
|
631
|
-
checkPermissions(botMember, ctx.guild, scope, false, options?.missing ? { missing: options.missing } : void 0);
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Requires the caller to have `roleId`, read from the member's role cache. Refuses outside a guild first, then
|
|
636
|
-
* when the role is missing.
|
|
637
|
-
*
|
|
638
|
-
* Interaction-only and excludes ModalSubmit.
|
|
639
|
-
*
|
|
640
|
-
* @param roleId - The role snowflake the caller must hold, read from the member's role cache.
|
|
641
|
-
* @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-role one with `missingRole`.
|
|
642
|
-
*
|
|
643
|
-
* @see {@link Gated}
|
|
644
|
-
*
|
|
645
|
-
* @example
|
|
646
|
-
* ```ts
|
|
647
|
-
* \@Gated(RequireRole('123456789012345678'))
|
|
648
|
-
* \@SlashRoute('vip')
|
|
649
|
-
* class VipHandler extends SlashHandler<'vip'> {
|
|
650
|
-
* async execute() {
|
|
651
|
-
* // ...
|
|
652
|
-
* }
|
|
653
|
-
* }
|
|
654
|
-
* ```
|
|
655
|
-
*/
|
|
656
|
-
function RequireRole(roleId, options) {
|
|
657
|
-
return (0, _seedcord_core.defineGate)("RequireRole", (ctx) => {
|
|
658
|
-
if (!ctx.guild) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message));
|
|
659
|
-
if (!ctx.member) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message ?? UNCACHED_MEMBER));
|
|
660
|
-
if (ctx.member.roles.cache.has(roleId)) return;
|
|
661
|
-
const role = ctx.guild.roles.cache.get(roleId) ?? null;
|
|
662
|
-
throw (0, _seedcord_core_internal.pickNotice)(options?.missingRole, (message) => new MissingRole(message, role));
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
|
|
666
335
|
//#endregion
|
|
667
336
|
//#region src/bot/injectors/EmojiInjector.ts
|
|
668
337
|
const emojiStorage = {};
|
|
@@ -684,14 +353,14 @@ const Emojis = emojiStorage;
|
|
|
684
353
|
*/
|
|
685
354
|
var EmojiInjector = class {
|
|
686
355
|
core;
|
|
687
|
-
logger = new
|
|
356
|
+
logger = new _seedcord_logger.Logger("Emojis");
|
|
688
357
|
constructor(core) {
|
|
689
358
|
this.core = core;
|
|
690
359
|
}
|
|
691
360
|
async init() {
|
|
692
361
|
this.clearEmojis();
|
|
693
362
|
if (!this.core.config.bot.emojis || Object.keys(this.core.config.bot.emojis).length === 0) {
|
|
694
|
-
this.logger.debug(
|
|
363
|
+
this.logger.debug("No emojis configured, skipping emoji injection.");
|
|
695
364
|
return;
|
|
696
365
|
}
|
|
697
366
|
const configEmojis = this.core.config.bot.emojis;
|
|
@@ -759,7 +428,7 @@ const MULTI_GUILD = Symbol("seedcord:commandmentions:multi-guild");
|
|
|
759
428
|
*/
|
|
760
429
|
var CommandMentionInjector = class {
|
|
761
430
|
core;
|
|
762
|
-
logger = new
|
|
431
|
+
logger = new _seedcord_logger.Logger("CommandMentions");
|
|
763
432
|
constructor(core) {
|
|
764
433
|
this.core = core;
|
|
765
434
|
}
|
|
@@ -851,6 +520,37 @@ async function fetchText(client, channelId) {
|
|
|
851
520
|
throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
852
521
|
}
|
|
853
522
|
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region src/bot/notices/utils.ts
|
|
525
|
+
function mentionFor(subject) {
|
|
526
|
+
if (subject instanceof discord_js.Role) return `<@&${subject.id}>`;
|
|
527
|
+
return `<@${subject.id}>`;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
532
|
+
function checkPermissions(a, b, c, d, e) {
|
|
533
|
+
const { for: pFor, in: pIn, scope, inverse = false, missingNotice: missingCtor, dangerousNotice: dangerousCtor } = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
|
|
534
|
+
for: a,
|
|
535
|
+
in: b,
|
|
536
|
+
scope: c,
|
|
537
|
+
inverse: d ?? false,
|
|
538
|
+
...e
|
|
539
|
+
} : a;
|
|
540
|
+
const Missing = missingCtor ?? _seedcord_core_internal.MissingPermissions;
|
|
541
|
+
const Dangerous = dangerousCtor ?? HasDangerousPermissions;
|
|
542
|
+
const subject = mentionFor(pFor);
|
|
543
|
+
const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
|
|
544
|
+
const names = (bits) => bits.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
|
|
545
|
+
if (inverse) {
|
|
546
|
+
const present = scope.filter((bit) => perms.has(bit, true));
|
|
547
|
+
if (present.length > 0) throw new Dangerous(void 0, subject, names(present));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const missingBits = scope.filter((bit) => !perms.has(bit, true));
|
|
551
|
+
if (missingBits.length > 0) throw new Missing(void 0, subject, names(missingBits));
|
|
552
|
+
}
|
|
553
|
+
|
|
854
554
|
//#endregion
|
|
855
555
|
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
856
556
|
/**
|
|
@@ -878,17 +578,17 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
878
578
|
if (target instanceof discord_js.Guild) {
|
|
879
579
|
const me = target.members.me;
|
|
880
580
|
if (!me) {
|
|
881
|
-
const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
882
|
-
throw new ((errors?.
|
|
581
|
+
const names = scope.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
|
|
582
|
+
throw new ((errors?.missingNotice) ?? _seedcord_core_internal.MissingPermissions)(void 0, "The bot", names);
|
|
883
583
|
}
|
|
884
584
|
checkPermissions(me, target, scope, inverse, errors);
|
|
885
585
|
return;
|
|
886
586
|
}
|
|
887
587
|
const me = target.guild.members.me;
|
|
888
588
|
if (!me) {
|
|
889
|
-
new
|
|
890
|
-
const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
891
|
-
throw new ((errors?.
|
|
589
|
+
new _seedcord_logger.Logger("checkBotPermissions").warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
590
|
+
const names = scope.map((bit) => _seedcord_core_internal.PermissionNames.get(bit) ?? String(bit));
|
|
591
|
+
throw new ((errors?.missingNotice) ?? _seedcord_core_internal.MissingPermissions)(void 0, "The bot", names);
|
|
892
592
|
}
|
|
893
593
|
checkPermissions(me, target, scope, inverse, errors);
|
|
894
594
|
}
|
|
@@ -925,26 +625,26 @@ function getBotRole(guild) {
|
|
|
925
625
|
* // Check with custom error handling
|
|
926
626
|
* hasPermsToAssign({
|
|
927
627
|
* targetRole: role,
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
628
|
+
* noticeOverrides: {
|
|
629
|
+
* higherNotice: CustomHigherNotice,
|
|
630
|
+
* managedNotice: CustomManagedNotice,
|
|
631
|
+
* missingNotice: CustomMissingPermissionsNotice
|
|
932
632
|
* }
|
|
933
633
|
* });
|
|
934
634
|
* ```
|
|
935
635
|
*/
|
|
936
636
|
function hasPermsToAssign(roleOrOptions) {
|
|
937
|
-
const { targetRole, errors } = roleOrOptions instanceof discord_js.Role ? {
|
|
637
|
+
const { targetRole, noticeOverrides: errors } = roleOrOptions instanceof discord_js.Role ? {
|
|
938
638
|
targetRole: roleOrOptions,
|
|
939
|
-
|
|
639
|
+
noticeOverrides: void 0
|
|
940
640
|
} : roleOrOptions;
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
const
|
|
641
|
+
const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
|
|
642
|
+
const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
|
|
643
|
+
const MissingNotice = errors?.missingNotice ?? _seedcord_core_internal.MissingPermissions;
|
|
944
644
|
const botRole = getBotRole(targetRole.guild);
|
|
945
|
-
if (targetRole.comparePositionTo(botRole) >= 0) throw new
|
|
946
|
-
if (targetRole.managed) throw new
|
|
947
|
-
checkBotPermissions(targetRole.guild, [discord_js.PermissionFlagsBits.ManageRoles], false, {
|
|
645
|
+
if (targetRole.comparePositionTo(botRole) >= 0) throw new HigherNotice("Role is higher than me", targetRole, botRole);
|
|
646
|
+
if (targetRole.managed) throw new ManagedNotice(`Cannot assign managed role ${targetRole.name}`);
|
|
647
|
+
checkBotPermissions(targetRole.guild, [discord_js.PermissionFlagsBits.ManageRoles], false, { missingNotice: MissingNotice });
|
|
948
648
|
}
|
|
949
649
|
|
|
950
650
|
//#endregion
|
|
@@ -1072,114 +772,309 @@ async function updateMemberRoles(rolesToAdd, rolesToRemove, member) {
|
|
|
1072
772
|
}
|
|
1073
773
|
|
|
1074
774
|
//#endregion
|
|
1075
|
-
//#region src/bot/
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
*/
|
|
1083
|
-
const HARMLESS_API_CODES = new Set([
|
|
1084
|
-
discord_js.RESTJSONErrorCodes.UnknownInteraction,
|
|
1085
|
-
discord_js.RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
1086
|
-
discord_js.RESTJSONErrorCodes.UnknownMessage
|
|
1087
|
-
]);
|
|
1088
|
-
|
|
1089
|
-
//#endregion
|
|
1090
|
-
//#region src/miscellaneous/flagsFor.ts
|
|
775
|
+
//#region src/bot/ReplySender.ts
|
|
776
|
+
function seedState(interaction) {
|
|
777
|
+
if (interaction.deferred && interaction.ephemeral === null) return "deferred-update";
|
|
778
|
+
if (interaction.replied) return "replied";
|
|
779
|
+
if (interaction.deferred) return "deferred-reply";
|
|
780
|
+
return "unacked";
|
|
781
|
+
}
|
|
1091
782
|
/**
|
|
1092
|
-
*
|
|
1093
|
-
*
|
|
1094
|
-
*
|
|
1095
|
-
* @param ephemeral - Whether the reply is ephemeral.
|
|
1096
|
-
* @returns The OR'd `MessageFlags` value as a plain number.
|
|
1097
|
-
*
|
|
1098
|
-
* @internal
|
|
783
|
+
* Writes interaction responses through the discord.js interaction. Construction is internal to the repliable
|
|
784
|
+
* handler bases and the dispatcher.
|
|
1099
785
|
*/
|
|
1100
|
-
|
|
1101
|
-
let flags = discord_js.MessageFlags.IsComponentsV2;
|
|
1102
|
-
if (ephemeral) flags |= discord_js.MessageFlags.Ephemeral;
|
|
1103
|
-
return flags;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
//#endregion
|
|
1107
|
-
//#region src/bot/ReplySender.ts
|
|
1108
|
-
/**
|
|
1109
|
-
* Sends a {@link ReplyResponse} to an interaction, picking reply, editReply, or followUp from the live
|
|
1110
|
-
* acknowledgement state. Logs and drops its own send failures so a dead token never escapes into the
|
|
1111
|
-
* controller.
|
|
1112
|
-
*/
|
|
1113
|
-
var ReplySender = class {
|
|
786
|
+
var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
1114
787
|
interaction;
|
|
1115
|
-
|
|
1116
|
-
|
|
788
|
+
constructor(interaction, routeId) {
|
|
789
|
+
super(routeId, seedState(interaction));
|
|
1117
790
|
this.interaction = interaction;
|
|
1118
791
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
* @returns The sent {@link Message}, or `undefined` when the send was swallowed.
|
|
1126
|
-
*/
|
|
1127
|
-
async send(response, ephemeral = true) {
|
|
1128
|
-
try {
|
|
1129
|
-
return await this.dispatch(response, ephemeral);
|
|
1130
|
-
} catch (error) {
|
|
1131
|
-
this.logSwallowed("send", error);
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
792
|
+
async writeReply(response, opts) {
|
|
793
|
+
const result = await this.interaction.reply({
|
|
794
|
+
...this.replyOptions(response, (0, _seedcord_core_internal.sendFlags)(opts)),
|
|
795
|
+
withResponse: true
|
|
796
|
+
});
|
|
797
|
+
return this.createdMessage(result);
|
|
1134
798
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
* its outcome. Never throws, a failed edit is logged and dropped.
|
|
1138
|
-
*
|
|
1139
|
-
* @param message - The message to edit (the handle a prior {@link send} returned).
|
|
1140
|
-
* @param response - The reply to replace it with.
|
|
1141
|
-
*/
|
|
1142
|
-
async edit(message, response) {
|
|
1143
|
-
try {
|
|
1144
|
-
await this.interaction.webhook.editMessage(message, this.editBody(response));
|
|
1145
|
-
} catch (error) {
|
|
1146
|
-
this.logSwallowed("edit", error);
|
|
1147
|
-
}
|
|
799
|
+
async writeDefer(opts) {
|
|
800
|
+
await this.interaction.deferReply({ flags: (0, _seedcord_core_internal.deferFlags)(opts) });
|
|
1148
801
|
}
|
|
1149
|
-
async
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
802
|
+
async writeDeferUpdate() {
|
|
803
|
+
await this.sourceInteraction("deferUpdate").deferUpdate();
|
|
804
|
+
}
|
|
805
|
+
async writeUpdate(response) {
|
|
806
|
+
const result = await this.sourceInteraction("update").update({
|
|
807
|
+
...this.updateOptions(response),
|
|
808
|
+
withResponse: true
|
|
809
|
+
});
|
|
810
|
+
return this.createdMessage(result);
|
|
811
|
+
}
|
|
812
|
+
async writeFollowUp(response, opts) {
|
|
813
|
+
return await this.interaction.followUp(this.replyOptions(response, (0, _seedcord_core_internal.sendFlags)(opts)));
|
|
814
|
+
}
|
|
815
|
+
async writeEditOriginal(response) {
|
|
816
|
+
return await this.interaction.editReply(this.editOptions(response));
|
|
817
|
+
}
|
|
818
|
+
async writeEditTarget(targetId, response) {
|
|
819
|
+
return await this.interaction.webhook.editMessage(targetId, this.editOptions(response));
|
|
820
|
+
}
|
|
821
|
+
async writeDeleteOriginal() {
|
|
822
|
+
await this.interaction.deleteReply();
|
|
1157
823
|
}
|
|
1158
|
-
|
|
824
|
+
async writeDeleteTarget(targetId) {
|
|
825
|
+
await this.interaction.deleteReply(targetId);
|
|
826
|
+
}
|
|
827
|
+
async writeModal(data) {
|
|
828
|
+
const { interaction } = this;
|
|
829
|
+
if (interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
830
|
+
await interaction.showModal(data);
|
|
831
|
+
}
|
|
832
|
+
guardModalSource() {
|
|
833
|
+
if (this.interaction.isModalSubmit()) this.rejectModalSubmit();
|
|
834
|
+
}
|
|
835
|
+
rejectModalSubmit() {
|
|
836
|
+
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyIllegalAckState, [
|
|
837
|
+
"showModal",
|
|
838
|
+
"this interaction is a modal submit",
|
|
839
|
+
"A modal cannot open another modal.",
|
|
840
|
+
this.routeId
|
|
841
|
+
]);
|
|
842
|
+
}
|
|
843
|
+
sourceInteraction(method) {
|
|
844
|
+
const { interaction } = this;
|
|
845
|
+
if (interaction.isMessageComponent()) return interaction;
|
|
846
|
+
if (interaction.isModalSubmit() && interaction.isFromMessage()) return interaction;
|
|
847
|
+
throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ReplyUpdateWithoutSource, [method, this.routeId]);
|
|
848
|
+
}
|
|
849
|
+
replyOptions(response, flags) {
|
|
850
|
+
const reply = this.serialize(response);
|
|
1159
851
|
return {
|
|
1160
|
-
components:
|
|
1161
|
-
flags
|
|
1162
|
-
...
|
|
1163
|
-
...
|
|
852
|
+
components: reply.components,
|
|
853
|
+
flags,
|
|
854
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
855
|
+
...reply.files && { files: [...reply.files] }
|
|
1164
856
|
};
|
|
1165
857
|
}
|
|
1166
|
-
|
|
858
|
+
editOptions(response) {
|
|
859
|
+
const reply = this.serialize(response);
|
|
1167
860
|
return {
|
|
1168
|
-
components:
|
|
861
|
+
components: reply.components,
|
|
1169
862
|
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
1170
|
-
...
|
|
1171
|
-
...
|
|
863
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
864
|
+
...reply.files && { files: [...reply.files] }
|
|
1172
865
|
};
|
|
1173
866
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
867
|
+
updateOptions(response) {
|
|
868
|
+
const reply = this.serialize(response);
|
|
869
|
+
return {
|
|
870
|
+
components: reply.components,
|
|
871
|
+
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
872
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
873
|
+
...reply.files && { files: [...reply.files] }
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
createdMessage(result) {
|
|
877
|
+
return result.resource?.message ?? void 0;
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
883
|
+
/**
|
|
884
|
+
* Computes the dispatch route for a chat-input or autocomplete interaction. The controller and
|
|
885
|
+
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
886
|
+
* interaction and a generated registry key always match.
|
|
887
|
+
*
|
|
888
|
+
* @internal
|
|
889
|
+
*/
|
|
890
|
+
function slashRouteOf(interaction) {
|
|
891
|
+
const group = interaction.options.getSubcommandGroup(false) ?? void 0;
|
|
892
|
+
const subcommand = interaction.options.getSubcommand(false) ?? void 0;
|
|
893
|
+
return (0, _seedcord_utils_internal.buildSlashRoute)(interaction.commandName, subcommand, group);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
//#endregion
|
|
897
|
+
//#region src/miscellaneous/FaultThrottle.ts
|
|
898
|
+
/**
|
|
899
|
+
* Drops duplicate fault reports inside a fixed window so a recurring fault (a database outage on a
|
|
900
|
+
* hot path) reports once per window instead of flooding the webhook. Keyed on a structural string, so
|
|
901
|
+
* the map stays small, and a stale entry is evicted on the next read of its key.
|
|
902
|
+
*
|
|
903
|
+
* @internal
|
|
904
|
+
*/
|
|
905
|
+
var FaultThrottle = class {
|
|
906
|
+
windowMs;
|
|
907
|
+
now;
|
|
908
|
+
lastReportedAt = /* @__PURE__ */ new Map();
|
|
909
|
+
constructor(windowMs = 6e4, now = () => Date.now()) {
|
|
910
|
+
this.windowMs = windowMs;
|
|
911
|
+
this.now = now;
|
|
912
|
+
}
|
|
913
|
+
/** Evicts the entry for this key if it has aged out, so the map never holds stale keys forever. */
|
|
914
|
+
shouldReport(key) {
|
|
915
|
+
const last = this.lastReportedAt.get(key);
|
|
916
|
+
if (last === void 0) return true;
|
|
917
|
+
if (this.now() - last < this.windowMs) return false;
|
|
918
|
+
this.lastReportedAt.delete(key);
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
markReported(key) {
|
|
922
|
+
this.lastReportedAt.set(key, this.now());
|
|
923
|
+
}
|
|
924
|
+
clear() {
|
|
925
|
+
this.lastReportedAt.clear();
|
|
1180
926
|
}
|
|
1181
927
|
};
|
|
1182
928
|
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region src/miscellaneous/extractErrorResponse.ts
|
|
931
|
+
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling");
|
|
932
|
+
const faultThrottle = new FaultThrottle();
|
|
933
|
+
/**
|
|
934
|
+
* Processes an error into a user-facing reply and routes its report.
|
|
935
|
+
*
|
|
936
|
+
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
937
|
+
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
938
|
+
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
939
|
+
* threaded into both the reply and the bus payload.
|
|
940
|
+
*/
|
|
941
|
+
function extractErrorResponse(error, core, origin) {
|
|
942
|
+
const uuid = node_crypto.randomUUID();
|
|
943
|
+
const developerUsername = core.config.notifications?.developerUsername;
|
|
944
|
+
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
945
|
+
uuid,
|
|
946
|
+
developerUsername
|
|
947
|
+
};
|
|
948
|
+
if (error instanceof _seedcord_core.Notice) {
|
|
949
|
+
if (error.report) reportFault(error, core, origin, uuid);
|
|
950
|
+
return {
|
|
951
|
+
uuid,
|
|
952
|
+
response: error.render(ctx)
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
reportRawFault(error, core, origin, uuid);
|
|
956
|
+
const override = core.config.errors?.defaultError;
|
|
957
|
+
return {
|
|
958
|
+
uuid,
|
|
959
|
+
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
function withThrottle(origin, error, publish) {
|
|
963
|
+
const key = faultKey(origin, error);
|
|
964
|
+
if (!faultThrottle.shouldReport(key)) {
|
|
965
|
+
logger$2.info(`throttled duplicate fault ${key}`);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
publish();
|
|
969
|
+
faultThrottle.markReported(key);
|
|
970
|
+
}
|
|
971
|
+
function reportFault(denial, core, origin, uuid) {
|
|
972
|
+
withThrottle(origin, denial, () => {
|
|
973
|
+
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
974
|
+
if (origin.interaction) core.bus.publish("handledException", {
|
|
975
|
+
denial,
|
|
976
|
+
uuid,
|
|
977
|
+
source: buildInteractionSource(origin.interaction)
|
|
978
|
+
});
|
|
979
|
+
else if (origin.event) core.bus.publish("handledException", {
|
|
980
|
+
denial,
|
|
981
|
+
uuid,
|
|
982
|
+
source: buildEventSource(origin.event, origin)
|
|
983
|
+
});
|
|
984
|
+
else core.bus.publish("unknownException", {
|
|
985
|
+
uuid,
|
|
986
|
+
error: denial,
|
|
987
|
+
...scalarActors(origin),
|
|
988
|
+
metadata: metadataFor(origin)
|
|
989
|
+
});
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
function causeLine(error) {
|
|
993
|
+
const { cause } = error;
|
|
994
|
+
if (!Error.isError(cause)) return "";
|
|
995
|
+
const [first] = cause.message.split("\n");
|
|
996
|
+
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
997
|
+
}
|
|
998
|
+
function reportRawFault(error, core, origin, uuid) {
|
|
999
|
+
withThrottle(origin, error, () => {
|
|
1000
|
+
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
1001
|
+
else logger$2.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
1002
|
+
core.bus.publish("unknownException", {
|
|
1003
|
+
uuid,
|
|
1004
|
+
error,
|
|
1005
|
+
...scalarActors(origin),
|
|
1006
|
+
metadata: metadataFor(origin)
|
|
1007
|
+
});
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
function scalarActors(origin) {
|
|
1011
|
+
return {
|
|
1012
|
+
guild: origin.guild ? {
|
|
1013
|
+
id: origin.guild.id,
|
|
1014
|
+
name: origin.guild.name
|
|
1015
|
+
} : void 0,
|
|
1016
|
+
user: origin.user ? {
|
|
1017
|
+
id: origin.user.id,
|
|
1018
|
+
username: origin.user.username
|
|
1019
|
+
} : void 0
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function metadataFor(origin) {
|
|
1023
|
+
if (origin.event) return {
|
|
1024
|
+
eventName: origin.event.name,
|
|
1025
|
+
handler: origin.event.handler,
|
|
1026
|
+
args: origin.event.args
|
|
1027
|
+
};
|
|
1028
|
+
return origin.metadata;
|
|
1029
|
+
}
|
|
1030
|
+
function buildEventSource(event, origin) {
|
|
1031
|
+
return {
|
|
1032
|
+
kind: "event",
|
|
1033
|
+
eventName: event.name,
|
|
1034
|
+
handler: event.handler,
|
|
1035
|
+
userId: origin.user?.id ?? null,
|
|
1036
|
+
guildId: origin.guild?.id ?? null,
|
|
1037
|
+
channelId: event.channelId,
|
|
1038
|
+
raw: event.args
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function faultKey(origin, error) {
|
|
1042
|
+
const name = error instanceof _seedcord_core.Notice ? error.name : error instanceof discord_js.DiscordAPIError ? String(error.code) : error.constructor.name;
|
|
1043
|
+
if (origin.event) return `${origin.event.name}:${origin.event.handler}:${name}`;
|
|
1044
|
+
if (origin.interaction) return `${interactionRoute(origin.interaction)}:${name}`;
|
|
1045
|
+
if (origin.route) return `autocomplete:${origin.route}:${name}`;
|
|
1046
|
+
return `autocomplete:${name}`;
|
|
1047
|
+
}
|
|
1048
|
+
/** The stable route id of a repliable interaction, its slash route path, command name, or customId prefix. */
|
|
1049
|
+
function interactionRoute(interaction) {
|
|
1050
|
+
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
1051
|
+
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
1052
|
+
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
1053
|
+
return "interaction";
|
|
1054
|
+
}
|
|
1055
|
+
function buildInteractionSource(interaction) {
|
|
1056
|
+
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
1057
|
+
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
1058
|
+
return {
|
|
1059
|
+
kind: "interaction",
|
|
1060
|
+
interactionKind: interactionKind(interaction),
|
|
1061
|
+
command,
|
|
1062
|
+
customId,
|
|
1063
|
+
userId: interaction.user.id,
|
|
1064
|
+
guildId: interaction.guildId,
|
|
1065
|
+
channelId: interaction.channelId,
|
|
1066
|
+
interactionId: interaction.id,
|
|
1067
|
+
raw: interaction
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function interactionKind(interaction) {
|
|
1071
|
+
if (interaction.isChatInputCommand()) return "slash";
|
|
1072
|
+
if (interaction.isContextMenuCommand()) return "context-menu";
|
|
1073
|
+
if (interaction.isButton()) return "button";
|
|
1074
|
+
if (interaction.isAnySelectMenu()) return "select";
|
|
1075
|
+
return "modal";
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1183
1078
|
//#endregion
|
|
1184
1079
|
//#region src/bot/confirm/reserved.ts
|
|
1185
1080
|
const RESERVED_CONFIRM_PREFIX = "__seedcord_confirm";
|
|
@@ -1224,9 +1119,9 @@ async function collectChoice(message, userId, timeoutMs) {
|
|
|
1224
1119
|
return null;
|
|
1225
1120
|
}
|
|
1226
1121
|
}
|
|
1227
|
-
async function settle(sender,
|
|
1122
|
+
async function settle(sender, message, outcome) {
|
|
1228
1123
|
if (outcome === void 0) {
|
|
1229
|
-
await
|
|
1124
|
+
await sender.delete(message).catch(() => void 0);
|
|
1230
1125
|
return;
|
|
1231
1126
|
}
|
|
1232
1127
|
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
@@ -1234,30 +1129,19 @@ async function settle(sender, interaction, message, outcome) {
|
|
|
1234
1129
|
async function getConfirmation(interaction, prompt, options) {
|
|
1235
1130
|
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
1236
1131
|
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
1237
|
-
const sender = new ReplySender(interaction);
|
|
1238
|
-
const message = await sender.send(response, ephemeral);
|
|
1239
|
-
if (!message) return false;
|
|
1132
|
+
const sender = new ReplySender(interaction, interactionRoute(interaction));
|
|
1133
|
+
const message = await sender.send(response, { ephemeral });
|
|
1240
1134
|
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
1241
1135
|
if (!winner) {
|
|
1242
|
-
await settle(sender,
|
|
1136
|
+
await settle(sender, message, options?.onTimeout);
|
|
1243
1137
|
return false;
|
|
1244
1138
|
}
|
|
1245
1139
|
await winner.deferUpdate().catch(() => void 0);
|
|
1246
1140
|
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
1247
|
-
await settle(sender,
|
|
1141
|
+
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
1248
1142
|
return confirmed;
|
|
1249
1143
|
}
|
|
1250
1144
|
|
|
1251
|
-
//#endregion
|
|
1252
|
-
//#region src/hmr/devChannel.ts
|
|
1253
|
-
let channel;
|
|
1254
|
-
function setDevChannel(next) {
|
|
1255
|
-
channel = next;
|
|
1256
|
-
}
|
|
1257
|
-
function getDevChannel() {
|
|
1258
|
-
return channel;
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
1145
|
//#endregion
|
|
1262
1146
|
//#region src/interfaces/Plugin.ts
|
|
1263
1147
|
/**
|
|
@@ -1285,7 +1169,7 @@ var Plugin = class extends _seedcord_event_emitter.TypedEventEmitter {
|
|
|
1285
1169
|
* @param patterns - Glob patterns relative to the project root
|
|
1286
1170
|
*/
|
|
1287
1171
|
registerCriticalFiles(patterns) {
|
|
1288
|
-
getDevChannel()?.send("seedcord:register-critical-files", { patterns });
|
|
1172
|
+
(0, _seedcord_core_internal.getDevChannel)()?.send("seedcord:register-critical-files", { patterns });
|
|
1289
1173
|
}
|
|
1290
1174
|
/** @internal */
|
|
1291
1175
|
removeListener(event, listener) {
|
|
@@ -1367,19 +1251,10 @@ var Pluggable = class Pluggable extends _seedcord_event_emitter.TypedEventEmitte
|
|
|
1367
1251
|
//#endregion
|
|
1368
1252
|
//#region src/handlers/BaseHandler.ts
|
|
1369
1253
|
/**
|
|
1370
|
-
*
|
|
1254
|
+
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
1255
|
+
* gate hook.
|
|
1371
1256
|
*/
|
|
1372
|
-
var BaseHandler = class {
|
|
1373
|
-
core;
|
|
1374
|
-
event;
|
|
1375
|
-
logger;
|
|
1376
|
-
dispatch;
|
|
1377
|
-
constructor(event, core, dispatch) {
|
|
1378
|
-
this.core = core;
|
|
1379
|
-
this.event = event;
|
|
1380
|
-
this.logger = new _seedcord_services.Logger(this.constructor.name);
|
|
1381
|
-
this.dispatch = dispatch;
|
|
1382
|
-
}
|
|
1257
|
+
var BaseHandler = class extends _seedcord_core.BaseHandler {
|
|
1383
1258
|
/** @internal */
|
|
1384
1259
|
getEvent() {
|
|
1385
1260
|
return this.event;
|
|
@@ -1403,36 +1278,49 @@ var InteractionMiddleware = class extends BaseHandler {
|
|
|
1403
1278
|
};
|
|
1404
1279
|
|
|
1405
1280
|
//#endregion
|
|
1406
|
-
//#region src/handlers/
|
|
1281
|
+
//#region src/handlers/RepliableHandler.ts
|
|
1407
1282
|
/**
|
|
1408
|
-
* Shared base the
|
|
1283
|
+
* Shared base the repliable interaction handlers extend.
|
|
1409
1284
|
*
|
|
1410
|
-
* Not a public entry point.
|
|
1411
|
-
* or {@link
|
|
1412
|
-
*
|
|
1285
|
+
* Not a public entry point. Extend {@link SlashHandler}, {@link ButtonHandler}, {@link ModalHandler},
|
|
1286
|
+
* {@link SelectMenuHandler}, or {@link ContextMenuHandler} instead. This class binds the core reply base
|
|
1287
|
+
* to the gateway sender.
|
|
1413
1288
|
*
|
|
1414
|
-
* @typeParam
|
|
1289
|
+
* @typeParam Event - The repliable interaction type this handler processes
|
|
1415
1290
|
*/
|
|
1416
|
-
var
|
|
1417
|
-
|
|
1418
|
-
|
|
1291
|
+
var RepliableHandler = class extends _seedcord_core.RepliableHandler {
|
|
1292
|
+
/** @internal */
|
|
1293
|
+
getEvent() {
|
|
1294
|
+
return this.event;
|
|
1295
|
+
}
|
|
1296
|
+
buildSender(event, _core, routeId) {
|
|
1297
|
+
return new ReplySender(event, routeId);
|
|
1419
1298
|
}
|
|
1420
1299
|
};
|
|
1421
1300
|
|
|
1422
1301
|
//#endregion
|
|
1423
|
-
//#region src/
|
|
1302
|
+
//#region src/handlers/interaction/InteractionHandler.ts
|
|
1424
1303
|
/**
|
|
1425
|
-
*
|
|
1426
|
-
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
1427
|
-
* interaction and a generated registry key always match.
|
|
1304
|
+
* Shared base the typed interaction handlers extend.
|
|
1428
1305
|
*
|
|
1429
|
-
* @
|
|
1306
|
+
* Not a public entry point. Extend {@link SlashHandler}, {@link ButtonHandler}, {@link ModalHandler},
|
|
1307
|
+
* or {@link SelectMenuHandler} instead. This class adds `showModal` on top of the reply members those bases
|
|
1308
|
+
* share.
|
|
1309
|
+
*
|
|
1310
|
+
* @typeParam Repliable - The interaction type this handler processes
|
|
1430
1311
|
*/
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1312
|
+
var InteractionHandler = class extends RepliableHandler {
|
|
1313
|
+
constructor(event, core, dispatch) {
|
|
1314
|
+
super(event, core, dispatch);
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Open a modal. Must be the initial response to this interaction. The modal kind rejects this call at
|
|
1318
|
+
* compile time (Discord forbids a modal in response to a modal).
|
|
1319
|
+
*/
|
|
1320
|
+
showModal(modal) {
|
|
1321
|
+
return this.sender.showModal(modal);
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1436
1324
|
|
|
1437
1325
|
//#endregion
|
|
1438
1326
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
@@ -1468,7 +1356,7 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
1468
1356
|
}
|
|
1469
1357
|
/**
|
|
1470
1358
|
* Run the arm for whichever command fired. Use this only when the handler is registered for several
|
|
1471
|
-
* routes.
|
|
1359
|
+
* routes.
|
|
1472
1360
|
*
|
|
1473
1361
|
* Provide one arm per registered route, keyed by its route string, and each arm receives that route's
|
|
1474
1362
|
* own narrowed options. The arms must cover every route in the generic, a missing route or an unknown
|
|
@@ -1492,7 +1380,7 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
1492
1380
|
*/
|
|
1493
1381
|
async match(arms) {
|
|
1494
1382
|
const route = slashRouteOf(this.event);
|
|
1495
|
-
const arm = arms[route];
|
|
1383
|
+
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
1496
1384
|
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.SlashMatchArmMissing, [route]);
|
|
1497
1385
|
return await arm(this.options);
|
|
1498
1386
|
}
|
|
@@ -1563,11 +1451,11 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1563
1451
|
constructor(event, core, dispatch) {
|
|
1564
1452
|
super(event, core, dispatch);
|
|
1565
1453
|
}
|
|
1454
|
+
decodedFocused;
|
|
1566
1455
|
/**
|
|
1567
1456
|
* The focused option for this interaction. `value` is always the raw partial string the user is typing,
|
|
1568
1457
|
* even for an integer or number option, so coerce it yourself when you need a number.
|
|
1569
1458
|
*/
|
|
1570
|
-
decodedFocused;
|
|
1571
1459
|
get focused() {
|
|
1572
1460
|
if (this.decodedFocused) return this.decodedFocused;
|
|
1573
1461
|
const raw = this.event.options.getFocused(true);
|
|
@@ -1583,7 +1471,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1583
1471
|
* match is how you read it. There is no single-field shortcut the way slash/component handlers have one.
|
|
1584
1472
|
*
|
|
1585
1473
|
* Provide one arm per autocompletable field across the registered commands, keyed by field name. Each
|
|
1586
|
-
* arm receives the focused partial value and a `respond`
|
|
1474
|
+
* arm receives the focused partial value and a `respond` typed to that field's choice value. The
|
|
1587
1475
|
* arms must cover every autocompletable field, a missing field or an unknown key is a compile error. A
|
|
1588
1476
|
* focused field with no arm, only reachable from a stale-deployed command, throws at runtime.
|
|
1589
1477
|
*
|
|
@@ -1605,11 +1493,15 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1605
1493
|
*/
|
|
1606
1494
|
async match(arms) {
|
|
1607
1495
|
const { name, value } = this.focused;
|
|
1608
|
-
const respond = (choices) => this.
|
|
1609
|
-
const arm = arms[name];
|
|
1496
|
+
const respond = (choices) => this.respond(choices);
|
|
1497
|
+
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
1610
1498
|
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.AutocompleteMatchArmMissing, [name]);
|
|
1611
1499
|
return await arm(value, respond);
|
|
1612
1500
|
}
|
|
1501
|
+
/** Send autocomplete suggestions, callback type 8. Prefer {@link match}, which restricts each field's choices to its declared type. */
|
|
1502
|
+
async respond(choices) {
|
|
1503
|
+
await this.event.respond(choices);
|
|
1504
|
+
}
|
|
1613
1505
|
/** The firing command route, for a field whose completion differs per registered command. */
|
|
1614
1506
|
get route() {
|
|
1615
1507
|
return slashRouteOf(this.event);
|
|
@@ -1617,7 +1509,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1617
1509
|
/**
|
|
1618
1510
|
* The already-entered options on this command, restricted to the kinds Discord resolves during autocomplete
|
|
1619
1511
|
* (string, integer, number, boolean) with every read nullable, since a sibling is partial while the user
|
|
1620
|
-
* types the focused field.
|
|
1512
|
+
* types the focused field. The focused field is on `this.focused`.
|
|
1621
1513
|
*/
|
|
1622
1514
|
get options() {
|
|
1623
1515
|
return this.event.options;
|
|
@@ -1629,25 +1521,25 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1629
1521
|
/**
|
|
1630
1522
|
* Shared base the customId-routed component handlers extend.
|
|
1631
1523
|
*
|
|
1632
|
-
* Not a public entry point.
|
|
1633
|
-
*
|
|
1634
|
-
* those bases share, so DO NOT use it directly.
|
|
1524
|
+
* Not a public entry point. Extend {@link ButtonHandler}, {@link SelectMenuHandler}, or {@link ModalHandler}
|
|
1525
|
+
* instead. This class defines the customId decode and the route matching those bases share.
|
|
1635
1526
|
*
|
|
1636
1527
|
* @typeParam Event - The component interaction type this handler processes
|
|
1637
1528
|
* @typeParam Defs - The customId route definitions registered on the concrete handler
|
|
1638
1529
|
*/
|
|
1639
1530
|
var ComponentHandler = class extends InteractionHandler {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1531
|
+
/** Rewrite the source message this component interaction came from. */
|
|
1532
|
+
update(response) {
|
|
1533
|
+
return this.sender.update(response);
|
|
1534
|
+
}
|
|
1535
|
+
/** Acknowledge the component without changing the source message. */
|
|
1536
|
+
deferUpdate() {
|
|
1537
|
+
return this.sender.deferUpdate();
|
|
1644
1538
|
}
|
|
1645
1539
|
decoded;
|
|
1646
1540
|
get route() {
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
this.decoded = decoded;
|
|
1650
|
-
return decoded;
|
|
1541
|
+
this.decoded ??= (0, _seedcord_core_internal.decodeComponentRoute)(this.constructor, this.event.customId);
|
|
1542
|
+
return this.decoded;
|
|
1651
1543
|
}
|
|
1652
1544
|
/**
|
|
1653
1545
|
* The decoded params of the single route this handler is registered for.
|
|
@@ -1666,10 +1558,11 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1666
1558
|
* handler `this.params` is `never`, so match is the only way to read the decoded params.
|
|
1667
1559
|
*
|
|
1668
1560
|
* Provide one arm per registered route, keyed by its prefix, and each arm receives that route's own
|
|
1669
|
-
* decoded params. The arms
|
|
1670
|
-
*
|
|
1561
|
+
* decoded params. The arms cover every registered route prefix, checked at compile time, and a prefix
|
|
1562
|
+
* unmatched at runtime throws `CustomIdMatchArmMissing`. Decoding runs before any arm, so a stale or
|
|
1563
|
+
* corrupt wire throws before an arm body executes.
|
|
1671
1564
|
*
|
|
1672
|
-
* @param arms - One
|
|
1565
|
+
* @param arms - One callback per registered route, keyed by prefix.
|
|
1673
1566
|
* @returns The result of the arm that ran.
|
|
1674
1567
|
*
|
|
1675
1568
|
* @example
|
|
@@ -1678,8 +1571,8 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1678
1571
|
* class ReviewButtons extends ButtonHandler<[typeof Approve, typeof Reject]> {
|
|
1679
1572
|
* async execute() {
|
|
1680
1573
|
* await this.match({
|
|
1681
|
-
* approve: ({ userId }) => this.
|
|
1682
|
-
* reject: ({ userId }) => this.
|
|
1574
|
+
* approve: ({ userId }) => this.reply(`approved <@${userId}>`),
|
|
1575
|
+
* reject: ({ userId }) => this.reply(`rejected <@${userId}>`)
|
|
1683
1576
|
* });
|
|
1684
1577
|
* }
|
|
1685
1578
|
* }
|
|
@@ -1687,7 +1580,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1687
1580
|
*/
|
|
1688
1581
|
async match(arms) {
|
|
1689
1582
|
const { prefix, params } = this.route;
|
|
1690
|
-
const arm = arms[prefix];
|
|
1583
|
+
const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
|
|
1691
1584
|
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CustomIdMatchArmMissing, [prefix]);
|
|
1692
1585
|
return await arm(params);
|
|
1693
1586
|
}
|
|
@@ -1699,8 +1592,9 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1699
1592
|
* Base class for a button interaction handler.
|
|
1700
1593
|
*
|
|
1701
1594
|
* Register the customId definitions this handler decodes with `@ButtonRoute`, list the same ones in the
|
|
1702
|
-
* generic, then read `this.params` for a single route or `this.match` for several.
|
|
1703
|
-
*
|
|
1595
|
+
* generic, then read `this.params` for a single route or `this.match` for several. Reply through the handler
|
|
1596
|
+
* members, or rewrite the source message with `this.update`. Passing different definitions to the decorator
|
|
1597
|
+
* and the generic is a compile error.
|
|
1704
1598
|
*
|
|
1705
1599
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ApproveId]`.
|
|
1706
1600
|
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
@@ -1711,7 +1605,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1711
1605
|
* class ApproveButton extends ButtonHandler<[typeof ApproveId]> {
|
|
1712
1606
|
* async execute() {
|
|
1713
1607
|
* const { userId } = this.params;
|
|
1714
|
-
* await this.
|
|
1608
|
+
* await this.reply(`approved <@${userId}>`);
|
|
1715
1609
|
* }
|
|
1716
1610
|
* }
|
|
1717
1611
|
* ```
|
|
@@ -1725,7 +1619,8 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1725
1619
|
*
|
|
1726
1620
|
* Register the customId definitions this handler decodes with `@ModalRoute`, list the same ones in the
|
|
1727
1621
|
* generic, then read `this.params` for a single route or `this.match` for several. Read the submitted
|
|
1728
|
-
* inputs from `this.event.fields
|
|
1622
|
+
* inputs from `this.event.fields`, and reply through the handler members. Discord forbids opening a modal in
|
|
1623
|
+
* response to a modal, so `showModal` fails compilation on this kind.
|
|
1729
1624
|
*
|
|
1730
1625
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ConfigId]`.
|
|
1731
1626
|
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
@@ -1737,7 +1632,7 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1737
1632
|
* async execute() {
|
|
1738
1633
|
* const { guildId } = this.params;
|
|
1739
1634
|
* const name = this.event.fields.getTextInputValue('name');
|
|
1740
|
-
* await this.
|
|
1635
|
+
* await this.reply(`saved ${name} for ${guildId}`);
|
|
1741
1636
|
* }
|
|
1742
1637
|
* }
|
|
1743
1638
|
* ```
|
|
@@ -1751,7 +1646,8 @@ var ModalHandler = class extends ComponentHandler {};
|
|
|
1751
1646
|
*
|
|
1752
1647
|
* Pass the select kind first and the customId definitions second, the same order as `@SelectMenuRoute`,
|
|
1753
1648
|
* so `this.event` and `this.event.values` are narrowed to that kind. Read `this.params` for a single
|
|
1754
|
-
* route or `this.match` for several
|
|
1649
|
+
* route or `this.match` for several, and reply through the handler members or rewrite the source message
|
|
1650
|
+
* with `this.update`.
|
|
1755
1651
|
*
|
|
1756
1652
|
* @typeParam Kind - The select kind from {@link SelectMenuKind}, e.g. `SelectMenuKind.User`.
|
|
1757
1653
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof AssignId]`.
|
|
@@ -1763,7 +1659,7 @@ var ModalHandler = class extends ComponentHandler {};
|
|
|
1763
1659
|
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
1764
1660
|
* async execute() {
|
|
1765
1661
|
* const { roleId } = this.params;
|
|
1766
|
-
* await this.
|
|
1662
|
+
* await this.reply(`assigning ${this.event.values.length} member(s) to <@&${roleId}>`);
|
|
1767
1663
|
* }
|
|
1768
1664
|
* }
|
|
1769
1665
|
* ```
|
|
@@ -2029,16 +1925,16 @@ var Paginator = class {
|
|
|
2029
1925
|
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
2030
1926
|
this.Handler = class Nav extends ButtonHandler {
|
|
2031
1927
|
async execute() {
|
|
2032
|
-
await this.
|
|
1928
|
+
await this.deferUpdate();
|
|
2033
1929
|
const response = await loadPage(contextOf(this.event, this.core), this.params.page);
|
|
2034
|
-
await
|
|
1930
|
+
await this.update(response);
|
|
2035
1931
|
}
|
|
2036
1932
|
};
|
|
2037
1933
|
}
|
|
2038
1934
|
/** Render page 0 and send it, picking reply or followUp from the interaction's state. */
|
|
2039
1935
|
async start(interaction, core) {
|
|
2040
1936
|
const response = await this.page(contextOf(interaction, core), 0);
|
|
2041
|
-
return new ReplySender(interaction).send(response, this.config.ephemeral ?? false);
|
|
1937
|
+
return new ReplySender(interaction, interactionRoute(interaction)).send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
2042
1938
|
}
|
|
2043
1939
|
/** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
|
|
2044
1940
|
async page(ctx, n) {
|
|
@@ -2120,7 +2016,7 @@ var Subscriber = class {
|
|
|
2120
2016
|
constructor(data, core) {
|
|
2121
2017
|
this.data = data;
|
|
2122
2018
|
this.core = core;
|
|
2123
|
-
this.logger = new
|
|
2019
|
+
this.logger = new _seedcord_logger.Logger(this.constructor.name);
|
|
2124
2020
|
}
|
|
2125
2021
|
};
|
|
2126
2022
|
|
|
@@ -2163,183 +2059,183 @@ function Subscribe(subscriber, options) {
|
|
|
2163
2059
|
}
|
|
2164
2060
|
|
|
2165
2061
|
//#endregion
|
|
2166
|
-
//#region src/subscribers/
|
|
2062
|
+
//#region src/subscribers/decorators/WebhookUrl.ts
|
|
2167
2063
|
/**
|
|
2168
|
-
*
|
|
2064
|
+
* Declares the environment variable a {@link WebhookLog} reporter reads its webhook url from.
|
|
2169
2065
|
*
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2066
|
+
* An unset variable disables the reporter with a boot warning. A set but malformed value throws
|
|
2067
|
+
* at boot.
|
|
2172
2068
|
*
|
|
2173
|
-
* @
|
|
2069
|
+
* @param envKey - The environment variable name holding the webhook url
|
|
2070
|
+
* @decorator
|
|
2071
|
+
* @example
|
|
2072
|
+
* ```ts
|
|
2073
|
+
* \@Subscribe('memberBanned')
|
|
2074
|
+
* \@WebhookUrl('BAN_LOG_WEBHOOK_URL')
|
|
2075
|
+
* class BanLog extends WebhookLog<'memberBanned'> {
|
|
2076
|
+
* report() {
|
|
2077
|
+
* return { components: [new BanCard(this.data).component] };
|
|
2078
|
+
* }
|
|
2079
|
+
* }
|
|
2080
|
+
* ```
|
|
2174
2081
|
*/
|
|
2175
|
-
|
|
2176
|
-
constructor
|
|
2177
|
-
|
|
2082
|
+
function WebhookUrl(envKey) {
|
|
2083
|
+
return function(constructor) {
|
|
2084
|
+
Reflect.defineMetadata(_seedcord_core_internal.WebhookUrlMetadataKey, envKey, constructor);
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
//#endregion
|
|
2089
|
+
//#region src/miscellaneous/flagsFor.ts
|
|
2090
|
+
/** @internal */
|
|
2091
|
+
function flagsFor(ephemeral) {
|
|
2092
|
+
let flags = discord_api_types_v10.MessageFlags.IsComponentsV2;
|
|
2093
|
+
if (ephemeral) flags |= discord_api_types_v10.MessageFlags.Ephemeral;
|
|
2094
|
+
return flags;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
//#endregion
|
|
2098
|
+
//#region src/subscribers/bases/webhookHelpers.ts
|
|
2099
|
+
const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
|
|
2100
|
+
function isDiscordWebhookUrl(value) {
|
|
2101
|
+
return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);
|
|
2102
|
+
}
|
|
2103
|
+
function jsonAttachment(name, description, data) {
|
|
2104
|
+
const content = (0, _seedcord_utils.filterCirculars)(data);
|
|
2105
|
+
return {
|
|
2106
|
+
name,
|
|
2107
|
+
description,
|
|
2108
|
+
data: Buffer.from(JSON.stringify(content, void 0, 2), "utf8")
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
function neutralizeFences(text) {
|
|
2112
|
+
return text.replaceAll("```", "```");
|
|
2113
|
+
}
|
|
2114
|
+
/** An error's stack with its direct cause appended (one level), ANSI-stripped for the webhook payload. */
|
|
2115
|
+
function errorReport(error) {
|
|
2116
|
+
let report = error.stack ?? `${error.name}: ${error.message}`;
|
|
2117
|
+
if (Error.isError(error.cause)) {
|
|
2118
|
+
const cause = error.cause.stack ?? `${error.cause.name}: ${error.cause.message}`;
|
|
2119
|
+
report += `\n\nCaused by:\n${cause}`;
|
|
2120
|
+
}
|
|
2121
|
+
const stripped = neutralizeFences((0, _seedcord_utils.stripAnsi)(report));
|
|
2122
|
+
return stripped.length > 1800 ? `${stripped.slice(0, 1799)}…` : stripped;
|
|
2123
|
+
}
|
|
2124
|
+
var WebhookSeparator = class extends _seedcord_core.BuilderComponent {
|
|
2125
|
+
constructor() {
|
|
2126
|
+
super("separator");
|
|
2127
|
+
this.instance.setSpacing(discord_api_types_v10.SeparatorSpacingSize.Small).setDivider(true);
|
|
2178
2128
|
}
|
|
2179
2129
|
};
|
|
2180
2130
|
|
|
2181
2131
|
//#endregion
|
|
2182
|
-
//#region src/
|
|
2132
|
+
//#region src/subscribers/bases/WebhookSender.ts
|
|
2133
|
+
const HTTP_NOT_FOUND = 404;
|
|
2134
|
+
const HTTP_UNAUTHORIZED = 401;
|
|
2183
2135
|
/**
|
|
2184
|
-
*
|
|
2185
|
-
*
|
|
2186
|
-
* You can either implement a custom `onHmr()` method in your class by extending `HmrAware`, or if it's a plugin, override the existing `onHmr()` with fully custom logic. Or, use this class's `handle()` method inside your `onHmr()` to get standard HMR handling for your modules based on the provided options you give to the constructor.
|
|
2136
|
+
* Sends component messages to a Discord webhook through `@discordjs/rest`.
|
|
2187
2137
|
*
|
|
2188
|
-
*
|
|
2138
|
+
* @throws A `SeedcordError` when the url does not carry a webhook id and token.
|
|
2189
2139
|
*/
|
|
2190
|
-
var
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
constructor(
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
};
|
|
2198
|
-
this.store = {
|
|
2199
|
-
fileToHandlers: /* @__PURE__ */ new Map(),
|
|
2200
|
-
fileToMiddlewares: /* @__PURE__ */ new Map(),
|
|
2201
|
-
handlerArtifacts: /* @__PURE__ */ new Map()
|
|
2202
|
-
};
|
|
2140
|
+
var WebhookSender = class {
|
|
2141
|
+
rest = new _discordjs_rest.REST();
|
|
2142
|
+
route;
|
|
2143
|
+
constructor(url) {
|
|
2144
|
+
const match = /\/webhooks\/(\d+)\/([\w$-]+)$/.exec(url);
|
|
2145
|
+
if (!match?.[1] || !match[2]) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigWebhookUrlInvalid, ["The url"]);
|
|
2146
|
+
this.route = discord_api_types_v10.Routes.webhook(match[1], match[2]);
|
|
2203
2147
|
}
|
|
2204
2148
|
/**
|
|
2205
|
-
*
|
|
2206
|
-
*
|
|
2207
|
-
* - If it's a deletion, it unloads the module.
|
|
2208
|
-
* - If it's an update/creation, it checks if the file is tracked, unloads it if so, then reloads the file if it is in the handlers or middlewares directory and is a valid handler/middleware.
|
|
2149
|
+
* Fetches the webhook object, sending no message. Returns `missing` on a 404 or 401 from
|
|
2150
|
+
* Discord and `unreachable` on any other failure.
|
|
2209
2151
|
*/
|
|
2210
|
-
async
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
return;
|
|
2216
|
-
}
|
|
2217
|
-
if (type === "update" && !(0, node_fs.existsSync)(file)) {
|
|
2218
|
-
if (this.isTracked(file)) {
|
|
2219
|
-
logger.info(`Received update for non-existent file: ${(0, _seedcord_utils.formatFilePath)(file)}, treating as delete`);
|
|
2220
|
-
this.unload(file);
|
|
2221
|
-
}
|
|
2222
|
-
return;
|
|
2223
|
-
}
|
|
2224
|
-
const rollbackEnabled = event.rollback ?? true;
|
|
2225
|
-
const filesToReload = affectedModules && affectedModules.length > 0 ? affectedModules : [file];
|
|
2226
|
-
const absHandlersDir = (0, node_path.resolve)(process.cwd(), handlersDir);
|
|
2227
|
-
const absMiddlewaresDir = middlewaresDir ? (0, node_path.resolve)(process.cwd(), middlewaresDir) : null;
|
|
2228
|
-
for (const fileToReload of filesToReload) {
|
|
2229
|
-
const isHandler = fileToReload.startsWith(absHandlersDir);
|
|
2230
|
-
const isMiddleware = absMiddlewaresDir ? fileToReload.startsWith(absMiddlewaresDir) : false;
|
|
2231
|
-
if (!isHandler && !isMiddleware) continue;
|
|
2232
|
-
await this.reloadWithRollback(fileToReload, rollbackEnabled);
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
async reloadWithRollback(file, rollbackEnabled) {
|
|
2236
|
-
const { logger } = this.options;
|
|
2237
|
-
const snapshot = this.snapshotUnits(file);
|
|
2238
|
-
this.unload(file);
|
|
2239
|
-
if (await this.reloadFile(file)) return;
|
|
2240
|
-
this.unload(file);
|
|
2241
|
-
if (!rollbackEnabled) return;
|
|
2242
|
-
this.restoreUnits(file, snapshot);
|
|
2243
|
-
logger.warn(`${chalk.default.yellow.bold("Rolled back")} ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))} to the last-good version`);
|
|
2244
|
-
}
|
|
2245
|
-
/**
|
|
2246
|
-
* Tracks a handler module as being loaded from a specific file.
|
|
2247
|
-
* @param file - The file path the handler was loaded from.
|
|
2248
|
-
* @param handler - The handler module instance.
|
|
2249
|
-
*/
|
|
2250
|
-
trackHandler(file, handler) {
|
|
2251
|
-
let set = this.store.fileToHandlers.get(file);
|
|
2252
|
-
if (!set) {
|
|
2253
|
-
set = /* @__PURE__ */ new Set();
|
|
2254
|
-
this.store.fileToHandlers.set(file, set);
|
|
2255
|
-
}
|
|
2256
|
-
set.add(handler);
|
|
2257
|
-
if (this.options.getArtifacts) {
|
|
2258
|
-
const artifacts = this.options.getArtifacts(handler);
|
|
2259
|
-
this.store.handlerArtifacts.set(handler, artifacts);
|
|
2152
|
+
async verify() {
|
|
2153
|
+
try {
|
|
2154
|
+
await this.rest.get(this.route, { auth: false });
|
|
2155
|
+
return "ok";
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
return error instanceof _discordjs_rest.DiscordAPIError && (error.status === HTTP_NOT_FOUND || error.status === HTTP_UNAUTHORIZED) ? "missing" : "unreachable";
|
|
2260
2158
|
}
|
|
2261
2159
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
this.store.handlerArtifacts.delete(handler);
|
|
2286
|
-
const name = handler.name ?? "Handler";
|
|
2287
|
-
logger.info(`${chalk.default.red.bold("Unloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
2288
|
-
}
|
|
2289
|
-
this.store.fileToHandlers.delete(file);
|
|
2290
|
-
}
|
|
2291
|
-
const middlewares = this.store.fileToMiddlewares.get(file);
|
|
2292
|
-
if (middlewares) {
|
|
2293
|
-
for (const middleware of middlewares) {
|
|
2294
|
-
this.options.unregisterMiddleware?.(middleware);
|
|
2295
|
-
const name = middleware.name ?? "Middleware";
|
|
2296
|
-
logger.info(`${chalk.default.red.bold("Unloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
2297
|
-
}
|
|
2298
|
-
this.store.fileToMiddlewares.delete(file);
|
|
2299
|
-
}
|
|
2160
|
+
async send(options) {
|
|
2161
|
+
const { flags, username, components, files } = options;
|
|
2162
|
+
await this.rest.post(this.route, {
|
|
2163
|
+
auth: false,
|
|
2164
|
+
query: new URLSearchParams({
|
|
2165
|
+
with_components: "true",
|
|
2166
|
+
wait: "true"
|
|
2167
|
+
}),
|
|
2168
|
+
body: {
|
|
2169
|
+
flags,
|
|
2170
|
+
username,
|
|
2171
|
+
components: components.map((component) => component.toJSON()),
|
|
2172
|
+
...files?.length && { attachments: files.map((file, id) => ({
|
|
2173
|
+
id,
|
|
2174
|
+
filename: file.name,
|
|
2175
|
+
description: file.description
|
|
2176
|
+
})) }
|
|
2177
|
+
},
|
|
2178
|
+
...files?.length && { files: files.map((file) => ({
|
|
2179
|
+
name: file.name,
|
|
2180
|
+
data: file.data
|
|
2181
|
+
})) }
|
|
2182
|
+
});
|
|
2300
2183
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2184
|
+
};
|
|
2185
|
+
|
|
2186
|
+
//#endregion
|
|
2187
|
+
//#region src/subscribers/bases/WebhookLog.ts
|
|
2188
|
+
/**
|
|
2189
|
+
* Base class for subscribers that deliver their event to a Discord webhook.
|
|
2190
|
+
*
|
|
2191
|
+
* Declare the url's environment variable with `@WebhookUrl` and implement {@link report}. Url
|
|
2192
|
+
* resolution, validation, sender reuse, and sending run in the base. When the variable is unset
|
|
2193
|
+
* the reporter is skipped at registration with a boot warning.
|
|
2194
|
+
*
|
|
2195
|
+
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
2196
|
+
*/
|
|
2197
|
+
var WebhookLog = class WebhookLog extends Subscriber {
|
|
2198
|
+
static senders = /* @__PURE__ */ new Map();
|
|
2199
|
+
async execute() {
|
|
2200
|
+
const url = WebhookLog.urlOf(WebhookLog.envKeyOf(this.constructor));
|
|
2201
|
+
if (url === null) {
|
|
2202
|
+
this.logger.warn(`${chalk.default.bold(this.constructor.name)} has no webhook url set, this reporter is disabled`);
|
|
2203
|
+
return;
|
|
2306
2204
|
}
|
|
2205
|
+
const { username, components, files } = await this.report();
|
|
2307
2206
|
try {
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
const name = val.name ?? "Handler";
|
|
2315
|
-
logger.info(`${chalk.default.blue.bold("Reloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
2316
|
-
} else if (isMiddleware && registerMiddleware && isMiddleware(val)) {
|
|
2317
|
-
registerMiddleware(val, file);
|
|
2318
|
-
this.trackMiddleware(file, val);
|
|
2319
|
-
const name = val.name ?? "Middleware";
|
|
2320
|
-
logger.info(`${chalk.default.blue.bold("Reloaded")} ${chalk.default.cyan.bold(name)} from ${chalk.default.gray((0, _seedcord_utils.formatFilePath)(file))}`);
|
|
2321
|
-
}
|
|
2322
|
-
return true;
|
|
2207
|
+
await WebhookLog.senderFor(url).send({
|
|
2208
|
+
flags: flagsFor(false),
|
|
2209
|
+
username: username ?? this.constructor.name,
|
|
2210
|
+
components,
|
|
2211
|
+
files
|
|
2212
|
+
});
|
|
2323
2213
|
} catch (error) {
|
|
2324
|
-
logger.error(
|
|
2325
|
-
return false;
|
|
2214
|
+
this.logger.error("Failed to send webhook report", error);
|
|
2326
2215
|
}
|
|
2327
2216
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
restoreUnits(file, snapshot) {
|
|
2335
|
-
for (const handler of snapshot.handlers) {
|
|
2336
|
-
this.options.registerHandler(handler, file);
|
|
2337
|
-
this.trackHandler(file, handler);
|
|
2338
|
-
}
|
|
2339
|
-
if (this.options.registerMiddleware) for (const middleware of snapshot.middlewares) {
|
|
2340
|
-
this.options.registerMiddleware(middleware, file);
|
|
2341
|
-
this.trackMiddleware(file, middleware);
|
|
2217
|
+
/** @internal */
|
|
2218
|
+
static senderFor(url) {
|
|
2219
|
+
let sender = WebhookLog.senders.get(url);
|
|
2220
|
+
if (!sender) {
|
|
2221
|
+
sender = new WebhookSender(url);
|
|
2222
|
+
WebhookLog.senders.set(url, sender);
|
|
2342
2223
|
}
|
|
2224
|
+
return sender;
|
|
2225
|
+
}
|
|
2226
|
+
/** @internal */
|
|
2227
|
+
static envKeyOf(ctor) {
|
|
2228
|
+
const envKey = Reflect.getMetadata(_seedcord_core_internal.WebhookUrlMetadataKey, ctor);
|
|
2229
|
+
if (!envKey) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.DecoratorWebhookUrlMissing, [ctor.name]);
|
|
2230
|
+
return envKey;
|
|
2231
|
+
}
|
|
2232
|
+
/** @internal Returns null when the env var is unset, throws when set but malformed. */
|
|
2233
|
+
static urlOf(envKey) {
|
|
2234
|
+
if (!envapt.Envapter.has(envKey)) return null;
|
|
2235
|
+
const raw = envapt.Envapter.getRequired(envKey, envapt.Converters.String).trim();
|
|
2236
|
+
if (raw === "") return null;
|
|
2237
|
+
if (!isDiscordWebhookUrl(raw)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigWebhookUrlInvalid, [envKey]);
|
|
2238
|
+
return raw;
|
|
2343
2239
|
}
|
|
2344
2240
|
};
|
|
2345
2241
|
|
|
@@ -2398,11 +2294,13 @@ var CommandRegistry = class {
|
|
|
2398
2294
|
core;
|
|
2399
2295
|
onDeployed;
|
|
2400
2296
|
name = "Commands";
|
|
2401
|
-
logger = new
|
|
2297
|
+
logger = new _seedcord_logger.Logger("Commands");
|
|
2402
2298
|
isInitialised = false;
|
|
2403
2299
|
globalCommands = [];
|
|
2404
2300
|
guildCommands = new discord_js.Collection();
|
|
2405
2301
|
ctorToCommand = /* @__PURE__ */ new Map();
|
|
2302
|
+
loading = false;
|
|
2303
|
+
loadedCommands = [];
|
|
2406
2304
|
hmrHandler;
|
|
2407
2305
|
pendingEvents = /* @__PURE__ */ new Map();
|
|
2408
2306
|
constructor(core, onDeployed) {
|
|
@@ -2411,7 +2309,7 @@ var CommandRegistry = class {
|
|
|
2411
2309
|
const commandsDir = this.core.config.bot.commands.path;
|
|
2412
2310
|
if (!commandsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["CommandRegistry", "commands"]);
|
|
2413
2311
|
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
2414
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
2312
|
+
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
2415
2313
|
handlersDir: commandsDir,
|
|
2416
2314
|
isHandler: this.isCommandClass.bind(this),
|
|
2417
2315
|
registerHandler: this.registerCommand.bind(this),
|
|
@@ -2429,15 +2327,32 @@ var CommandRegistry = class {
|
|
|
2429
2327
|
this.isInitialised = true;
|
|
2430
2328
|
const commandsDir = this.core.config.bot.commands.path;
|
|
2431
2329
|
if (!commandsDir) return;
|
|
2432
|
-
this.
|
|
2433
|
-
|
|
2434
|
-
|
|
2330
|
+
this.loading = true;
|
|
2331
|
+
this.loadedCommands.length = 0;
|
|
2332
|
+
try {
|
|
2333
|
+
await this.loadCommands(commandsDir);
|
|
2334
|
+
} finally {
|
|
2335
|
+
this.loading = false;
|
|
2336
|
+
}
|
|
2337
|
+
this.reportLoad();
|
|
2338
|
+
(0, _seedcord_core_internal.getDevChannel)()?.on("seedcord:refresh-commands", (data) => {
|
|
2339
|
+
this.refresh(data.shouldRefresh);
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
reportLoad() {
|
|
2343
|
+
const { utils } = this.logger;
|
|
2344
|
+
utils.summary("Loaded commands", {
|
|
2435
2345
|
global: this.globalCommands.length,
|
|
2436
2346
|
"guild groups": this.guildCommands.size
|
|
2437
2347
|
});
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2348
|
+
let slash = 0;
|
|
2349
|
+
let menu = 0;
|
|
2350
|
+
for (const command of this.loadedCommands) if (command.kind === "slash command") slash++;
|
|
2351
|
+
else menu++;
|
|
2352
|
+
utils.block("Loaded commands", [...utils.entries(this.loadedCommands), ...utils.counts({
|
|
2353
|
+
"slash commands": slash,
|
|
2354
|
+
"context menus": menu
|
|
2355
|
+
})]);
|
|
2441
2356
|
}
|
|
2442
2357
|
/** @internal */
|
|
2443
2358
|
async refresh(shouldRefresh = true) {
|
|
@@ -2457,11 +2372,11 @@ var CommandRegistry = class {
|
|
|
2457
2372
|
if (commandsDir && event.file.startsWith((0, node_path.resolve)(process.cwd(), commandsDir))) {
|
|
2458
2373
|
this.pendingEvents.delete(event.file);
|
|
2459
2374
|
this.pendingEvents.set(event.file, event);
|
|
2460
|
-
getDevChannel()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => (0, _seedcord_utils.formatFilePath)(f)) });
|
|
2375
|
+
(0, _seedcord_core_internal.getDevChannel)()?.send("seedcord:commands-update-prompt", { files: [...this.pendingEvents.keys()].map((f) => (0, _seedcord_utils.formatFilePath)(f)) });
|
|
2461
2376
|
} else await this.hmrHandler?.handle(event);
|
|
2462
2377
|
}
|
|
2463
2378
|
async loadCommands(dir) {
|
|
2464
|
-
await (0,
|
|
2379
|
+
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, rel, mod) => {
|
|
2465
2380
|
for (const exported of Object.values(mod)) if (this.isCommandClass(exported)) {
|
|
2466
2381
|
this.registerCommand(exported, rel);
|
|
2467
2382
|
this.hmrHandler?.trackHandler(fullPath, exported);
|
|
@@ -2476,25 +2391,27 @@ var CommandRegistry = class {
|
|
|
2476
2391
|
const meta = Reflect.getMetadata(_seedcord_core_internal.CommandMetadataKey, ctor);
|
|
2477
2392
|
if (!meta) return;
|
|
2478
2393
|
const comp = new ctor().component;
|
|
2479
|
-
const
|
|
2480
|
-
if (meta.scope === "global")
|
|
2481
|
-
|
|
2482
|
-
this.
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
for (const g of meta.guilds) {
|
|
2486
|
-
const arr = this.guildCommands.get(g) ?? [];
|
|
2487
|
-
arr.push(comp);
|
|
2488
|
-
this.guildCommands.set(g, arr);
|
|
2489
|
-
}
|
|
2490
|
-
this.logger.utils.registration(ctor.name, rel);
|
|
2491
|
-
this.logger.utils.item(`Guild ${commandType}: ${chalk.default.bold.cyan(comp.name)} for ${chalk.default.magenta.bold(meta.guilds.length)} guild(s)`);
|
|
2394
|
+
const kind = comp instanceof _discordjs_builders.SlashCommandBuilder ? "slash command" : "context menu";
|
|
2395
|
+
if (meta.scope === "global") this.globalCommands.push(comp);
|
|
2396
|
+
else for (const g of meta.guilds) {
|
|
2397
|
+
const arr = this.guildCommands.get(g) ?? [];
|
|
2398
|
+
arr.push(comp);
|
|
2399
|
+
this.guildCommands.set(g, arr);
|
|
2492
2400
|
}
|
|
2493
2401
|
this.ctorToCommand.set(ctor, {
|
|
2494
2402
|
name: comp.name,
|
|
2495
2403
|
scope: meta.scope,
|
|
2496
2404
|
...meta.scope === "guild" && { guilds: meta.guilds }
|
|
2497
2405
|
});
|
|
2406
|
+
if (this.loading) {
|
|
2407
|
+
this.loadedCommands.push({
|
|
2408
|
+
name: comp.name,
|
|
2409
|
+
from: (0, _seedcord_utils.formatFilePath)(rel),
|
|
2410
|
+
kind
|
|
2411
|
+
});
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
this.logger.utils.registration(comp.name, rel, `${meta.scope} ${kind}`);
|
|
2498
2415
|
}
|
|
2499
2416
|
unregisterCommand(ctor, artifacts) {
|
|
2500
2417
|
const info = artifacts ?? this.ctorToCommand.get(ctor);
|
|
@@ -2521,8 +2438,7 @@ var CommandRegistry = class {
|
|
|
2521
2438
|
const deployed = await this.core.bot.client.application?.commands.set(this.globalCommands);
|
|
2522
2439
|
if (deployed) result.global = deployed;
|
|
2523
2440
|
const tag = this.globalCommands.length === 1 ? "command" : "commands";
|
|
2524
|
-
this.logger.utils.
|
|
2525
|
-
this.logger.utils.item(`${this.globalCommands.map((command) => chalk.default.bold.cyan(command.name)).join(", ")}`);
|
|
2441
|
+
this.logger.utils.block(`Deployed ${this.globalCommands.length} global ${tag}`, this.logger.utils.wrap(this.globalCommands.map((command) => command.name)), "info");
|
|
2526
2442
|
}
|
|
2527
2443
|
for (const [guildId, commands] of this.guildCommands.entries()) {
|
|
2528
2444
|
const guild = this.core.bot.client.guilds.cache.get(guildId);
|
|
@@ -2533,269 +2449,101 @@ var CommandRegistry = class {
|
|
|
2533
2449
|
const deployed = await guild.commands.set(commands);
|
|
2534
2450
|
result.guilds.set(guildId, deployed);
|
|
2535
2451
|
const tag = commands.length === 1 ? "command" : "commands";
|
|
2536
|
-
this.logger.utils.
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
//#
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
const
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
}
|
|
2576
|
-
return {
|
|
2577
|
-
guild: null,
|
|
2578
|
-
member: null,
|
|
2579
|
-
user: null,
|
|
2580
|
-
channelId: null
|
|
2581
|
-
};
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
//#endregion
|
|
2585
|
-
//#region src/bot/gates/runGates.ts
|
|
2586
|
-
function interactionGateContext(interaction, core) {
|
|
2587
|
-
const rawMember = interaction.member;
|
|
2588
|
-
const memberRoleIds = rawMember instanceof discord_js.GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
|
|
2589
|
-
return {
|
|
2590
|
-
kind: "interaction",
|
|
2591
|
-
interaction,
|
|
2592
|
-
core,
|
|
2593
|
-
user: interaction.user,
|
|
2594
|
-
guild: interaction.guild,
|
|
2595
|
-
member: rawMember instanceof discord_js.GuildMember ? rawMember : null,
|
|
2596
|
-
userId: interaction.user.id,
|
|
2597
|
-
guildId: interaction.guildId,
|
|
2598
|
-
channelId: interaction.channelId,
|
|
2599
|
-
memberRoleIds,
|
|
2600
|
-
memberPermissions: interaction.memberPermissions?.bitfield ?? null,
|
|
2601
|
-
routeId: null
|
|
2602
|
-
};
|
|
2603
|
-
}
|
|
2604
|
-
function eventGateContext(eventName, args, core) {
|
|
2605
|
-
const actor = deriveEventActor(args);
|
|
2606
|
-
return {
|
|
2607
|
-
kind: "event",
|
|
2608
|
-
core,
|
|
2609
|
-
eventName,
|
|
2610
|
-
payload: args,
|
|
2611
|
-
user: actor.user,
|
|
2612
|
-
guild: actor.guild,
|
|
2613
|
-
member: actor.member,
|
|
2614
|
-
userId: actor.user?.id ?? null,
|
|
2615
|
-
guildId: actor.guild?.id ?? null,
|
|
2616
|
-
channelId: actor.channelId,
|
|
2617
|
-
memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
|
|
2618
|
-
memberPermissions: actor.member?.permissions.bitfield ?? null,
|
|
2619
|
-
routeId: null
|
|
2620
|
-
};
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
//#endregion
|
|
2624
|
-
//#region src/miscellaneous/FaultThrottle.ts
|
|
2625
|
-
/**
|
|
2626
|
-
* Drops duplicate fault reports inside a fixed window so a recurring fault (a database outage on a
|
|
2627
|
-
* hot path) reports once per window instead of flooding the webhook. Keyed on a structural string, so
|
|
2628
|
-
* the map stays small, and a stale entry is evicted on the next read of its key.
|
|
2629
|
-
*
|
|
2630
|
-
* @internal
|
|
2631
|
-
*/
|
|
2632
|
-
var FaultThrottle = class {
|
|
2633
|
-
windowMs;
|
|
2634
|
-
now;
|
|
2635
|
-
lastReportedAt = /* @__PURE__ */ new Map();
|
|
2636
|
-
constructor(windowMs = 6e4, now = () => Date.now()) {
|
|
2637
|
-
this.windowMs = windowMs;
|
|
2638
|
-
this.now = now;
|
|
2639
|
-
}
|
|
2640
|
-
/** Evicts the entry for this key if it has aged out, so the map never holds stale keys forever. */
|
|
2641
|
-
shouldReport(key) {
|
|
2642
|
-
const last = this.lastReportedAt.get(key);
|
|
2643
|
-
if (last === void 0) return true;
|
|
2644
|
-
if (this.now() - last < this.windowMs) return false;
|
|
2645
|
-
this.lastReportedAt.delete(key);
|
|
2646
|
-
return true;
|
|
2647
|
-
}
|
|
2648
|
-
markReported(key) {
|
|
2649
|
-
this.lastReportedAt.set(key, this.now());
|
|
2650
|
-
}
|
|
2651
|
-
clear() {
|
|
2652
|
-
this.lastReportedAt.clear();
|
|
2653
|
-
}
|
|
2654
|
-
};
|
|
2655
|
-
|
|
2656
|
-
//#endregion
|
|
2657
|
-
//#region src/miscellaneous/extractErrorResponse.ts
|
|
2658
|
-
const logger$2 = new _seedcord_services.Logger("ErrorsHandling");
|
|
2659
|
-
/**
|
|
2660
|
-
* The single throttle for every fault publish, so a recurring fault reports once per window across both
|
|
2661
|
-
* the interaction and event paths. Exported so tests reset it between cases.
|
|
2662
|
-
*
|
|
2663
|
-
* @internal
|
|
2664
|
-
*/
|
|
2665
|
-
const faultThrottle = new FaultThrottle();
|
|
2666
|
-
/**
|
|
2667
|
-
* Processes an error into a user-facing reply and routes its report.
|
|
2668
|
-
*
|
|
2669
|
-
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
2670
|
-
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
2671
|
-
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
2672
|
-
* threaded into both the reply and the bus payload.
|
|
2673
|
-
*
|
|
2674
|
-
* @internal
|
|
2675
|
-
*/
|
|
2676
|
-
function extractErrorResponse(error, core, origin) {
|
|
2677
|
-
const uuid = node_crypto.randomUUID();
|
|
2678
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
2679
|
-
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
2680
|
-
uuid,
|
|
2681
|
-
developerUsername
|
|
2682
|
-
};
|
|
2683
|
-
if (error instanceof _seedcord_core.Notice) {
|
|
2684
|
-
if (error.report) reportFault(error, core, origin, uuid);
|
|
2685
|
-
return {
|
|
2686
|
-
uuid,
|
|
2687
|
-
response: error.render(ctx)
|
|
2688
|
-
};
|
|
2689
|
-
}
|
|
2690
|
-
reportRawFault(error, core, origin, uuid);
|
|
2691
|
-
const override = core.config.errors?.defaultError;
|
|
2692
|
-
return {
|
|
2693
|
-
uuid,
|
|
2694
|
-
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
2695
|
-
};
|
|
2696
|
-
}
|
|
2697
|
-
function withThrottle(origin, error, publish) {
|
|
2698
|
-
const key = faultKey(origin, error);
|
|
2699
|
-
if (!faultThrottle.shouldReport(key)) {
|
|
2700
|
-
logger$2.info(`throttled duplicate fault ${key}`);
|
|
2701
|
-
return;
|
|
2702
|
-
}
|
|
2703
|
-
publish();
|
|
2704
|
-
faultThrottle.markReported(key);
|
|
2705
|
-
}
|
|
2706
|
-
function reportFault(denial, core, origin, uuid) {
|
|
2707
|
-
withThrottle(origin, denial, () => {
|
|
2708
|
-
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
2709
|
-
if (origin.interaction) core.bus.publish("handledException", {
|
|
2710
|
-
denial,
|
|
2711
|
-
uuid,
|
|
2712
|
-
source: buildInteractionSource(origin.interaction)
|
|
2713
|
-
});
|
|
2714
|
-
else if (origin.event) core.bus.publish("handledException", {
|
|
2715
|
-
denial,
|
|
2716
|
-
uuid,
|
|
2717
|
-
source: buildEventSource(origin.event, origin)
|
|
2718
|
-
});
|
|
2719
|
-
else core.bus.publish("unknownException", {
|
|
2720
|
-
uuid,
|
|
2721
|
-
error: denial,
|
|
2722
|
-
guild: origin.guild,
|
|
2723
|
-
user: origin.user,
|
|
2724
|
-
metadata: metadataFor(origin)
|
|
2725
|
-
});
|
|
2726
|
-
});
|
|
2727
|
-
}
|
|
2728
|
-
function reportRawFault(error, core, origin, uuid) {
|
|
2729
|
-
withThrottle(origin, error, () => {
|
|
2730
|
-
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
2731
|
-
else logger$2.error(`${uuid} | ${error.message}`);
|
|
2732
|
-
core.bus.publish("unknownException", {
|
|
2733
|
-
uuid,
|
|
2734
|
-
error,
|
|
2735
|
-
guild: origin.guild,
|
|
2736
|
-
user: origin.user,
|
|
2737
|
-
metadata: metadataFor(origin)
|
|
2738
|
-
});
|
|
2739
|
-
});
|
|
2740
|
-
}
|
|
2741
|
-
function metadataFor(origin) {
|
|
2742
|
-
if (origin.event) return {
|
|
2743
|
-
eventName: origin.event.name,
|
|
2744
|
-
handler: origin.event.handler,
|
|
2745
|
-
args: origin.event.args
|
|
2746
|
-
};
|
|
2747
|
-
return origin.metadata;
|
|
2748
|
-
}
|
|
2749
|
-
function buildEventSource(event, origin) {
|
|
2452
|
+
this.logger.utils.block(`Deployed ${commands.length} ${tag} to ${_seedcord_logger.paint.amber.bold(guild.name)}`, this.logger.utils.wrap(commands.map((command) => command.name)), "info");
|
|
2453
|
+
}
|
|
2454
|
+
this.onDeployed?.(result);
|
|
2455
|
+
return result;
|
|
2456
|
+
}
|
|
2457
|
+
/** The deduplicated slash route keys across every global and guild command. @internal */
|
|
2458
|
+
routeLeaves() {
|
|
2459
|
+
return slashRouteLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2460
|
+
}
|
|
2461
|
+
/** The registered context-menu command names, split by kind, across every global and guild command. @internal */
|
|
2462
|
+
contextMenuLeaves() {
|
|
2463
|
+
return contextMenuLeaves([...this.globalCommands, ...this.guildCommands.values()].flat());
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
|
|
2467
|
+
//#endregion
|
|
2468
|
+
//#region src/miscellaneous/deriveEventActor.ts
|
|
2469
|
+
function deriveEventActor(args) {
|
|
2470
|
+
const tuple = Array.isArray(args) ? args : [args];
|
|
2471
|
+
for (const arg of tuple) {
|
|
2472
|
+
if (arg instanceof discord_js.Message) return {
|
|
2473
|
+
guild: arg.guild,
|
|
2474
|
+
member: arg.member,
|
|
2475
|
+
user: arg.author,
|
|
2476
|
+
channelId: arg.channelId
|
|
2477
|
+
};
|
|
2478
|
+
if (arg instanceof discord_js.GuildMember) return {
|
|
2479
|
+
guild: arg.guild,
|
|
2480
|
+
member: arg,
|
|
2481
|
+
user: arg.user,
|
|
2482
|
+
channelId: null
|
|
2483
|
+
};
|
|
2484
|
+
if (arg instanceof discord_js.User) return {
|
|
2485
|
+
guild: null,
|
|
2486
|
+
member: null,
|
|
2487
|
+
user: arg,
|
|
2488
|
+
channelId: null
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2750
2491
|
return {
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
guildId: origin.guild?.id ?? null,
|
|
2756
|
-
channelId: event.channelId,
|
|
2757
|
-
raw: event.args
|
|
2492
|
+
guild: null,
|
|
2493
|
+
member: null,
|
|
2494
|
+
user: null,
|
|
2495
|
+
channelId: null
|
|
2758
2496
|
};
|
|
2759
2497
|
}
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
}
|
|
2767
|
-
function interactionRoute(interaction) {
|
|
2768
|
-
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
2769
|
-
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
2770
|
-
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
2771
|
-
return "interaction";
|
|
2772
|
-
}
|
|
2773
|
-
function buildInteractionSource(interaction) {
|
|
2774
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
2775
|
-
const customId = interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit() ? interaction.customId : null;
|
|
2498
|
+
|
|
2499
|
+
//#endregion
|
|
2500
|
+
//#region src/bot/gates/runGates.ts
|
|
2501
|
+
function interactionGateContext(interaction, core) {
|
|
2502
|
+
const rawMember = interaction.member;
|
|
2503
|
+
const memberRoleIds = rawMember instanceof discord_js.GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
|
|
2776
2504
|
return {
|
|
2777
2505
|
kind: "interaction",
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2506
|
+
interaction,
|
|
2507
|
+
core,
|
|
2508
|
+
user: interaction.user,
|
|
2509
|
+
guild: interaction.guild,
|
|
2510
|
+
member: rawMember instanceof discord_js.GuildMember ? rawMember : null,
|
|
2781
2511
|
userId: interaction.user.id,
|
|
2782
2512
|
guildId: interaction.guildId,
|
|
2783
2513
|
channelId: interaction.channelId,
|
|
2784
|
-
|
|
2785
|
-
|
|
2514
|
+
memberRoleIds,
|
|
2515
|
+
memberPermissions: interaction.memberPermissions?.bitfield ?? null,
|
|
2516
|
+
appPermissions: interaction.appPermissions.bitfield,
|
|
2517
|
+
memberGuildPermissions: rawMember instanceof discord_js.GuildMember ? rawMember.permissions.bitfield : null,
|
|
2518
|
+
appGuildPermissions: interaction.guild?.members.me?.permissions.bitfield ?? null,
|
|
2519
|
+
routeId: null
|
|
2786
2520
|
};
|
|
2787
2521
|
}
|
|
2788
|
-
function
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2522
|
+
function eventGateContext(eventName, args, core) {
|
|
2523
|
+
const actor = deriveEventActor(args);
|
|
2524
|
+
return {
|
|
2525
|
+
kind: "event",
|
|
2526
|
+
core,
|
|
2527
|
+
eventName,
|
|
2528
|
+
payload: args,
|
|
2529
|
+
user: actor.user,
|
|
2530
|
+
guild: actor.guild,
|
|
2531
|
+
member: actor.member,
|
|
2532
|
+
userId: actor.user?.id ?? null,
|
|
2533
|
+
guildId: actor.guild?.id ?? null,
|
|
2534
|
+
channelId: actor.channelId,
|
|
2535
|
+
memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
|
|
2536
|
+
memberPermissions: actor.member?.permissions.bitfield ?? null,
|
|
2537
|
+
appPermissions: null,
|
|
2538
|
+
memberGuildPermissions: actor.member?.permissions.bitfield ?? null,
|
|
2539
|
+
appGuildPermissions: actor.guild?.members.me?.permissions.bitfield ?? null,
|
|
2540
|
+
routeId: null
|
|
2541
|
+
};
|
|
2794
2542
|
}
|
|
2795
2543
|
|
|
2796
2544
|
//#endregion
|
|
2797
2545
|
//#region src/bot/handleEventFault.ts
|
|
2798
|
-
const logger$1 = new
|
|
2546
|
+
const logger$1 = new _seedcord_logger.Logger("EventBoundary");
|
|
2799
2547
|
/**
|
|
2800
2548
|
* The event controller boundary. Reports a throw from an event handler and never replies, since a
|
|
2801
2549
|
* generic event has no reply target. A {@link Silence} or a non-reporting {@link Notice} stops quietly, a
|
|
@@ -2805,7 +2553,7 @@ const logger$1 = new _seedcord_services.Logger("EventBoundary");
|
|
|
2805
2553
|
*/
|
|
2806
2554
|
function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
2807
2555
|
if (caught instanceof _seedcord_core.Silence) {
|
|
2808
|
-
if (caught.reason !== void 0) logger$1.debug(`Silence: ${caught.reason}`);
|
|
2556
|
+
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger$1.debug(`Silence: ${caught.reason}`);
|
|
2809
2557
|
return;
|
|
2810
2558
|
}
|
|
2811
2559
|
if (!Error.isError(caught)) throw caught;
|
|
@@ -2839,20 +2587,23 @@ function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
|
2839
2587
|
*/
|
|
2840
2588
|
var EventDispatcher = class {
|
|
2841
2589
|
core;
|
|
2842
|
-
logger = new
|
|
2590
|
+
logger = new _seedcord_logger.Logger("Events");
|
|
2843
2591
|
isInitialized = false;
|
|
2844
2592
|
name = "Events";
|
|
2845
2593
|
eventMap = /* @__PURE__ */ new Map();
|
|
2846
2594
|
middlewares = [];
|
|
2847
2595
|
executedOnceHandlers = /* @__PURE__ */ new Set();
|
|
2848
2596
|
attachedEvents = /* @__PURE__ */ new Set();
|
|
2597
|
+
loading = false;
|
|
2598
|
+
loadedHandlers = [];
|
|
2599
|
+
loadedMiddlewares = [];
|
|
2849
2600
|
hmrHandler;
|
|
2850
2601
|
constructor(core) {
|
|
2851
2602
|
this.core = core;
|
|
2852
2603
|
const eventsDir = this.core.config.bot.events.path;
|
|
2853
2604
|
if (!eventsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["EventDispatcher", "events"]);
|
|
2854
2605
|
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
2855
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
2606
|
+
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
2856
2607
|
handlersDir: eventsDir,
|
|
2857
2608
|
...(0, _seedcord_utils.hasKeys)(this.core.config.bot.events, ["middlewares"]) && { middlewaresDir: this.core.config.bot.events.middlewares },
|
|
2858
2609
|
isHandler: this.isEventHandlerClass.bind(this),
|
|
@@ -2875,23 +2626,32 @@ var EventDispatcher = class {
|
|
|
2875
2626
|
this.isInitialized = true;
|
|
2876
2627
|
const handlersDir = this.core.config.bot.events.path;
|
|
2877
2628
|
if (!handlersDir) return;
|
|
2878
|
-
this.logger.info(chalk.default.bold(handlersDir));
|
|
2879
2629
|
const middlewareDir = (0, _seedcord_utils.hasKeys)(this.core.config.bot.events, ["middlewares"]) ? this.core.config.bot.events.middlewares : void 0;
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2630
|
+
this.loading = true;
|
|
2631
|
+
this.loadedHandlers.length = 0;
|
|
2632
|
+
this.loadedMiddlewares.length = 0;
|
|
2633
|
+
try {
|
|
2634
|
+
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
2635
|
+
await this.loadHandlers(handlersDir);
|
|
2636
|
+
} finally {
|
|
2637
|
+
this.loading = false;
|
|
2883
2638
|
}
|
|
2884
|
-
await this.loadHandlers(handlersDir);
|
|
2885
2639
|
this.attachToClient();
|
|
2886
|
-
this.
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2640
|
+
this.reportLoad();
|
|
2641
|
+
}
|
|
2642
|
+
reportLoad() {
|
|
2643
|
+
const { utils } = this.logger;
|
|
2644
|
+
utils.summary("Loaded", {
|
|
2645
|
+
"event handlers": this.loadedHandlers.length,
|
|
2646
|
+
middlewares: this.loadedMiddlewares.length
|
|
2890
2647
|
});
|
|
2891
|
-
this.
|
|
2648
|
+
if (this.loadedMiddlewares.length > 0) utils.block("Loaded event middlewares", utils.entries(this.loadedMiddlewares));
|
|
2649
|
+
const perEvent = {};
|
|
2650
|
+
for (const [event, handlers] of this.eventMap) perEvent[event] = handlers.length;
|
|
2651
|
+
utils.block("Loaded event handlers", [...utils.entries(this.loadedHandlers), ...utils.counts(perEvent)]);
|
|
2892
2652
|
}
|
|
2893
2653
|
async loadHandlers(dir) {
|
|
2894
|
-
await (0,
|
|
2654
|
+
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
2895
2655
|
for (const val of Object.values(imported)) {
|
|
2896
2656
|
if (!this.isEventHandlerClass(val)) continue;
|
|
2897
2657
|
this.registerHandler(val, relativePath);
|
|
@@ -2900,7 +2660,7 @@ var EventDispatcher = class {
|
|
|
2900
2660
|
}, this.logger);
|
|
2901
2661
|
}
|
|
2902
2662
|
async loadMiddlewares(dir) {
|
|
2903
|
-
await (0,
|
|
2663
|
+
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
2904
2664
|
for (const val of Object.values(imported)) {
|
|
2905
2665
|
if (!this.isMiddlewareClass(val)) continue;
|
|
2906
2666
|
this.registerMiddleware(val, relativePath);
|
|
@@ -2938,7 +2698,14 @@ var EventDispatcher = class {
|
|
|
2938
2698
|
...metadata.events && { events: metadata.events }
|
|
2939
2699
|
});
|
|
2940
2700
|
this.middlewares.sort((a, b) => a.priority - b.priority);
|
|
2941
|
-
|
|
2701
|
+
if (this.loading) {
|
|
2702
|
+
this.loadedMiddlewares.push({
|
|
2703
|
+
name: `${middlewareCtor.name} (${metadata.priority})`,
|
|
2704
|
+
from: (0, _seedcord_utils.formatFilePath)(relativePath)
|
|
2705
|
+
});
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
this.logger.utils.registration(`${middlewareCtor.name} ${_seedcord_logger.paint.mute(`(${metadata.priority})`)}`, relativePath, "event middleware");
|
|
2942
2709
|
}
|
|
2943
2710
|
async runMiddlewares(eventName, args) {
|
|
2944
2711
|
for (const { ctor, events } of this.middlewares) {
|
|
@@ -2976,10 +2743,15 @@ var EventDispatcher = class {
|
|
|
2976
2743
|
};
|
|
2977
2744
|
if (Array.isArray(raw)) for (const entry of raw) register(entry.event, entry.frequency);
|
|
2978
2745
|
else {
|
|
2979
|
-
const names = areRoutes(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
2746
|
+
const names = (0, _seedcord_core_internal.areRoutes)(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
2980
2747
|
for (const name of names) register(name, "on");
|
|
2981
2748
|
}
|
|
2982
|
-
|
|
2749
|
+
const from = (0, _seedcord_utils.formatFilePath)(relativePath);
|
|
2750
|
+
if (this.loading) this.loadedHandlers.push({
|
|
2751
|
+
name: handlerClass.name,
|
|
2752
|
+
from
|
|
2753
|
+
});
|
|
2754
|
+
else this.logger.utils.registration(handlerClass.name, from);
|
|
2983
2755
|
}
|
|
2984
2756
|
attachToClient() {
|
|
2985
2757
|
for (const [eventName] of this.eventMap) this.attachListener(eventName);
|
|
@@ -2988,12 +2760,12 @@ var EventDispatcher = class {
|
|
|
2988
2760
|
if (this.attachedEvents.has(eventName)) return;
|
|
2989
2761
|
this.attachedEvents.add(eventName);
|
|
2990
2762
|
const handlerEntries = this.eventMap.get(eventName);
|
|
2991
|
-
this.logger.debug(`Attaching ${
|
|
2763
|
+
this.logger.debug(`Attaching ${_seedcord_logger.paint.mint.bold(eventName)} to the client with ${_seedcord_logger.paint.mute(handlerEntries?.length ?? 0)} handler(s)`);
|
|
2992
2764
|
this.core.bot.client.on(eventName, (...args) => {
|
|
2993
2765
|
this.core.bot.emitSafe("any:event", eventName, ...args);
|
|
2994
2766
|
(async () => {
|
|
2995
2767
|
await this.processEvent(eventName, args).catch((err) => {
|
|
2996
|
-
this.logger.error(`[${
|
|
2768
|
+
this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
|
|
2997
2769
|
this.core.bot.emit("error:unhandled:event", err);
|
|
2998
2770
|
});
|
|
2999
2771
|
})();
|
|
@@ -3015,7 +2787,7 @@ var EventDispatcher = class {
|
|
|
3015
2787
|
}
|
|
3016
2788
|
async processHandler(eventName, ctor, args) {
|
|
3017
2789
|
try {
|
|
3018
|
-
this.logger.debug(`Processing ${
|
|
2790
|
+
this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(eventName)} with ${_seedcord_logger.paint.mute(ctor.name)}`);
|
|
3019
2791
|
const handler = new ctor(args, this.core, eventName);
|
|
3020
2792
|
await (0, _seedcord_core_internal.runHandlerGates)(ctor, eventGateContext(eventName, args, this.core));
|
|
3021
2793
|
await handler.execute();
|
|
@@ -3026,35 +2798,55 @@ var EventDispatcher = class {
|
|
|
3026
2798
|
};
|
|
3027
2799
|
|
|
3028
2800
|
//#endregion
|
|
3029
|
-
//#region src/bot/defaults/
|
|
2801
|
+
//#region src/bot/defaults/UnhandledRepliable.ts
|
|
3030
2802
|
/**
|
|
3031
|
-
* Default handler for unhandled interactions.
|
|
3032
|
-
* raw fault, so it needs no decorator of its own.
|
|
3033
|
-
*
|
|
3034
|
-
* @internal
|
|
2803
|
+
* @internal Default handler for unhandled repliable interactions.
|
|
3035
2804
|
*/
|
|
3036
|
-
var
|
|
2805
|
+
var UnhandledRepliable = class extends InteractionHandler {
|
|
3037
2806
|
async execute() {
|
|
3038
|
-
await this.
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
2807
|
+
await this.reply("Feature not implemented yet.");
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2810
|
+
|
|
2811
|
+
//#endregion
|
|
2812
|
+
//#region src/bot/defaults/UnhandledAutocomplete.ts
|
|
2813
|
+
/**
|
|
2814
|
+
* @internal Default handler for an autocomplete with no registered handler.
|
|
2815
|
+
*/
|
|
2816
|
+
var UnhandledAutocomplete = class extends AutocompleteHandler {
|
|
2817
|
+
async execute() {
|
|
2818
|
+
await this.respond([]);
|
|
3042
2819
|
}
|
|
3043
2820
|
};
|
|
3044
2821
|
|
|
2822
|
+
//#endregion
|
|
2823
|
+
//#region src/bot/harmlessApiCodes.ts
|
|
2824
|
+
/**
|
|
2825
|
+
* Discord API error codes that mean a dead end on an interaction, a dead token, a double ack, or a gone
|
|
2826
|
+
* message. Nothing to show the user and no bug to report, so the boundary's fault-card send drops them.
|
|
2827
|
+
*
|
|
2828
|
+
* @internal
|
|
2829
|
+
*/
|
|
2830
|
+
const HARMLESS_API_CODES = new Set([
|
|
2831
|
+
discord_js.RESTJSONErrorCodes.UnknownInteraction,
|
|
2832
|
+
discord_js.RESTJSONErrorCodes.InteractionHasAlreadyBeenAcknowledged,
|
|
2833
|
+
discord_js.RESTJSONErrorCodes.UnknownMessage
|
|
2834
|
+
]);
|
|
2835
|
+
|
|
3045
2836
|
//#endregion
|
|
3046
2837
|
//#region src/bot/handleInteractionFault.ts
|
|
3047
|
-
const logger = new
|
|
2838
|
+
const logger = new _seedcord_logger.Logger("InteractionBoundary");
|
|
3048
2839
|
/**
|
|
3049
2840
|
* The interaction controller boundary. Sorts a throw from any handler-lifecycle phase into the right
|
|
3050
|
-
* user reply plus the right bus event
|
|
3051
|
-
*
|
|
2841
|
+
* user reply plus the right bus event. When a handler is built its sender is passed in, so the boundary
|
|
2842
|
+
* sends from the current ack state. A middleware or pre-construction throw arrives with no sender, so the
|
|
2843
|
+
* boundary builds one from the interaction flags.
|
|
3052
2844
|
*
|
|
3053
2845
|
* @internal
|
|
3054
2846
|
*/
|
|
3055
|
-
async function handleInteractionFault(caught, interaction, core) {
|
|
2847
|
+
async function handleInteractionFault(caught, interaction, core, sender) {
|
|
3056
2848
|
if (caught instanceof _seedcord_core.Silence) {
|
|
3057
|
-
if (caught.reason !== void 0) logger.debug(`Silence: ${caught.reason}`);
|
|
2849
|
+
if (caught.reason !== void 0 && (core.config.errors?.logSilences ?? true)) logger.debug(`Silence: ${caught.reason}`);
|
|
3058
2850
|
return;
|
|
3059
2851
|
}
|
|
3060
2852
|
if (!Error.isError(caught)) throw caught;
|
|
@@ -3078,7 +2870,18 @@ async function handleInteractionFault(caught, interaction, core) {
|
|
|
3078
2870
|
user: interaction.user,
|
|
3079
2871
|
metadata: interaction
|
|
3080
2872
|
});
|
|
3081
|
-
await new ReplySender(interaction)
|
|
2873
|
+
await sendGuarded(sender ?? new ReplySender(interaction, interactionRoute(interaction)), response, caught instanceof _seedcord_core.Notice ? caught.ephemeral : true);
|
|
2874
|
+
}
|
|
2875
|
+
async function sendGuarded(sender, response, ephemeral) {
|
|
2876
|
+
try {
|
|
2877
|
+
await sender.send(response, { ephemeral });
|
|
2878
|
+
} catch (error) {
|
|
2879
|
+
if (error instanceof discord_js.DiscordAPIError && HARMLESS_API_CODES.has(error.code)) {
|
|
2880
|
+
logger.debug(`reply send hit harmless code ${error.code}`);
|
|
2881
|
+
return;
|
|
2882
|
+
}
|
|
2883
|
+
logger.error("reply send failed", error);
|
|
2884
|
+
}
|
|
3082
2885
|
}
|
|
3083
2886
|
|
|
3084
2887
|
//#endregion
|
|
@@ -3091,7 +2894,7 @@ async function handleInteractionFault(caught, interaction, core) {
|
|
|
3091
2894
|
*/
|
|
3092
2895
|
var InteractionDispatcher = class {
|
|
3093
2896
|
core;
|
|
3094
|
-
logger = new
|
|
2897
|
+
logger = new _seedcord_logger.Logger("Interactions");
|
|
3095
2898
|
isInitialized = false;
|
|
3096
2899
|
name = "Interactions";
|
|
3097
2900
|
slashMap = /* @__PURE__ */ new Map();
|
|
@@ -3107,6 +2910,9 @@ var InteractionDispatcher = class {
|
|
|
3107
2910
|
autocompleteMap = /* @__PURE__ */ new Map();
|
|
3108
2911
|
keysToIgnore = /* @__PURE__ */ new Set();
|
|
3109
2912
|
middlewares = [];
|
|
2913
|
+
loading = false;
|
|
2914
|
+
loadedHandlers = [];
|
|
2915
|
+
loadedMiddlewares = [];
|
|
3110
2916
|
hmrHandler;
|
|
3111
2917
|
routeTypes = [
|
|
3112
2918
|
[_seedcord_core_internal.InteractionRoutes.Slash, this.slashMap],
|
|
@@ -3129,7 +2935,7 @@ var InteractionDispatcher = class {
|
|
|
3129
2935
|
const interactionsDir = this.core.config.bot.interactions.path;
|
|
3130
2936
|
if (!interactionsDir) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreControllerPathMissing, ["InteractionDispatcher", "interactions"]);
|
|
3131
2937
|
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
3132
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
2938
|
+
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
3133
2939
|
handlersDir: interactionsDir,
|
|
3134
2940
|
...(0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["middlewares"]) && this.core.config.bot.interactions.middlewares && { middlewaresDir: this.core.config.bot.interactions.middlewares },
|
|
3135
2941
|
isHandler: this.isHandlerClass.bind(this),
|
|
@@ -3144,28 +2950,28 @@ var InteractionDispatcher = class {
|
|
|
3144
2950
|
}
|
|
3145
2951
|
/**
|
|
3146
2952
|
* Warns for each command route with no registered `@SlashRoute` handler, which would fall through to
|
|
3147
|
-
*
|
|
2953
|
+
* UnhandledRepliable at runtime. A bot may route commands outside the registry, so this does not throw.
|
|
3148
2954
|
*
|
|
3149
2955
|
* @internal
|
|
3150
2956
|
*/
|
|
3151
2957
|
warnUnhandledRoutes(commandLeaves) {
|
|
3152
|
-
for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${
|
|
2958
|
+
for (const route of commandLeaves) if (!this.slashMap.has(route)) this.logger.warn(`Slash route ${_seedcord_logger.paint.sky.bold(route)} has no registered ${chalk.default.bold("@SlashRoute")} handler.`);
|
|
3153
2959
|
}
|
|
3154
2960
|
/**
|
|
3155
|
-
* Warns for each context-menu command with no matching handler, which would fall through to
|
|
2961
|
+
* Warns for each context-menu command with no matching handler, which would fall through to UnhandledRepliable
|
|
3156
2962
|
* at runtime. Checked per kind since a user and a message command can share a name.
|
|
3157
2963
|
*
|
|
3158
2964
|
* @internal
|
|
3159
2965
|
*/
|
|
3160
2966
|
warnUnhandledContextMenuRoutes(leaves) {
|
|
3161
|
-
for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${
|
|
3162
|
-
for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${
|
|
2967
|
+
for (const name of leaves.user) if (!this.userContextMenuMap.has(name)) this.logger.warn(`User context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
2968
|
+
for (const name of leaves.message) if (!this.messageContextMenuMap.has(name)) this.logger.warn(`Message context menu ${_seedcord_logger.paint.sky.bold(name)} has no registered ${chalk.default.bold("@ContextMenuRoute")} handler.`);
|
|
3163
2969
|
}
|
|
3164
2970
|
getArtifacts(handlerClass) {
|
|
3165
2971
|
const artifacts = [];
|
|
3166
2972
|
for (const [routeType] of this.routeTypes) {
|
|
3167
2973
|
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
3168
|
-
if (areRoutes(meta)) artifacts.push({
|
|
2974
|
+
if ((0, _seedcord_core_internal.areRoutes)(meta)) artifacts.push({
|
|
3169
2975
|
routeType,
|
|
3170
2976
|
routes: meta
|
|
3171
2977
|
});
|
|
@@ -3177,32 +2983,42 @@ var InteractionDispatcher = class {
|
|
|
3177
2983
|
this.isInitialized = true;
|
|
3178
2984
|
const handlersDir = this.core.config.bot.interactions.path;
|
|
3179
2985
|
if (!handlersDir) return;
|
|
3180
|
-
this.logger.info(chalk.default.bold(handlersDir));
|
|
3181
2986
|
const middlewareDir = (0, _seedcord_utils.hasKeys)(this.core.config.bot.interactions, ["middlewares"]) ? this.core.config.bot.interactions.middlewares : void 0;
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
2987
|
+
this.loading = true;
|
|
2988
|
+
this.loadedHandlers.length = 0;
|
|
2989
|
+
this.loadedMiddlewares.length = 0;
|
|
2990
|
+
try {
|
|
2991
|
+
if (middlewareDir) await this.loadMiddlewares(middlewareDir);
|
|
2992
|
+
await this.loadHandlers(handlersDir);
|
|
2993
|
+
} finally {
|
|
2994
|
+
this.loading = false;
|
|
3185
2995
|
}
|
|
3186
|
-
await this.loadHandlers(handlersDir);
|
|
3187
2996
|
this.attachToClient();
|
|
3188
|
-
this.
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
2997
|
+
this.reportLoad();
|
|
2998
|
+
}
|
|
2999
|
+
reportLoad() {
|
|
3000
|
+
const { utils } = this.logger;
|
|
3001
|
+
utils.summary("Loaded", {
|
|
3002
|
+
"interaction handlers": this.loadedHandlers.length,
|
|
3003
|
+
middlewares: this.loadedMiddlewares.length
|
|
3004
|
+
});
|
|
3005
|
+
if (this.loadedMiddlewares.length > 0) utils.block("Loaded middlewares", utils.entries(this.loadedMiddlewares));
|
|
3006
|
+
utils.block("Loaded interaction handlers", [...utils.entries(this.loadedHandlers), ...utils.counts({
|
|
3007
|
+
slash: this.slashMap.size,
|
|
3008
|
+
buttons: this.buttonMap.size,
|
|
3009
|
+
modals: this.modalMap.size,
|
|
3010
|
+
"string selects": this.stringSelectMap.size,
|
|
3011
|
+
"user selects": this.userSelectMap.size,
|
|
3012
|
+
"role selects": this.roleSelectMap.size,
|
|
3013
|
+
"channel selects": this.channelSelectMap.size,
|
|
3014
|
+
"mentionable selects": this.mentionableSelectMap.size,
|
|
3015
|
+
"message menus": this.messageContextMenuMap.size,
|
|
3016
|
+
"user menus": this.userContextMenuMap.size,
|
|
3017
|
+
autocomplete: this.autocompleteMap.size
|
|
3018
|
+
})]);
|
|
3203
3019
|
}
|
|
3204
3020
|
async loadHandlers(dir) {
|
|
3205
|
-
await (0,
|
|
3021
|
+
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
3206
3022
|
for (const val of Object.values(imported)) {
|
|
3207
3023
|
if (!this.isHandlerClass(val)) continue;
|
|
3208
3024
|
this.registerHandler(val, relativePath);
|
|
@@ -3211,7 +3027,7 @@ var InteractionDispatcher = class {
|
|
|
3211
3027
|
}, this.logger);
|
|
3212
3028
|
}
|
|
3213
3029
|
async loadMiddlewares(dir) {
|
|
3214
|
-
await (0,
|
|
3030
|
+
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
3215
3031
|
for (const val of Object.values(imported)) {
|
|
3216
3032
|
if (!this.isMiddlewareClass(val)) continue;
|
|
3217
3033
|
this.registerMiddleware(val, relativePath);
|
|
@@ -3229,7 +3045,14 @@ var InteractionDispatcher = class {
|
|
|
3229
3045
|
priority: metadata.priority
|
|
3230
3046
|
});
|
|
3231
3047
|
this.middlewares.sort((a, b) => a.priority - b.priority);
|
|
3232
|
-
|
|
3048
|
+
if (this.loading) {
|
|
3049
|
+
this.loadedMiddlewares.push({
|
|
3050
|
+
name: `${middlewareCtor.name} (${metadata.priority})`,
|
|
3051
|
+
from: (0, _seedcord_utils.formatFilePath)(relativePath)
|
|
3052
|
+
});
|
|
3053
|
+
return;
|
|
3054
|
+
}
|
|
3055
|
+
this.logger.utils.registration(`${middlewareCtor.name} ${_seedcord_logger.paint.mute(`(${metadata.priority})`)}`, relativePath, "middleware");
|
|
3233
3056
|
}
|
|
3234
3057
|
isHandlerClass(obj) {
|
|
3235
3058
|
if (typeof obj !== "function") return false;
|
|
@@ -3243,7 +3066,7 @@ var InteractionDispatcher = class {
|
|
|
3243
3066
|
const writes = [];
|
|
3244
3067
|
for (const [routeType, map] of this.routeTypes) {
|
|
3245
3068
|
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
3246
|
-
if (!areRoutes(meta)) continue;
|
|
3069
|
+
if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
|
|
3247
3070
|
for (const route of meta) {
|
|
3248
3071
|
const existing = map.get(route);
|
|
3249
3072
|
if (existing && existing !== handlerClass) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
@@ -3253,9 +3076,15 @@ var InteractionDispatcher = class {
|
|
|
3253
3076
|
]);
|
|
3254
3077
|
writes.push([map, route]);
|
|
3255
3078
|
}
|
|
3256
|
-
this.logger.utils.registration(handlerClass.name, (0, _seedcord_utils.formatFilePath)(relativePath));
|
|
3257
3079
|
}
|
|
3080
|
+
if (writes.length === 0) return;
|
|
3258
3081
|
for (const [map, route] of writes) map.set(route, handlerClass);
|
|
3082
|
+
const from = (0, _seedcord_utils.formatFilePath)(relativePath);
|
|
3083
|
+
if (this.loading) this.loadedHandlers.push({
|
|
3084
|
+
name: handlerClass.name,
|
|
3085
|
+
from
|
|
3086
|
+
});
|
|
3087
|
+
else this.logger.utils.registration(handlerClass.name, from);
|
|
3259
3088
|
}
|
|
3260
3089
|
/** @internal For use in dev mode */
|
|
3261
3090
|
async onHmr(event) {
|
|
@@ -3271,7 +3100,7 @@ var InteractionDispatcher = class {
|
|
|
3271
3100
|
}
|
|
3272
3101
|
for (const [routeType, map] of this.routeTypes) {
|
|
3273
3102
|
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
3274
|
-
if (!areRoutes(meta)) continue;
|
|
3103
|
+
if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
|
|
3275
3104
|
meta.forEach((route) => map.delete(route));
|
|
3276
3105
|
}
|
|
3277
3106
|
}
|
|
@@ -3283,7 +3112,7 @@ var InteractionDispatcher = class {
|
|
|
3283
3112
|
this.core.bot.client.on(discord_js.Events.InteractionCreate, (interaction) => {
|
|
3284
3113
|
this.core.bot.emitSafe("any:interaction", interaction);
|
|
3285
3114
|
this.handleInteraction(interaction).catch((err) => {
|
|
3286
|
-
this.logger.error(`[${
|
|
3115
|
+
this.logger.error(`[${_seedcord_logger.paint.coral.bold("UNHANDLED ERROR AT ROOT")}] ${err.name}`, err.stack);
|
|
3287
3116
|
this.core.bot.emit("error:unhandled:interaction", err);
|
|
3288
3117
|
});
|
|
3289
3118
|
});
|
|
@@ -3294,22 +3123,31 @@ var InteractionDispatcher = class {
|
|
|
3294
3123
|
if (!prefix) return this.logger.warn(`${interactionType} has invalid customId: ${interaction.customId}`);
|
|
3295
3124
|
await this.processInteraction(interaction, () => prefix, (key) => getMap().get(key));
|
|
3296
3125
|
}
|
|
3297
|
-
async processInteraction(interaction, extractKey, getHandler) {
|
|
3126
|
+
async processInteraction(interaction, extractKey, getHandler, fallback = UnhandledRepliable) {
|
|
3298
3127
|
const key = extractKey(interaction);
|
|
3128
|
+
let sender;
|
|
3299
3129
|
try {
|
|
3300
3130
|
if (!interaction.isAutocomplete()) for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
|
|
3301
3131
|
let HandlerCtor = getHandler(key);
|
|
3302
3132
|
if (!HandlerCtor) {
|
|
3303
|
-
this.logger.warn(`No handler found for key ${
|
|
3304
|
-
HandlerCtor =
|
|
3133
|
+
this.logger.warn(`No handler found for key ${_seedcord_logger.paint.sky.bold(key)}. Falling back to ${fallback.name}.`);
|
|
3134
|
+
HandlerCtor = fallback;
|
|
3305
3135
|
}
|
|
3306
|
-
this.logger.debug(`Processing ${
|
|
3136
|
+
this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(key)} with ${_seedcord_logger.paint.mute(HandlerCtor.name)}`);
|
|
3307
3137
|
const dispatch = new _seedcord_core.DispatchContext((0, _seedcord_core_internal.routeIdOf)(HandlerCtor));
|
|
3308
3138
|
const handler = new HandlerCtor(interaction, this.core, dispatch);
|
|
3309
|
-
if (
|
|
3139
|
+
if (handler instanceof RepliableHandler) sender = handler.getSender();
|
|
3140
|
+
if (!interaction.isAutocomplete()) {
|
|
3141
|
+
const monitor = (0, _seedcord_core_internal.slowGateMonitor)();
|
|
3142
|
+
try {
|
|
3143
|
+
await (0, _seedcord_core_internal.runHandlerGates)(HandlerCtor, interactionGateContext(interaction, this.core), dispatch.routeId ?? void 0, monitor?.observe);
|
|
3144
|
+
} finally {
|
|
3145
|
+
monitor?.report(dispatch.routeId);
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3310
3148
|
await handler.execute();
|
|
3311
3149
|
} catch (caught) {
|
|
3312
|
-
await handleInteractionFault(caught, interaction, this.core);
|
|
3150
|
+
await handleInteractionFault(caught, interaction, this.core, sender);
|
|
3313
3151
|
}
|
|
3314
3152
|
}
|
|
3315
3153
|
async handleInteraction(interaction) {
|
|
@@ -3385,12 +3223,7 @@ var InteractionDispatcher = class {
|
|
|
3385
3223
|
}
|
|
3386
3224
|
async handleAutocomplete(interaction) {
|
|
3387
3225
|
const route = slashRouteOf(interaction);
|
|
3388
|
-
|
|
3389
|
-
this.logger.warn(`No autocomplete handler for ${chalk.default.bold.cyan(route)}.`);
|
|
3390
|
-
await interaction.respond([]);
|
|
3391
|
-
return;
|
|
3392
|
-
}
|
|
3393
|
-
await this.processInteraction(interaction, () => route, (key) => this.autocompleteMap.get(key));
|
|
3226
|
+
await this.processInteraction(interaction, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
|
|
3394
3227
|
}
|
|
3395
3228
|
};
|
|
3396
3229
|
|
|
@@ -3418,7 +3251,7 @@ function __decorate(decorators, target, key, desc) {
|
|
|
3418
3251
|
*/
|
|
3419
3252
|
var Bot = class extends Plugin {
|
|
3420
3253
|
/** @internal */
|
|
3421
|
-
logger = new
|
|
3254
|
+
logger = new _seedcord_logger.Logger("Bot");
|
|
3422
3255
|
isInitialized = false;
|
|
3423
3256
|
_client;
|
|
3424
3257
|
interactions;
|
|
@@ -3446,7 +3279,7 @@ var Bot = class extends Plugin {
|
|
|
3446
3279
|
});
|
|
3447
3280
|
}
|
|
3448
3281
|
this.emojiInjector = new EmojiInjector(core);
|
|
3449
|
-
core.shutdown.addTask(
|
|
3282
|
+
core.shutdown.addTask(_seedcord_core_node.ShutdownPhase.DiscordCleanup, "stop-bot", async () => await this.stop(), 2e3);
|
|
3450
3283
|
}
|
|
3451
3284
|
/**
|
|
3452
3285
|
* Initializes Discord client and all controllers
|
|
@@ -3479,7 +3312,9 @@ var Bot = class extends Plugin {
|
|
|
3479
3312
|
* Logs the bot into Discord using the configured token
|
|
3480
3313
|
*/
|
|
3481
3314
|
async login(token) {
|
|
3482
|
-
|
|
3315
|
+
this._client.once(discord_js.Events.ClientReady, () => this.emit("ready"));
|
|
3316
|
+
this._client.login(token);
|
|
3317
|
+
await this.waitFor("ready");
|
|
3483
3318
|
this.logger.info(`Logged in as ${chalk.default.bold.magenta(this._client.user?.username)}!`);
|
|
3484
3319
|
return this;
|
|
3485
3320
|
}
|
|
@@ -3503,199 +3338,72 @@ var Bot = class extends Plugin {
|
|
|
3503
3338
|
this.logger.error(`listener for ${String(event)} threw`, error);
|
|
3504
3339
|
}
|
|
3505
3340
|
};
|
|
3506
|
-
__decorate([(0,
|
|
3507
|
-
|
|
3508
|
-
//#endregion
|
|
3509
|
-
//#region src/hmr/HmrManager.ts
|
|
3510
|
-
var HmrManager = class {
|
|
3511
|
-
logger = new _seedcord_services.Logger("HMR", { channel: "hmr" });
|
|
3512
|
-
listeners = /* @__PURE__ */ new Set();
|
|
3513
|
-
constructor() {}
|
|
3514
|
-
/** @internal */
|
|
3515
|
-
init() {
|
|
3516
|
-
if (!{}.hot) return;
|
|
3517
|
-
setDevChannel((0, _seedcord_types_internal.wrapHot)({}.hot));
|
|
3518
|
-
if (envapt.Envapter.isDevelopment || envapt.Envapter.isTest) {
|
|
3519
|
-
this.logger.info("Enabled");
|
|
3520
|
-
getDevChannel()?.on("seedcord:hmr", (payload) => {
|
|
3521
|
-
const affected = payload.affectedModules?.length ?? 0;
|
|
3522
|
-
this.logger.info(`${chalk.default.bold("1")} module changed, ${chalk.default.bold(affected)} affected modules`);
|
|
3523
|
-
if (payload.affectedModules) this.logger.utils.list(payload.affectedModules.map((mod) => (0, _seedcord_utils.formatFilePath)(mod)), "Affected modules: ");
|
|
3524
|
-
this.handleUpdate(payload);
|
|
3525
|
-
});
|
|
3526
|
-
}
|
|
3527
|
-
}
|
|
3528
|
-
/** @internal */
|
|
3529
|
-
register(listener) {
|
|
3530
|
-
this.listeners.add(listener);
|
|
3531
|
-
}
|
|
3532
|
-
/** @internal */
|
|
3533
|
-
unregister(listener) {
|
|
3534
|
-
this.listeners.delete(listener);
|
|
3535
|
-
}
|
|
3536
|
-
async handleUpdate(event) {
|
|
3537
|
-
const promises = [...this.listeners].map(async (listener) => {
|
|
3538
|
-
try {
|
|
3539
|
-
await listener.onHmr(event);
|
|
3540
|
-
} catch (error) {
|
|
3541
|
-
this.logger.error(`Error handling HMR update in ${chalk.default.bold(listener.name)}: `, error);
|
|
3542
|
-
}
|
|
3543
|
-
});
|
|
3544
|
-
await Promise.all(promises);
|
|
3545
|
-
}
|
|
3546
|
-
};
|
|
3547
|
-
|
|
3548
|
-
//#endregion
|
|
3549
|
-
//#region src/subscribers/bases/webhookHelpers.ts
|
|
3550
|
-
const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
|
|
3551
|
-
/**
|
|
3552
|
-
* Whether a string is a well-formed Discord webhook URL.
|
|
3553
|
-
*
|
|
3554
|
-
* @internal
|
|
3555
|
-
*/
|
|
3556
|
-
function isDiscordWebhookUrl(value) {
|
|
3557
|
-
return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);
|
|
3558
|
-
}
|
|
3559
|
-
/**
|
|
3560
|
-
* Serializes a value to a JSON attachment, made circular-safe first.
|
|
3561
|
-
*
|
|
3562
|
-
* @internal
|
|
3563
|
-
*/
|
|
3564
|
-
function jsonAttachment(name, description, data) {
|
|
3565
|
-
const content = (0, _seedcord_utils.filterCirculars)(data);
|
|
3566
|
-
return new discord_js.AttachmentBuilder(Buffer.from(JSON.stringify(content, void 0, 2), "utf8"), {
|
|
3567
|
-
name,
|
|
3568
|
-
description
|
|
3569
|
-
});
|
|
3570
|
-
}
|
|
3571
|
-
/**
|
|
3572
|
-
* The small divider the default webhook reporters place between sections.
|
|
3573
|
-
*
|
|
3574
|
-
* @internal
|
|
3575
|
-
*/
|
|
3576
|
-
var WebhookSeparator = class extends _seedcord_core.BuilderComponent {
|
|
3577
|
-
constructor() {
|
|
3578
|
-
super("separator");
|
|
3579
|
-
this.instance.setSpacing(discord_js.SeparatorSpacingSize.Small).setDivider(true);
|
|
3580
|
-
}
|
|
3581
|
-
};
|
|
3341
|
+
__decorate([(0, envapt_legacy.Envapt)("DISCORD_BOT_TOKEN", { converter: (raw) => (0, _seedcord_errors_internal.validateDiscordToken)(raw) }), __decorateMetadata("design:type", String)], Bot.prototype, "botToken", void 0);
|
|
3582
3342
|
|
|
3583
3343
|
//#endregion
|
|
3584
3344
|
//#region src/subscribers/default/HandledException.ts
|
|
3585
|
-
var _HandledException;
|
|
3586
|
-
function webhookUrlValidator$1(raw) {
|
|
3587
|
-
if (raw !== null && typeof raw !== "string") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigHandledExceptionWebhookInvalid);
|
|
3588
|
-
const value = raw?.trim() ?? "";
|
|
3589
|
-
if (value === "") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigHandledExceptionWebhookMissing);
|
|
3590
|
-
if (!isDiscordWebhookUrl(value)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigHandledExceptionWebhookInvalid);
|
|
3591
|
-
return value;
|
|
3592
|
-
}
|
|
3593
3345
|
let HandledException = class HandledException extends WebhookLog {
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
try {
|
|
3601
|
-
await this.webhook.send({
|
|
3602
|
-
flags: flagsFor(false),
|
|
3603
|
-
withComponents: true,
|
|
3604
|
-
username: "Handled Exception",
|
|
3605
|
-
components: [new HandledExceptionContainer(this.data).component],
|
|
3606
|
-
files: [jsonAttachment("source.json", "The raw source the fault came from", source.raw)]
|
|
3607
|
-
});
|
|
3608
|
-
} catch (error) {
|
|
3609
|
-
this.logger.error("Failed to send handled exception webhook", error);
|
|
3610
|
-
}
|
|
3346
|
+
report() {
|
|
3347
|
+
return {
|
|
3348
|
+
username: "Handled Exception",
|
|
3349
|
+
components: [new HandledExceptionContainer(this.data).component],
|
|
3350
|
+
files: [jsonAttachment("source.json", "The raw source the fault came from", this.data.source.raw)]
|
|
3351
|
+
};
|
|
3611
3352
|
}
|
|
3612
3353
|
};
|
|
3613
|
-
__decorate([(
|
|
3614
|
-
HandledException = _HandledException = __decorate([Subscribe("handledException")], HandledException);
|
|
3354
|
+
HandledException = __decorate([Subscribe("handledException"), WebhookUrl("HANDLED_EXCEPTION_WEBHOOK_URL")], HandledException);
|
|
3615
3355
|
var HandledExceptionContainer = class extends _seedcord_core.BuilderComponent {
|
|
3616
3356
|
constructor(data) {
|
|
3617
3357
|
super("container");
|
|
3618
3358
|
const { denial, uuid, source } = data;
|
|
3619
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source))).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${causeStack(denial)}\`\`\``)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Source")).addFileComponents((file) => file.setURL("attachment://source.json"));
|
|
3359
|
+
this.instance.addTextDisplayComponents((text) => text.setContent(faultSummary(denial, source))).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${neutralizeFences(causeStack(denial))}\`\`\``)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent("### Source")).addFileComponents((file) => file.setURL("attachment://source.json"));
|
|
3620
3360
|
}
|
|
3621
3361
|
};
|
|
3622
|
-
/**
|
|
3623
|
-
* The summary block of a fault report, narrowed per source kind so an interaction fault shows its
|
|
3624
|
-
* command and customId while an event fault shows its event name and handler.
|
|
3625
|
-
*
|
|
3626
|
-
* @internal
|
|
3627
|
-
*/
|
|
3628
3362
|
function faultSummary(denial, source) {
|
|
3629
|
-
const head = `### A handled fault was reported: \`${denial.name}\`\n**Message:** ${denial.message}\n
|
|
3363
|
+
const head = (0, _seedcord_utils.stripAnsi)(`### A handled fault was reported: \`${denial.name}\`\n**Message:** ${denial.message}\n`);
|
|
3630
3364
|
if (source.kind === "event") return `${head}**Event:** ${source.eventName}\n**Handler:** ${source.handler}\n**User ID:** \`${source.userId ?? "n/a"}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\``;
|
|
3631
3365
|
return `${head}**Kind:** ${source.interactionKind}\n**Command:** ${source.command ?? "n/a"}\n**CustomId:** ${source.customId ?? "n/a"}\n**User ID:** \`${source.userId}\`\n**Guild ID:** \`${source.guildId ?? "n/a"}\`\n**Channel ID:** \`${source.channelId ?? "n/a"}\`\n**Interaction ID:** \`${source.interactionId}\``;
|
|
3632
3366
|
}
|
|
3633
|
-
/**
|
|
3634
|
-
* The text shown for a denial's cause in the report. Tolerates a non-serializable cause (a bigint or a
|
|
3635
|
-
* circular object) so building the report never throws.
|
|
3636
|
-
*
|
|
3637
|
-
* @internal
|
|
3638
|
-
*/
|
|
3639
3367
|
function causeStack(denial) {
|
|
3640
3368
|
const { cause } = denial;
|
|
3641
|
-
if (Error.isError(cause)) return cause.stack ?? cause.message;
|
|
3642
|
-
if (typeof cause === "string") return cause;
|
|
3369
|
+
if (Error.isError(cause)) return (0, _seedcord_utils.stripAnsi)(cause.stack ?? cause.message);
|
|
3370
|
+
if (typeof cause === "string") return (0, _seedcord_utils.stripAnsi)(cause);
|
|
3643
3371
|
if (typeof cause === "bigint" || typeof cause === "symbol" || typeof cause === "function") return cause.toString();
|
|
3644
3372
|
if (cause !== void 0) try {
|
|
3645
3373
|
return JSON.stringify(cause);
|
|
3646
3374
|
} catch {
|
|
3647
3375
|
return "[unserializable cause]";
|
|
3648
3376
|
}
|
|
3649
|
-
return denial.stack ?? "No cause recorded.";
|
|
3377
|
+
return (0, _seedcord_utils.stripAnsi)(denial.stack ?? "No cause recorded.");
|
|
3650
3378
|
}
|
|
3651
3379
|
|
|
3652
3380
|
//#endregion
|
|
3653
3381
|
//#region src/subscribers/default/UnknownException.ts
|
|
3654
|
-
var _UnknownException;
|
|
3655
|
-
function webhookUrlValidator(raw) {
|
|
3656
|
-
if (raw !== null && typeof raw !== "string") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
|
|
3657
|
-
const value = raw?.trim() ?? "";
|
|
3658
|
-
if (value === "") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
|
|
3659
|
-
if (!isDiscordWebhookUrl(value)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
|
|
3660
|
-
return value;
|
|
3661
|
-
}
|
|
3662
3382
|
let UnknownException = class UnknownException extends WebhookLog {
|
|
3663
|
-
|
|
3664
|
-
_UnknownException = this;
|
|
3665
|
-
}
|
|
3666
|
-
webhook = new discord_js.WebhookClient({ url: _UnknownException.unknownExceptionWebhookUrl });
|
|
3667
|
-
async execute() {
|
|
3383
|
+
report() {
|
|
3668
3384
|
const { metadata } = this.data;
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
username: "Unknown Exception",
|
|
3675
|
-
components: [new UnhandledErrorContainer(this.data).component],
|
|
3676
|
-
files: metadataFile ? [metadataFile] : []
|
|
3677
|
-
});
|
|
3678
|
-
} catch (error) {
|
|
3679
|
-
this.logger.error("Failed to send unknown exception webhook", error);
|
|
3680
|
-
}
|
|
3385
|
+
return {
|
|
3386
|
+
username: "Unknown Exception",
|
|
3387
|
+
components: [new UnhandledErrorContainer(this.data).component],
|
|
3388
|
+
files: metadata ? [jsonAttachment("metadata.json", "Metadata associated with the error", metadata)] : void 0
|
|
3389
|
+
};
|
|
3681
3390
|
}
|
|
3682
3391
|
};
|
|
3683
|
-
__decorate([(
|
|
3684
|
-
UnknownException = _UnknownException = __decorate([Subscribe("unknownException")], UnknownException);
|
|
3392
|
+
UnknownException = __decorate([Subscribe("unknownException"), WebhookUrl("UNKNOWN_EXCEPTION_WEBHOOK_URL")], UnknownException);
|
|
3685
3393
|
var UnhandledErrorContainer = class extends _seedcord_core.BuilderComponent {
|
|
3686
3394
|
constructor(data) {
|
|
3687
3395
|
super("container");
|
|
3688
3396
|
const { uuid, error, guild, user, metadata } = data;
|
|
3689
|
-
this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown\n**Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`\n**Guild Name:** ${guild?.name ?? "Not used in a guild"}\n**User ID:** \`${user?.id ?? "Missing user info"}\`\n**Username:** ${user?.username ?? "Missing user info"}\n`)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${error
|
|
3397
|
+
this.instance.addTextDisplayComponents((text) => text.setContent(`### An unknown exception was thrown\n**Guild ID:** \`${guild?.id ?? "Not used in a guild"}\`\n**Guild Name:** ${guild?.name ?? "Not used in a guild"}\n**User ID:** \`${user?.id ?? "Missing user info"}\`\n**Username:** ${user?.username ?? "Missing user info"}\n`)).addSeparatorComponents(new WebhookSeparator().component).addTextDisplayComponents((text) => text.setContent(`### UUID \`${uuid}\`\n\`\`\`${errorReport(error)}\`\`\``));
|
|
3690
3398
|
this.addTimestampsIfAvailable(error);
|
|
3691
3399
|
this.addMetadataIfAvailable(metadata);
|
|
3692
3400
|
}
|
|
3693
3401
|
addTimestampsIfAvailable(error) {
|
|
3694
|
-
if (!(error instanceof
|
|
3402
|
+
if (!(error instanceof _discordjs_rest.DiscordAPIError)) return;
|
|
3695
3403
|
const now = Date.now();
|
|
3696
3404
|
const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
|
|
3697
3405
|
if (!snowflake) return void 0;
|
|
3698
|
-
const interactionTs =
|
|
3406
|
+
const interactionTs = (0, _seedcord_utils.timestampFromSnowflake)(snowflake);
|
|
3699
3407
|
const diff = now - interactionTs;
|
|
3700
3408
|
const seconds = Math.floor(diff / 1e3);
|
|
3701
3409
|
const millis = diff % 1e3;
|
|
@@ -3710,28 +3418,25 @@ var UnhandledErrorContainer = class extends _seedcord_core.BuilderComponent {
|
|
|
3710
3418
|
//#endregion
|
|
3711
3419
|
//#region src/subscribers/Bus.ts
|
|
3712
3420
|
/**
|
|
3713
|
-
*
|
|
3714
|
-
*
|
|
3715
|
-
* Provides a centralized system for registering and executing custom subscribers
|
|
3716
|
-
* throughout the application lifecycle. Bus subscribers are loaded from configured directories
|
|
3717
|
-
* and can be triggered programmatically or by framework events. Accessed via `core.bus`. Do not
|
|
3718
|
-
* construct it directly.
|
|
3421
|
+
* Registers and dispatches subscribers. Subscribers load from the configured directories and run
|
|
3422
|
+
* on a programmatic or framework-event publish. Accessed via `core.bus`. Do not construct it directly.
|
|
3719
3423
|
*/
|
|
3720
3424
|
var Bus = class extends Plugin {
|
|
3721
3425
|
core;
|
|
3722
|
-
logger = new
|
|
3426
|
+
logger = new _seedcord_logger.Logger("Subscribers");
|
|
3723
3427
|
/** @internal */
|
|
3724
3428
|
name = "Subscribers";
|
|
3725
3429
|
isInitialized = false;
|
|
3726
3430
|
subscribersMap = /* @__PURE__ */ new Map();
|
|
3727
3431
|
executedOnceHandlers = /* @__PURE__ */ new Set();
|
|
3432
|
+
webhookProbes = /* @__PURE__ */ new Map();
|
|
3728
3433
|
hmrHandler;
|
|
3729
3434
|
constructor(core) {
|
|
3730
3435
|
super(core);
|
|
3731
3436
|
this.core = core;
|
|
3732
3437
|
if (this.core.config.subscribers.path) {
|
|
3733
3438
|
if (!envapt.Envapter.isDevelopment && !envapt.Envapter.isTest) return;
|
|
3734
|
-
this.hmrHandler = new HmrModuleHandler({
|
|
3439
|
+
this.hmrHandler = new _seedcord_core_hmr.HmrModuleHandler({
|
|
3735
3440
|
handlersDir: this.core.config.subscribers.path,
|
|
3736
3441
|
isHandler: this.isSubscriber.bind(this),
|
|
3737
3442
|
registerHandler: this.registerSubscriber.bind(this),
|
|
@@ -3750,7 +3455,6 @@ var Bus = class extends Plugin {
|
|
|
3750
3455
|
this.isInitialized = true;
|
|
3751
3456
|
this.registerSubscriber(UnknownException);
|
|
3752
3457
|
this.registerSubscriber(HandledException);
|
|
3753
|
-
envapt.Envapter.require("UNKNOWN_EXCEPTION_WEBHOOK_URL", "HANDLED_EXCEPTION_WEBHOOK_URL");
|
|
3754
3458
|
const subscribersDir = this.core.config.subscribers.path;
|
|
3755
3459
|
if (subscribersDir) {
|
|
3756
3460
|
this.logger.info(chalk.default.bold(subscribersDir));
|
|
@@ -3758,13 +3462,24 @@ var Bus = class extends Plugin {
|
|
|
3758
3462
|
const totalSubscribers = [...this.subscribersMap.values()].reduce((acc, handlers) => acc + handlers.length, 0);
|
|
3759
3463
|
this.logger.utils.list([`${chalk.default.bold.magenta(totalSubscribers)} subscribers`], chalk.default.bold.green("Loaded"));
|
|
3760
3464
|
}
|
|
3465
|
+
if (!envapt.Envapter.isTest) await this.verifyWebhooks();
|
|
3466
|
+
}
|
|
3467
|
+
/** @internal Exposed for tests. */
|
|
3468
|
+
async verifyWebhooks() {
|
|
3469
|
+
const missing = [];
|
|
3470
|
+
await Promise.all([...this.webhookProbes].map(async ([url, envKeys]) => {
|
|
3471
|
+
const result = await WebhookLog.senderFor(url).verify();
|
|
3472
|
+
if (result === "missing") missing.push(...envKeys);
|
|
3473
|
+
else if (result === "unreachable") this.logger.warn(`could not verify webhook at ${_seedcord_logger.paint.sky.bold(envKeys.join(", "))}`);
|
|
3474
|
+
}));
|
|
3475
|
+
if (missing.length > 0) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigWebhookNotFound, [missing.join(", ")]);
|
|
3761
3476
|
}
|
|
3762
|
-
/** @internal
|
|
3477
|
+
/** @internal */
|
|
3763
3478
|
async onHmr(event) {
|
|
3764
3479
|
if (this.hmrHandler) await this.hmrHandler.handle(event);
|
|
3765
3480
|
}
|
|
3766
3481
|
async loadSubscribers(dir) {
|
|
3767
|
-
await (0,
|
|
3482
|
+
await (0, _seedcord_utils_node.traverseDirectory)(dir, (fullPath, relativePath, imported) => {
|
|
3768
3483
|
for (const exportName of Object.keys(imported)) {
|
|
3769
3484
|
const val = imported[exportName];
|
|
3770
3485
|
if (this.isSubscriber(val)) {
|
|
@@ -3776,6 +3491,17 @@ var Bus = class extends Plugin {
|
|
|
3776
3491
|
}, this.logger);
|
|
3777
3492
|
}
|
|
3778
3493
|
registerSubscriber(handler) {
|
|
3494
|
+
if (handler.prototype instanceof WebhookLog) {
|
|
3495
|
+
const envKey = WebhookLog.envKeyOf(handler);
|
|
3496
|
+
const url = WebhookLog.urlOf(envKey);
|
|
3497
|
+
if (url === null) {
|
|
3498
|
+
this.logger.warn(`${chalk.default.bold(handler.name)} disabled, ${_seedcord_logger.paint.sky.bold(envKey)} is not set`);
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
const envKeys = this.webhookProbes.get(url) ?? [];
|
|
3502
|
+
if (!envKeys.includes(envKey)) envKeys.push(envKey);
|
|
3503
|
+
this.webhookProbes.set(url, envKeys);
|
|
3504
|
+
}
|
|
3779
3505
|
const options = Reflect.getMetadata(_seedcord_core_internal.SubscribeMetadataKey, handler);
|
|
3780
3506
|
let handlers = this.subscribersMap.get(options.subscriber);
|
|
3781
3507
|
if (!handlers) {
|
|
@@ -3835,7 +3561,7 @@ var Bus = class extends Plugin {
|
|
|
3835
3561
|
//#endregion
|
|
3836
3562
|
//#region src/version.ts
|
|
3837
3563
|
/** Package version */
|
|
3838
|
-
const version = "0.1.0-next.
|
|
3564
|
+
const version = "0.1.0-next.3";
|
|
3839
3565
|
|
|
3840
3566
|
//#endregion
|
|
3841
3567
|
//#region src/Seedcord.ts
|
|
@@ -3854,6 +3580,8 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3854
3580
|
/** @internal */
|
|
3855
3581
|
version = version;
|
|
3856
3582
|
static isInstantiated = false;
|
|
3583
|
+
healthCheck;
|
|
3584
|
+
hmrManager;
|
|
3857
3585
|
/** @see {@link CoordinatedShutdown} */
|
|
3858
3586
|
shutdown;
|
|
3859
3587
|
/** @see {@link CoordinatedStartup} */
|
|
@@ -3864,10 +3592,6 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3864
3592
|
bot;
|
|
3865
3593
|
/** @see {@link IRateLimiter} */
|
|
3866
3594
|
rateLimiter;
|
|
3867
|
-
/** @see {@link HealthCheck} */
|
|
3868
|
-
healthCheck;
|
|
3869
|
-
/** @see {@link HmrManager} */
|
|
3870
|
-
hmrManager;
|
|
3871
3595
|
/**
|
|
3872
3596
|
* Creates a new Seedcord instance
|
|
3873
3597
|
*
|
|
@@ -3877,42 +3601,43 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3877
3601
|
constructor(config) {
|
|
3878
3602
|
if (Seedcord.isInstantiated) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreSingletonViolation);
|
|
3879
3603
|
Seedcord.isInstantiated = true;
|
|
3880
|
-
const shutdown = new
|
|
3881
|
-
const startup = new
|
|
3604
|
+
const shutdown = new _seedcord_core_node.CoordinatedShutdown(config.shutdownEnabled);
|
|
3605
|
+
const startup = new _seedcord_core_node.CoordinatedStartup();
|
|
3882
3606
|
super(shutdown, startup);
|
|
3883
3607
|
this.config = config;
|
|
3608
|
+
(0, _seedcord_logger_node.installNodeDefaults)(config.logger);
|
|
3884
3609
|
(0, _seedcord_core_internal.setBotColor)(config.botColor);
|
|
3885
3610
|
this.shutdown = shutdown;
|
|
3886
3611
|
this.startup = startup;
|
|
3887
|
-
this.hmrManager = new HmrManager();
|
|
3612
|
+
this.hmrManager = new _seedcord_core_internal.HmrManager();
|
|
3888
3613
|
this.hmrManager.init();
|
|
3889
3614
|
this.bus = new Bus(this);
|
|
3890
3615
|
this.bot = new Bot(this);
|
|
3891
3616
|
this.rateLimiter = config.store ?? new _seedcord_rate_limiter.MemoryRateLimiter();
|
|
3892
|
-
this.healthCheck = new
|
|
3617
|
+
this.healthCheck = new _seedcord_core_node.HealthCheck(this.shutdown, config.healthCheck);
|
|
3893
3618
|
this.registerStartupTasks();
|
|
3894
3619
|
}
|
|
3895
|
-
/**
|
|
3620
|
+
/** The bot's discord username, populated after login. */
|
|
3621
|
+
get username() {
|
|
3622
|
+
return this.bot.client.user?.username;
|
|
3623
|
+
}
|
|
3896
3624
|
static reset() {
|
|
3897
3625
|
Seedcord.isInstantiated = false;
|
|
3626
|
+
_seedcord_logger.LoggerChannelRegistry.instance.reset();
|
|
3898
3627
|
}
|
|
3899
|
-
/**
|
|
3900
|
-
* Registers default startup tasks
|
|
3901
|
-
* @internal
|
|
3902
|
-
*/
|
|
3903
3628
|
registerStartupTasks() {
|
|
3904
3629
|
if (envapt.Envapter.isDevelopment || envapt.Envapter.isTest) this.registerHmrAwareModules();
|
|
3905
|
-
this.startup.addTask(
|
|
3630
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Configuration, "Bus Initialization", async () => {
|
|
3906
3631
|
this.bus.logger.utils.initialization("Subscribers", "start");
|
|
3907
3632
|
await this.bus.init();
|
|
3908
3633
|
this.bus.logger.utils.initialization("Subscribers", "end");
|
|
3909
3634
|
});
|
|
3910
|
-
this.startup.addTask(
|
|
3635
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Instantiation, "Bot Initialization", async () => {
|
|
3911
3636
|
this.bot.logger.utils.initialization("Bot", "start");
|
|
3912
3637
|
await this.bot.init();
|
|
3913
3638
|
this.bot.logger.utils.initialization("Bot", "end");
|
|
3914
3639
|
});
|
|
3915
|
-
this.startup.addTask(
|
|
3640
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Ready, "Health Check", async () => {
|
|
3916
3641
|
this.healthCheck.logger.utils.initialization("HealthCheck", "start");
|
|
3917
3642
|
await this.healthCheck.init();
|
|
3918
3643
|
this.healthCheck.logger.utils.initialization("HealthCheck", "end");
|
|
@@ -3928,7 +3653,7 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3928
3653
|
return this;
|
|
3929
3654
|
}
|
|
3930
3655
|
registerHmrAwareModules() {
|
|
3931
|
-
this.startup.addTask(
|
|
3656
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Configuration, "HMR Registration", async () => {
|
|
3932
3657
|
this.hmrManager.register(this.bot);
|
|
3933
3658
|
this.hmrManager.register(this.bus);
|
|
3934
3659
|
for (const plugin of this.plugins) this.hmrManager.register(plugin);
|
|
@@ -3940,49 +3665,45 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3940
3665
|
//#endregion
|
|
3941
3666
|
exports.ArraySource = ArraySource;
|
|
3942
3667
|
exports.AutocompleteHandler = AutocompleteHandler;
|
|
3943
|
-
exports.AutocompleteRoute = AutocompleteRoute;
|
|
3944
3668
|
exports.ButtonHandler = ButtonHandler;
|
|
3945
|
-
exports.ButtonRoute = ButtonRoute;
|
|
3946
3669
|
exports.CommandMentions = CommandMentions;
|
|
3947
3670
|
exports.ContextMenuHandler = ContextMenuHandler;
|
|
3948
|
-
exports.ContextMenuRoute = ContextMenuRoute;
|
|
3949
3671
|
exports.CursorSource = CursorSource;
|
|
3950
3672
|
exports.Emojis = Emojis;
|
|
3951
3673
|
exports.EventHandler = EventHandler;
|
|
3952
3674
|
exports.EventMiddleware = EventMiddleware;
|
|
3953
3675
|
exports.Gated = Gated;
|
|
3954
|
-
exports.HmrModuleHandler = HmrModuleHandler;
|
|
3955
3676
|
exports.IgnoreBots = IgnoreBots;
|
|
3956
3677
|
exports.InteractionHandler = InteractionHandler;
|
|
3957
3678
|
exports.InteractionMiddleware = InteractionMiddleware;
|
|
3958
3679
|
exports.Middleware = Middleware;
|
|
3959
3680
|
exports.MiddlewareType = MiddlewareType;
|
|
3960
3681
|
exports.ModalHandler = ModalHandler;
|
|
3961
|
-
exports.ModalRoute = ModalRoute;
|
|
3962
3682
|
exports.Nsfw = Nsfw;
|
|
3963
3683
|
exports.Paginator = Paginator;
|
|
3964
3684
|
exports.Pluggable = Pluggable;
|
|
3965
3685
|
exports.Plugin = Plugin;
|
|
3966
|
-
|
|
3686
|
+
exports.RegisterEvent = RegisterEvent;
|
|
3687
|
+
exports.RepliableHandler = RepliableHandler;
|
|
3688
|
+
exports.Seedcord = Seedcord;
|
|
3689
|
+
exports.SelectMenuHandler = SelectMenuHandler;
|
|
3690
|
+
Object.defineProperty(exports, 'ShutdownPhase', {
|
|
3967
3691
|
enumerable: true,
|
|
3968
3692
|
get: function () {
|
|
3969
|
-
return
|
|
3693
|
+
return _seedcord_core_node.ShutdownPhase;
|
|
3970
3694
|
}
|
|
3971
3695
|
});
|
|
3972
|
-
exports.RegisterEvent = RegisterEvent;
|
|
3973
|
-
exports.ReplySender = ReplySender;
|
|
3974
|
-
exports.RequireBotPermissions = RequireBotPermissions;
|
|
3975
|
-
exports.RequirePermissions = RequirePermissions;
|
|
3976
|
-
exports.RequireRole = RequireRole;
|
|
3977
|
-
exports.Seedcord = Seedcord;
|
|
3978
|
-
exports.SelectMenuHandler = SelectMenuHandler;
|
|
3979
|
-
exports.SelectMenuKind = SelectMenuKind;
|
|
3980
|
-
exports.SelectMenuRoute = SelectMenuRoute;
|
|
3981
3696
|
exports.SlashHandler = SlashHandler;
|
|
3982
|
-
exports
|
|
3697
|
+
Object.defineProperty(exports, 'StartupPhase', {
|
|
3698
|
+
enumerable: true,
|
|
3699
|
+
get: function () {
|
|
3700
|
+
return _seedcord_core_node.StartupPhase;
|
|
3701
|
+
}
|
|
3702
|
+
});
|
|
3983
3703
|
exports.Subscribe = Subscribe;
|
|
3984
3704
|
exports.Subscriber = Subscriber;
|
|
3985
3705
|
exports.WebhookLog = WebhookLog;
|
|
3706
|
+
exports.WebhookUrl = WebhookUrl;
|
|
3986
3707
|
exports.checkBotPermissions = checkBotPermissions;
|
|
3987
3708
|
exports.checkPermissions = checkPermissions;
|
|
3988
3709
|
exports.fetchGuildMember = fetchGuildMember;
|
|
@@ -4017,17 +3738,17 @@ Object.keys(_seedcord_event_emitter).forEach(function (k) {
|
|
|
4017
3738
|
});
|
|
4018
3739
|
});
|
|
4019
3740
|
|
|
4020
|
-
Object.keys(
|
|
3741
|
+
Object.keys(_seedcord_logger).forEach(function (k) {
|
|
4021
3742
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
4022
3743
|
enumerable: true,
|
|
4023
|
-
get: function () { return
|
|
3744
|
+
get: function () { return _seedcord_logger[k]; }
|
|
4024
3745
|
});
|
|
4025
3746
|
});
|
|
4026
3747
|
|
|
4027
|
-
Object.keys(
|
|
3748
|
+
Object.keys(_seedcord_rate_limiter).forEach(function (k) {
|
|
4028
3749
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
4029
3750
|
enumerable: true,
|
|
4030
|
-
get: function () { return
|
|
3751
|
+
get: function () { return _seedcord_rate_limiter[k]; }
|
|
4031
3752
|
});
|
|
4032
3753
|
});
|
|
4033
3754
|
|