@seedcord/gateway 0.1.0-next.2 → 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 +803 -948
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +365 -642
- package/dist/index.mjs +803 -940
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -12
package/dist/index.cjs
CHANGED
|
@@ -28,28 +28,30 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
//#endregion
|
|
29
29
|
require("reflect-metadata");
|
|
30
30
|
let _seedcord_logger_node = require("@seedcord/logger/node");
|
|
31
|
-
let _seedcord_core = require("@seedcord/core");
|
|
32
31
|
let _seedcord_core_internal = require("@seedcord/core/internal");
|
|
33
|
-
let discord_js = require("discord.js");
|
|
34
32
|
let _seedcord_errors = require("@seedcord/errors");
|
|
35
33
|
let _seedcord_errors_internal = require("@seedcord/errors/internal");
|
|
36
|
-
let
|
|
34
|
+
let _seedcord_core = require("@seedcord/core");
|
|
37
35
|
let _seedcord_logger = require("@seedcord/logger");
|
|
38
36
|
let _discordjs_builders = require("@discordjs/builders");
|
|
39
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);
|
|
40
41
|
let _seedcord_event_emitter = require("@seedcord/event-emitter");
|
|
41
|
-
let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
|
|
42
|
-
let _seedcord_services = require("@seedcord/services");
|
|
43
|
-
let _seedcord_types_internal = require("@seedcord/types/internal");
|
|
44
|
-
let envapt = require("envapt");
|
|
45
42
|
let chalk = require("chalk");
|
|
46
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");
|
|
49
|
+
let _seedcord_rate_limiter = require("@seedcord/rate-limiter");
|
|
50
|
+
let _seedcord_types_internal = require("@seedcord/types/internal");
|
|
47
51
|
let envapt_legacy = require("envapt/legacy");
|
|
48
52
|
let node_path = require("node:path");
|
|
49
53
|
let _seedcord_core_hmr = require("@seedcord/core/hmr");
|
|
50
54
|
let _seedcord_utils_node = require("@seedcord/utils/node");
|
|
51
|
-
let node_crypto = require("node:crypto");
|
|
52
|
-
node_crypto = __toESM(node_crypto, 1);
|
|
53
55
|
|
|
54
56
|
//#region src/logger-bootstrap.ts
|
|
55
57
|
(0, _seedcord_logger_node.installNodeDefaults)();
|
|
@@ -147,190 +149,6 @@ function Gated(...gates) {
|
|
|
147
149
|
};
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
//#endregion
|
|
151
|
-
//#region src/miscellaneous/areRoutes.ts
|
|
152
|
-
/**
|
|
153
|
-
* Checks if the provided value is an array of strings.
|
|
154
|
-
*
|
|
155
|
-
* @internal
|
|
156
|
-
*/
|
|
157
|
-
function areRoutes(routes) {
|
|
158
|
-
return Array.isArray(routes) && routes.every((r) => typeof r === "string");
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
//#endregion
|
|
162
|
-
//#region src/bot/decorators/Interactions.ts
|
|
163
|
-
/**
|
|
164
|
-
* Types of select menus supported for routing
|
|
165
|
-
*/
|
|
166
|
-
let SelectMenuKind = /* @__PURE__ */ function(SelectMenuKind) {
|
|
167
|
-
SelectMenuKind["String"] = "string";
|
|
168
|
-
SelectMenuKind["User"] = "user";
|
|
169
|
-
SelectMenuKind["Role"] = "role";
|
|
170
|
-
SelectMenuKind["Channel"] = "channel";
|
|
171
|
-
SelectMenuKind["Mentionable"] = "mentionable";
|
|
172
|
-
return SelectMenuKind;
|
|
173
|
-
}({});
|
|
174
|
-
/**
|
|
175
|
-
* Routes one or more slash commands to a {@link SlashHandler}.
|
|
176
|
-
*
|
|
177
|
-
* Pass the same route string(s) as the handler's generic. A single command reads `this.options`, several
|
|
178
|
-
* commands branch with `this.match`. The decorator routes and the generic must match exactly, so listing
|
|
179
|
-
* fewer or more than the handler declares is a compile error. Subcommands use a slash path.
|
|
180
|
-
*
|
|
181
|
-
* @param routes - The route string(s) this handler serves, e.g. `'ban'`, `'ban', 'kick'`, or `'demo/setup'`.
|
|
182
|
-
* @decorator
|
|
183
|
-
* @example
|
|
184
|
-
* ```ts
|
|
185
|
-
* \@SlashRoute('ban')
|
|
186
|
-
* class BanHandler extends SlashHandler<'ban'> {
|
|
187
|
-
* async execute() {
|
|
188
|
-
* const target = this.options.getUser('target');
|
|
189
|
-
* }
|
|
190
|
-
* }
|
|
191
|
-
* ```
|
|
192
|
-
*
|
|
193
|
-
* @example
|
|
194
|
-
* ```ts
|
|
195
|
-
* \@SlashRoute('ban', 'kick')
|
|
196
|
-
* class ModerationHandler extends SlashHandler<'ban' | 'kick'> {
|
|
197
|
-
* async execute() {
|
|
198
|
-
* await this.match({ ban: (o) => o.getUser('target'), kick: (o) => o.getUser('member') });
|
|
199
|
-
* }
|
|
200
|
-
* }
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
function SlashRoute(...routes) {
|
|
204
|
-
return function(constructor) {
|
|
205
|
-
storeMetadata(_seedcord_core_internal.InteractionRoutes.Slash, routes, constructor);
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Routes button interactions to handler classes.
|
|
210
|
-
*
|
|
211
|
-
* Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
|
|
212
|
-
* generic. Passing different definitions to the decorator and the generic is a compile error. Routing
|
|
213
|
-
* matches the stable prefix, so a wire minted from an older shape still reaches the handler, where
|
|
214
|
-
* reading this.params throws StaleCustomId and the controller boundary turns it into a reply.
|
|
215
|
-
*
|
|
216
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
217
|
-
* @decorator
|
|
218
|
-
*/
|
|
219
|
-
function ButtonRoute(...defs) {
|
|
220
|
-
return function(constructor) {
|
|
221
|
-
storeComponentRoute(_seedcord_core_internal.InteractionRoutes.Button, defs, constructor);
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Routes modal submissions to handler classes.
|
|
226
|
-
*
|
|
227
|
-
* Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
|
|
228
|
-
* generic. Passing different definitions to the decorator and the generic is a compile error. Routing
|
|
229
|
-
* matches the stable prefix, so an older-shape wire still reaches the handler and throws StaleCustomId
|
|
230
|
-
* on read, the same as a button route.
|
|
231
|
-
*
|
|
232
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
233
|
-
* @decorator
|
|
234
|
-
*/
|
|
235
|
-
function ModalRoute(...defs) {
|
|
236
|
-
return function(constructor) {
|
|
237
|
-
storeComponentRoute(_seedcord_core_internal.InteractionRoutes.Modal, defs, constructor);
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Routes one or more context-menu commands to a {@link ContextMenuHandler}.
|
|
242
|
-
*
|
|
243
|
-
* Pass the kind (`ApplicationCommandType.User` or `ApplicationCommandType.Message`) and the command name(s),
|
|
244
|
-
* each checked against that kind's registry, so a typo is a compile error. The same kind must sit on the
|
|
245
|
-
* handler's generic, cross-checked both directions. Context menus carry no options, so a multi-name handler
|
|
246
|
-
* reads `this.target` uniformly with no branch.
|
|
247
|
-
*
|
|
248
|
-
* @param kind - The context-menu kind, `ApplicationCommandType.User` or `ApplicationCommandType.Message`.
|
|
249
|
-
* @param names - The command name(s) this handler serves, keyed off the matching registry.
|
|
250
|
-
* @decorator
|
|
251
|
-
* @example
|
|
252
|
-
* ```ts
|
|
253
|
-
* \@ContextMenuRoute(ApplicationCommandType.User, 'View Profile')
|
|
254
|
-
* class ViewProfile extends ContextMenuHandler<ApplicationCommandType.User> {
|
|
255
|
-
* async execute() {
|
|
256
|
-
* const user = this.target;
|
|
257
|
-
* }
|
|
258
|
-
* }
|
|
259
|
-
* ```
|
|
260
|
-
*/
|
|
261
|
-
function ContextMenuRoute(kind, ...names) {
|
|
262
|
-
return function(constructor) {
|
|
263
|
-
storeMetadata(kind === discord_js.ApplicationCommandType.User ? _seedcord_core_internal.InteractionRoutes.UserContextMenu : _seedcord_core_internal.InteractionRoutes.MessageContextMenu, names, constructor);
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Routes one or more commands' autocomplete to an {@link AutocompleteHandler}.
|
|
268
|
-
*
|
|
269
|
-
* Pass the same command route string(s) as the handler's generic. One command branches with `this.match`
|
|
270
|
-
* over its autocompletable fields, several commands share one handler whose arms span every command's
|
|
271
|
-
* fields. The decorator routes and the generic must match exactly, so listing fewer or more than the
|
|
272
|
-
* handler declares is a compile error.
|
|
273
|
-
*
|
|
274
|
-
* @param routes - The command route string(s) this handler serves, e.g. `'search'` or `'search', 'find'`.
|
|
275
|
-
* @decorator
|
|
276
|
-
* @example
|
|
277
|
-
* ```ts
|
|
278
|
-
* \@AutocompleteRoute('search')
|
|
279
|
-
* class SearchAutocomplete extends AutocompleteHandler<'search'> {
|
|
280
|
-
* async execute() {
|
|
281
|
-
* await this.match({ query: (value, respond) => respond([{ name: value, value }]) });
|
|
282
|
-
* }
|
|
283
|
-
* }
|
|
284
|
-
* ```
|
|
285
|
-
*/
|
|
286
|
-
function AutocompleteRoute(...routes) {
|
|
287
|
-
return function(constructor) {
|
|
288
|
-
storeMetadata(_seedcord_core_internal.InteractionRoutes.Autocomplete, routes, constructor);
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Routes select menu interactions to handler classes.
|
|
293
|
-
*
|
|
294
|
-
* Pass the select kind and the {@link CustomId} definition(s) this handler decodes. The handler's
|
|
295
|
-
* generic must list the same definitions, and its second generic argument must be the matching select
|
|
296
|
-
* interaction type, or it is a compile error.
|
|
297
|
-
*
|
|
298
|
-
* @param type - Select menu kind from {@link SelectMenuKind}.
|
|
299
|
-
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
300
|
-
* @decorator
|
|
301
|
-
*
|
|
302
|
-
* @example
|
|
303
|
-
* ```typescript
|
|
304
|
-
* \@SelectMenuRoute(SelectMenuKind.User, AssignId)
|
|
305
|
-
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
306
|
-
* // handles user select menus minted from AssignId
|
|
307
|
-
* }
|
|
308
|
-
* ```
|
|
309
|
-
*/
|
|
310
|
-
function SelectMenuRoute(type, ...defs) {
|
|
311
|
-
return function(constructor) {
|
|
312
|
-
storeComponentRoute({
|
|
313
|
-
["string"]: _seedcord_core_internal.InteractionRoutes.StringMenu,
|
|
314
|
-
["user"]: _seedcord_core_internal.InteractionRoutes.UserMenu,
|
|
315
|
-
["role"]: _seedcord_core_internal.InteractionRoutes.RoleMenu,
|
|
316
|
-
["channel"]: _seedcord_core_internal.InteractionRoutes.ChannelMenu,
|
|
317
|
-
["mentionable"]: _seedcord_core_internal.InteractionRoutes.MentionableMenu
|
|
318
|
-
}[type], defs, constructor);
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
function storeComponentRoute(route, defs, constructor) {
|
|
322
|
-
storeMetadata(route, defs.map((def) => def.prefix), constructor);
|
|
323
|
-
Reflect.defineMetadata(_seedcord_core_internal.ComponentDefsKey, defs, constructor);
|
|
324
|
-
}
|
|
325
|
-
function storeMetadata(route, routes, constructor) {
|
|
326
|
-
const key = _seedcord_core_internal.InteractionRouteKeys[route];
|
|
327
|
-
const savedRoutes = Reflect.getMetadata(key, constructor);
|
|
328
|
-
const existing = areRoutes(savedRoutes) ? savedRoutes : [];
|
|
329
|
-
const toStore = Array.isArray(routes) ? routes : [routes];
|
|
330
|
-
Reflect.defineMetadata(key, [...existing, ...toStore], constructor);
|
|
331
|
-
Reflect.defineMetadata(_seedcord_core_internal.InteractionMetadataKey, true, constructor);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
152
|
//#endregion
|
|
335
153
|
//#region src/bot/decorators/Middlewares.ts
|
|
336
154
|
/**
|
|
@@ -399,21 +217,6 @@ const IgnoreBots = (0, _seedcord_core.defineGate)("IgnoreBots", (ctx) => {
|
|
|
399
217
|
if (ctx.user?.bot) throw new _seedcord_core.Silence("actor is a bot");
|
|
400
218
|
});
|
|
401
219
|
|
|
402
|
-
//#endregion
|
|
403
|
-
//#region src/bot/notices/utils.ts
|
|
404
|
-
function mentionFor(subject) {
|
|
405
|
-
if (subject instanceof discord_js.Role) return `<@&${subject.id}>`;
|
|
406
|
-
if (subject instanceof discord_js.TextChannel) return `<#${subject.id}>`;
|
|
407
|
-
if (subject instanceof discord_js.GuildMember) return `<@${subject.id}>`;
|
|
408
|
-
return `\`${subject.name}\``;
|
|
409
|
-
}
|
|
410
|
-
function labelFor(subject) {
|
|
411
|
-
if (subject instanceof discord_js.Role) return "role";
|
|
412
|
-
if (subject instanceof discord_js.TextChannel) return "channel";
|
|
413
|
-
if (subject instanceof discord_js.GuildMember) return "member";
|
|
414
|
-
return "guild";
|
|
415
|
-
}
|
|
416
|
-
|
|
417
220
|
//#endregion
|
|
418
221
|
//#region src/bot/notices/index.ts
|
|
419
222
|
var NotNsfw = class extends _seedcord_core_internal.GateNotice {
|
|
@@ -421,13 +224,6 @@ var NotNsfw = class extends _seedcord_core_internal.GateNotice {
|
|
|
421
224
|
super(message);
|
|
422
225
|
}
|
|
423
226
|
};
|
|
424
|
-
var MissingRole = class extends _seedcord_core_internal.GateNotice {
|
|
425
|
-
role;
|
|
426
|
-
constructor(message, role) {
|
|
427
|
-
super(message ?? (role ? `You need the ${role.name} role to use this.` : "You do not have the required role."));
|
|
428
|
-
this.role = role;
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
227
|
var RoleHigherThanMe = class extends _seedcord_core.Notice {
|
|
432
228
|
role;
|
|
433
229
|
botRole;
|
|
@@ -448,30 +244,19 @@ var CannotAssignBotRole = class extends _seedcord_core.Notice {
|
|
|
448
244
|
return { components: [new _seedcord_core_internal.NoticeCard("I cannot assign a managed role.").component] };
|
|
449
245
|
}
|
|
450
246
|
};
|
|
451
|
-
var MissingPermissions = class extends _seedcord_core.Notice {
|
|
452
|
-
where;
|
|
453
|
-
missingPerms;
|
|
454
|
-
constructor(message, where, missingPerms) {
|
|
455
|
-
super(message);
|
|
456
|
-
this.where = where;
|
|
457
|
-
this.missingPerms = missingPerms;
|
|
458
|
-
}
|
|
459
|
-
render() {
|
|
460
|
-
const bullets = this.missingPerms.map((perm) => `• ${perm}`).join("\n");
|
|
461
|
-
return { components: [new _seedcord_core_internal.NoticeCard(`The ${labelFor(this.where)} ${mentionFor(this.where)} is missing the following permission entries:\n\n${bullets}`).component] };
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
247
|
var HasDangerousPermissions = class extends _seedcord_core.Notice {
|
|
465
|
-
|
|
248
|
+
subject;
|
|
466
249
|
dangerousPerms;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
250
|
+
customLead;
|
|
251
|
+
constructor(message, subject, dangerousPerms) {
|
|
252
|
+
super(message ?? "A dangerous permission is enabled.");
|
|
253
|
+
this.subject = subject;
|
|
470
254
|
this.dangerousPerms = dangerousPerms;
|
|
255
|
+
this.customLead = message;
|
|
471
256
|
}
|
|
472
257
|
render() {
|
|
473
258
|
const bullets = this.dangerousPerms.map((perm) => `• ${perm}`).join("\n");
|
|
474
|
-
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] };
|
|
475
260
|
}
|
|
476
261
|
};
|
|
477
262
|
var UserNotFound = class extends _seedcord_core.Notice {
|
|
@@ -547,129 +332,6 @@ function Nsfw(options) {
|
|
|
547
332
|
});
|
|
548
333
|
}
|
|
549
334
|
|
|
550
|
-
//#endregion
|
|
551
|
-
//#region src/bot/utilities/permissions/checkPermissions.ts
|
|
552
|
-
/**
|
|
553
|
-
* Map of permission bits to their prettified human readable names.
|
|
554
|
-
*/
|
|
555
|
-
const PermissionNames = new Map(Object.entries(discord_js.PermissionFlagsBits).map(([key, bit]) => [bit, (0, _seedcord_utils.prettify)(key)]));
|
|
556
|
-
function checkPermissions(a, b, c, d, e) {
|
|
557
|
-
const { for: pFor, in: pIn, scope, inverse = false, missing: missingCtor, dangerous: dangerousCtor } = a instanceof discord_js.Role || a instanceof discord_js.GuildMember ? {
|
|
558
|
-
for: a,
|
|
559
|
-
in: b,
|
|
560
|
-
scope: c,
|
|
561
|
-
inverse: d ?? false,
|
|
562
|
-
...e
|
|
563
|
-
} : a;
|
|
564
|
-
const Missing = missingCtor ?? MissingPermissions;
|
|
565
|
-
const Dangerous = dangerousCtor ?? HasDangerousPermissions;
|
|
566
|
-
const perms = pIn instanceof discord_js.Guild ? pFor.permissions : pIn.permissionsFor(pFor, true);
|
|
567
|
-
const names = (bits) => bits.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
568
|
-
const present = scope.filter((bit) => perms.has(bit, true));
|
|
569
|
-
const presentNames = names(present);
|
|
570
|
-
if (inverse) {
|
|
571
|
-
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);
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
const missingBits = scope.filter((bit) => !perms.has(bit, true));
|
|
575
|
-
if (missingBits.length > 0) throw new Missing("Missing Any/All/No Permissions", pFor, names(missingBits));
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
//#endregion
|
|
579
|
-
//#region src/bot/gates/catalog/permissions.ts
|
|
580
|
-
const UNCACHED_MEMBER = "Your server member data could not be resolved. Try again.";
|
|
581
|
-
/**
|
|
582
|
-
* Requires the caller to hold every permission in `scope`, via `checkPermissions`. Refuses outside a guild.
|
|
583
|
-
*
|
|
584
|
-
* Interaction-only and excludes ModalSubmit, since a modal lacks a reliable cached caller member.
|
|
585
|
-
*
|
|
586
|
-
* @param scope - The permission flag bits the caller must all hold.
|
|
587
|
-
* @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-permissions one with `missing`.
|
|
588
|
-
*
|
|
589
|
-
* @see {@link Gated}
|
|
590
|
-
*
|
|
591
|
-
* @example
|
|
592
|
-
* ```ts
|
|
593
|
-
* import { PermissionFlagsBits } from 'discord.js';
|
|
594
|
-
*
|
|
595
|
-
* \@Gated(RequirePermissions([PermissionFlagsBits.BanMembers]))
|
|
596
|
-
* \@SlashRoute('ban')
|
|
597
|
-
* class BanHandler extends SlashHandler<'ban'> {
|
|
598
|
-
* async execute() {
|
|
599
|
-
* const target = this.options.getUser('target');
|
|
600
|
-
* }
|
|
601
|
-
* }
|
|
602
|
-
* ```
|
|
603
|
-
*/
|
|
604
|
-
function RequirePermissions(scope, options) {
|
|
605
|
-
return (0, _seedcord_core.defineGate)("RequirePermissions", (ctx) => {
|
|
606
|
-
if (!ctx.guild) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message));
|
|
607
|
-
if (!ctx.member) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message ?? UNCACHED_MEMBER));
|
|
608
|
-
checkPermissions(ctx.member, ctx.guild, scope, false, options?.missing ? { missing: options.missing } : void 0);
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Requires the bot to hold every permission in `scope`, via `checkPermissions`. Refuses outside a guild.
|
|
613
|
-
*
|
|
614
|
-
* It reads the bot's own member, so it attaches to modal handlers as well.
|
|
615
|
-
*
|
|
616
|
-
* @param scope - The permission flag bits the bot must all hold.
|
|
617
|
-
* @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-permissions one with `missing`.
|
|
618
|
-
*
|
|
619
|
-
* @see {@link Gated}
|
|
620
|
-
*
|
|
621
|
-
* @example
|
|
622
|
-
* ```ts
|
|
623
|
-
* import { PermissionFlagsBits } from 'discord.js';
|
|
624
|
-
*
|
|
625
|
-
* \@Gated(RequireBotPermissions([PermissionFlagsBits.BanMembers]))
|
|
626
|
-
* \@ModalRoute(MyModalId)
|
|
627
|
-
* class MyModalHandler extends ModalHandler<[typeof MyModalId]> {
|
|
628
|
-
* async execute() {
|
|
629
|
-
* // ...
|
|
630
|
-
* }
|
|
631
|
-
* }
|
|
632
|
-
* ```
|
|
633
|
-
*/
|
|
634
|
-
function RequireBotPermissions(scope, options) {
|
|
635
|
-
return (0, _seedcord_core.defineGate)("RequireBotPermissions", (ctx) => {
|
|
636
|
-
const botMember = ctx.guild?.members.me;
|
|
637
|
-
if (!ctx.guild || !botMember) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message));
|
|
638
|
-
checkPermissions(botMember, ctx.guild, scope, false, options?.missing ? { missing: options.missing } : void 0);
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Requires the caller to have `roleId`, read from the member's role cache. Refuses outside a guild first, then
|
|
643
|
-
* when the role is missing.
|
|
644
|
-
*
|
|
645
|
-
* Interaction-only and excludes ModalSubmit.
|
|
646
|
-
*
|
|
647
|
-
* @param roleId - The role snowflake the caller must hold, read from the member's role cache.
|
|
648
|
-
* @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-role one with `missingRole`.
|
|
649
|
-
*
|
|
650
|
-
* @see {@link Gated}
|
|
651
|
-
*
|
|
652
|
-
* @example
|
|
653
|
-
* ```ts
|
|
654
|
-
* \@Gated(RequireRole('123456789012345678'))
|
|
655
|
-
* \@SlashRoute('vip')
|
|
656
|
-
* class VipHandler extends SlashHandler<'vip'> {
|
|
657
|
-
* async execute() {
|
|
658
|
-
* // ...
|
|
659
|
-
* }
|
|
660
|
-
* }
|
|
661
|
-
* ```
|
|
662
|
-
*/
|
|
663
|
-
function RequireRole(roleId, options) {
|
|
664
|
-
return (0, _seedcord_core.defineGate)("RequireRole", (ctx) => {
|
|
665
|
-
if (!ctx.guild) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message));
|
|
666
|
-
if (!ctx.member) throw (0, _seedcord_core_internal.pickNotice)(options?.notInGuild, (message) => new _seedcord_core_internal.NotInGuild(message ?? UNCACHED_MEMBER));
|
|
667
|
-
if (ctx.member.roles.cache.has(roleId)) return;
|
|
668
|
-
const role = ctx.guild.roles.cache.get(roleId) ?? null;
|
|
669
|
-
throw (0, _seedcord_core_internal.pickNotice)(options?.missingRole, (message) => new MissingRole(message, role));
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
|
|
673
335
|
//#endregion
|
|
674
336
|
//#region src/bot/injectors/EmojiInjector.ts
|
|
675
337
|
const emojiStorage = {};
|
|
@@ -858,6 +520,37 @@ async function fetchText(client, channelId) {
|
|
|
858
520
|
throw new CouldNotFindChannel("Channel not found or not a text channel", channelId);
|
|
859
521
|
}
|
|
860
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
|
+
|
|
861
554
|
//#endregion
|
|
862
555
|
//#region src/bot/utilities/permissions/checkBotPermissions.ts
|
|
863
556
|
/**
|
|
@@ -885,8 +578,8 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
885
578
|
if (target instanceof discord_js.Guild) {
|
|
886
579
|
const me = target.members.me;
|
|
887
580
|
if (!me) {
|
|
888
|
-
const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
889
|
-
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);
|
|
890
583
|
}
|
|
891
584
|
checkPermissions(me, target, scope, inverse, errors);
|
|
892
585
|
return;
|
|
@@ -894,8 +587,8 @@ function checkBotPermissions(target, scope, inverse = false, errors) {
|
|
|
894
587
|
const me = target.guild.members.me;
|
|
895
588
|
if (!me) {
|
|
896
589
|
new _seedcord_logger.Logger("checkBotPermissions").warn(`Bot member is unavailable in guild ${target.guild.id} while checking permissions in channel ${target.id}`);
|
|
897
|
-
const names = scope.map((bit) => PermissionNames.get(bit) ?? String(bit));
|
|
898
|
-
throw new ((errors?.
|
|
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);
|
|
899
592
|
}
|
|
900
593
|
checkPermissions(me, target, scope, inverse, errors);
|
|
901
594
|
}
|
|
@@ -932,26 +625,26 @@ function getBotRole(guild) {
|
|
|
932
625
|
* // Check with custom error handling
|
|
933
626
|
* hasPermsToAssign({
|
|
934
627
|
* targetRole: role,
|
|
935
|
-
*
|
|
936
|
-
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
628
|
+
* noticeOverrides: {
|
|
629
|
+
* higherNotice: CustomHigherNotice,
|
|
630
|
+
* managedNotice: CustomManagedNotice,
|
|
631
|
+
* missingNotice: CustomMissingPermissionsNotice
|
|
939
632
|
* }
|
|
940
633
|
* });
|
|
941
634
|
* ```
|
|
942
635
|
*/
|
|
943
636
|
function hasPermsToAssign(roleOrOptions) {
|
|
944
|
-
const { targetRole, errors } = roleOrOptions instanceof discord_js.Role ? {
|
|
637
|
+
const { targetRole, noticeOverrides: errors } = roleOrOptions instanceof discord_js.Role ? {
|
|
945
638
|
targetRole: roleOrOptions,
|
|
946
|
-
|
|
639
|
+
noticeOverrides: void 0
|
|
947
640
|
} : roleOrOptions;
|
|
948
|
-
const
|
|
949
|
-
const
|
|
950
|
-
const
|
|
641
|
+
const HigherNotice = errors?.higherNotice ?? RoleHigherThanMe;
|
|
642
|
+
const ManagedNotice = errors?.managedNotice ?? CannotAssignBotRole;
|
|
643
|
+
const MissingNotice = errors?.missingNotice ?? _seedcord_core_internal.MissingPermissions;
|
|
951
644
|
const botRole = getBotRole(targetRole.guild);
|
|
952
|
-
if (targetRole.comparePositionTo(botRole) >= 0) throw new
|
|
953
|
-
if (targetRole.managed) throw new
|
|
954
|
-
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 });
|
|
955
648
|
}
|
|
956
649
|
|
|
957
650
|
//#endregion
|
|
@@ -1079,113 +772,308 @@ async function updateMemberRoles(rolesToAdd, rolesToRemove, member) {
|
|
|
1079
772
|
}
|
|
1080
773
|
|
|
1081
774
|
//#endregion
|
|
1082
|
-
//#region src/bot/
|
|
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
|
+
}
|
|
1083
782
|
/**
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1086
|
-
* the original throw, and {@link ReplySender} drops them on its own send.
|
|
1087
|
-
*
|
|
1088
|
-
* @internal
|
|
783
|
+
* Writes interaction responses through the discord.js interaction. Construction is internal to the repliable
|
|
784
|
+
* handler bases and the dispatcher.
|
|
1089
785
|
*/
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
786
|
+
var ReplySender = class extends _seedcord_core_internal.BaseReplySender {
|
|
787
|
+
interaction;
|
|
788
|
+
constructor(interaction, routeId) {
|
|
789
|
+
super(routeId, seedState(interaction));
|
|
790
|
+
this.interaction = interaction;
|
|
791
|
+
}
|
|
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);
|
|
798
|
+
}
|
|
799
|
+
async writeDefer(opts) {
|
|
800
|
+
await this.interaction.deferReply({ flags: (0, _seedcord_core_internal.deferFlags)(opts) });
|
|
801
|
+
}
|
|
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();
|
|
823
|
+
}
|
|
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);
|
|
851
|
+
return {
|
|
852
|
+
components: reply.components,
|
|
853
|
+
flags,
|
|
854
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
855
|
+
...reply.files && { files: [...reply.files] }
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
editOptions(response) {
|
|
859
|
+
const reply = this.serialize(response);
|
|
860
|
+
return {
|
|
861
|
+
components: reply.components,
|
|
862
|
+
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
863
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
864
|
+
...reply.files && { files: [...reply.files] }
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
updateOptions(response) {
|
|
868
|
+
const reply = this.serialize(response);
|
|
869
|
+
return {
|
|
870
|
+
components: reply.components,
|
|
871
|
+
flags: discord_js.MessageFlags.IsComponentsV2,
|
|
872
|
+
...reply.allowedMentions && { allowedMentions: reply.allowedMentions },
|
|
873
|
+
...reply.files && { files: [...reply.files] }
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
createdMessage(result) {
|
|
877
|
+
return result.resource?.message ?? void 0;
|
|
878
|
+
}
|
|
879
|
+
};
|
|
1095
880
|
|
|
1096
881
|
//#endregion
|
|
1097
|
-
//#region src/miscellaneous/
|
|
882
|
+
//#region src/bot/utilities/miscellaneous/slashRouteOf.ts
|
|
1098
883
|
/**
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
* @param ephemeral - Whether the reply is ephemeral.
|
|
1103
|
-
* @returns The OR'd `MessageFlags` value as a plain number.
|
|
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.
|
|
1104
887
|
*
|
|
1105
888
|
* @internal
|
|
1106
889
|
*/
|
|
1107
|
-
function
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
return
|
|
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);
|
|
1111
894
|
}
|
|
1112
895
|
|
|
1113
896
|
//#endregion
|
|
1114
|
-
//#region src/
|
|
897
|
+
//#region src/miscellaneous/FaultThrottle.ts
|
|
1115
898
|
/**
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1118
|
-
*
|
|
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
|
|
1119
904
|
*/
|
|
1120
|
-
var
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
* Sends the reply and returns the sent message so a caller can attach a collector to it. Never throws,
|
|
1128
|
-
* a failed send is logged and dropped and resolves to `undefined`.
|
|
1129
|
-
*
|
|
1130
|
-
* @param response - The ComponentsV2 reply to show.
|
|
1131
|
-
* @param ephemeral - Whether the reply is ephemeral. {@default `true`}
|
|
1132
|
-
* @returns The sent {@link Message}, or `undefined` when the send was swallowed.
|
|
1133
|
-
*/
|
|
1134
|
-
async send(response, ephemeral = true) {
|
|
1135
|
-
try {
|
|
1136
|
-
return await this.dispatch(response, ephemeral);
|
|
1137
|
-
} catch (error) {
|
|
1138
|
-
this.logSwallowed("send", error);
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
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;
|
|
1141
912
|
}
|
|
1142
|
-
/**
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
async edit(message, response) {
|
|
1150
|
-
try {
|
|
1151
|
-
await this.interaction.webhook.editMessage(message, this.editBody(response));
|
|
1152
|
-
} catch (error) {
|
|
1153
|
-
this.logSwallowed("edit", error);
|
|
1154
|
-
}
|
|
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;
|
|
1155
920
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
if (this.interaction.deferred) {
|
|
1159
|
-
if (this.interaction.ephemeral === null) return await this.interaction.followUp(this.replyOptions(response, ephemeral));
|
|
1160
|
-
return await this.interaction.editReply(this.editBody(response));
|
|
1161
|
-
}
|
|
1162
|
-
await this.interaction.reply(this.replyOptions(response, ephemeral));
|
|
1163
|
-
return await this.interaction.fetchReply();
|
|
921
|
+
markReported(key) {
|
|
922
|
+
this.lastReportedAt.set(key, this.now());
|
|
1164
923
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
components: response.components,
|
|
1168
|
-
flags: flagsFor(ephemeral),
|
|
1169
|
-
...response.allowedMentions && { allowedMentions: response.allowedMentions },
|
|
1170
|
-
...response.files && { files: response.files }
|
|
1171
|
-
};
|
|
924
|
+
clear() {
|
|
925
|
+
this.lastReportedAt.clear();
|
|
1172
926
|
}
|
|
1173
|
-
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region src/miscellaneous/extractErrorResponse.ts
|
|
931
|
+
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling");
|
|
932
|
+
const faultThrottle = new FaultThrottle();
|
|
933
|
+
/**
|
|
934
|
+
* Processes an error into a user-facing reply and routes its report.
|
|
935
|
+
*
|
|
936
|
+
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
937
|
+
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
938
|
+
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
939
|
+
* threaded into both the reply and the bus payload.
|
|
940
|
+
*/
|
|
941
|
+
function extractErrorResponse(error, core, origin) {
|
|
942
|
+
const uuid = node_crypto.randomUUID();
|
|
943
|
+
const developerUsername = core.config.notifications?.developerUsername;
|
|
944
|
+
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
945
|
+
uuid,
|
|
946
|
+
developerUsername
|
|
947
|
+
};
|
|
948
|
+
if (error instanceof _seedcord_core.Notice) {
|
|
949
|
+
if (error.report) reportFault(error, core, origin, uuid);
|
|
1174
950
|
return {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
...response.allowedMentions && { allowedMentions: response.allowedMentions },
|
|
1178
|
-
...response.files && { files: response.files }
|
|
951
|
+
uuid,
|
|
952
|
+
response: error.render(ctx)
|
|
1179
953
|
};
|
|
1180
954
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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;
|
|
1187
967
|
}
|
|
1188
|
-
|
|
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
|
+
}
|
|
1189
1077
|
|
|
1190
1078
|
//#endregion
|
|
1191
1079
|
//#region src/bot/confirm/reserved.ts
|
|
@@ -1231,9 +1119,9 @@ async function collectChoice(message, userId, timeoutMs) {
|
|
|
1231
1119
|
return null;
|
|
1232
1120
|
}
|
|
1233
1121
|
}
|
|
1234
|
-
async function settle(sender,
|
|
1122
|
+
async function settle(sender, message, outcome) {
|
|
1235
1123
|
if (outcome === void 0) {
|
|
1236
|
-
await
|
|
1124
|
+
await sender.delete(message).catch(() => void 0);
|
|
1237
1125
|
return;
|
|
1238
1126
|
}
|
|
1239
1127
|
await sender.edit(message, typeof outcome === "function" ? await outcome() : outcome);
|
|
@@ -1241,17 +1129,16 @@ async function settle(sender, interaction, message, outcome) {
|
|
|
1241
1129
|
async function getConfirmation(interaction, prompt, options) {
|
|
1242
1130
|
const { ephemeral = true, timeoutMs = DEFAULT_TIMEOUT_MS } = options ?? {};
|
|
1243
1131
|
const response = typeof prompt === "string" ? defaultPrompt(prompt, options) : await prompt(CONFIRM_IDS);
|
|
1244
|
-
const sender = new ReplySender(interaction);
|
|
1245
|
-
const message = await sender.send(response, ephemeral);
|
|
1246
|
-
if (!message) return false;
|
|
1132
|
+
const sender = new ReplySender(interaction, interactionRoute(interaction));
|
|
1133
|
+
const message = await sender.send(response, { ephemeral });
|
|
1247
1134
|
const winner = await collectChoice(message, interaction.user.id, timeoutMs);
|
|
1248
1135
|
if (!winner) {
|
|
1249
|
-
await settle(sender,
|
|
1136
|
+
await settle(sender, message, options?.onTimeout);
|
|
1250
1137
|
return false;
|
|
1251
1138
|
}
|
|
1252
1139
|
await winner.deferUpdate().catch(() => void 0);
|
|
1253
1140
|
const confirmed = winner.customId === CONFIRM_IDS.confirm;
|
|
1254
|
-
await settle(sender,
|
|
1141
|
+
await settle(sender, message, confirmed ? options?.onConfirm : options?.onCancel);
|
|
1255
1142
|
return confirmed;
|
|
1256
1143
|
}
|
|
1257
1144
|
|
|
@@ -1364,19 +1251,10 @@ var Pluggable = class Pluggable extends _seedcord_event_emitter.TypedEventEmitte
|
|
|
1364
1251
|
//#endregion
|
|
1365
1252
|
//#region src/handlers/BaseHandler.ts
|
|
1366
1253
|
/**
|
|
1367
|
-
*
|
|
1254
|
+
* The gateway base, fixes the core base's `TCore` to the gateway {@link Core} and adds the `getEvent`
|
|
1255
|
+
* gate hook.
|
|
1368
1256
|
*/
|
|
1369
|
-
var BaseHandler = class {
|
|
1370
|
-
core;
|
|
1371
|
-
event;
|
|
1372
|
-
logger;
|
|
1373
|
-
dispatch;
|
|
1374
|
-
constructor(event, core, dispatch) {
|
|
1375
|
-
this.core = core;
|
|
1376
|
-
this.event = event;
|
|
1377
|
-
this.logger = new _seedcord_logger.Logger(this.constructor.name);
|
|
1378
|
-
this.dispatch = dispatch;
|
|
1379
|
-
}
|
|
1257
|
+
var BaseHandler = class extends _seedcord_core.BaseHandler {
|
|
1380
1258
|
/** @internal */
|
|
1381
1259
|
getEvent() {
|
|
1382
1260
|
return this.event;
|
|
@@ -1400,36 +1278,49 @@ var InteractionMiddleware = class extends BaseHandler {
|
|
|
1400
1278
|
};
|
|
1401
1279
|
|
|
1402
1280
|
//#endregion
|
|
1403
|
-
//#region src/handlers/
|
|
1281
|
+
//#region src/handlers/RepliableHandler.ts
|
|
1404
1282
|
/**
|
|
1405
|
-
* Shared base the
|
|
1283
|
+
* Shared base the repliable interaction handlers extend.
|
|
1406
1284
|
*
|
|
1407
|
-
* Not a public entry point.
|
|
1408
|
-
* or {@link
|
|
1409
|
-
*
|
|
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.
|
|
1410
1288
|
*
|
|
1411
|
-
* @typeParam
|
|
1289
|
+
* @typeParam Event - The repliable interaction type this handler processes
|
|
1412
1290
|
*/
|
|
1413
|
-
var
|
|
1414
|
-
|
|
1415
|
-
|
|
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);
|
|
1416
1298
|
}
|
|
1417
1299
|
};
|
|
1418
1300
|
|
|
1419
1301
|
//#endregion
|
|
1420
|
-
//#region src/
|
|
1302
|
+
//#region src/handlers/interaction/InteractionHandler.ts
|
|
1421
1303
|
/**
|
|
1422
|
-
*
|
|
1423
|
-
* `seedcord codegen` both assemble route keys through the same {@link buildSlashRoute}, so a dispatched
|
|
1424
|
-
* interaction and a generated registry key always match.
|
|
1304
|
+
* Shared base the typed interaction handlers extend.
|
|
1425
1305
|
*
|
|
1426
|
-
* @
|
|
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
|
|
1427
1311
|
*/
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
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
|
+
};
|
|
1433
1324
|
|
|
1434
1325
|
//#endregion
|
|
1435
1326
|
//#region src/handlers/interaction/SlashHandler.ts
|
|
@@ -1465,7 +1356,7 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
1465
1356
|
}
|
|
1466
1357
|
/**
|
|
1467
1358
|
* Run the arm for whichever command fired. Use this only when the handler is registered for several
|
|
1468
|
-
* routes.
|
|
1359
|
+
* routes.
|
|
1469
1360
|
*
|
|
1470
1361
|
* Provide one arm per registered route, keyed by its route string, and each arm receives that route's
|
|
1471
1362
|
* own narrowed options. The arms must cover every route in the generic, a missing route or an unknown
|
|
@@ -1489,7 +1380,7 @@ var SlashHandler = class extends InteractionHandler {
|
|
|
1489
1380
|
*/
|
|
1490
1381
|
async match(arms) {
|
|
1491
1382
|
const route = slashRouteOf(this.event);
|
|
1492
|
-
const arm = arms[route];
|
|
1383
|
+
const arm = Object.hasOwn(arms, route) ? arms[route] : void 0;
|
|
1493
1384
|
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.SlashMatchArmMissing, [route]);
|
|
1494
1385
|
return await arm(this.options);
|
|
1495
1386
|
}
|
|
@@ -1560,11 +1451,11 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1560
1451
|
constructor(event, core, dispatch) {
|
|
1561
1452
|
super(event, core, dispatch);
|
|
1562
1453
|
}
|
|
1454
|
+
decodedFocused;
|
|
1563
1455
|
/**
|
|
1564
1456
|
* The focused option for this interaction. `value` is always the raw partial string the user is typing,
|
|
1565
1457
|
* even for an integer or number option, so coerce it yourself when you need a number.
|
|
1566
1458
|
*/
|
|
1567
|
-
decodedFocused;
|
|
1568
1459
|
get focused() {
|
|
1569
1460
|
if (this.decodedFocused) return this.decodedFocused;
|
|
1570
1461
|
const raw = this.event.options.getFocused(true);
|
|
@@ -1580,7 +1471,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1580
1471
|
* match is how you read it. There is no single-field shortcut the way slash/component handlers have one.
|
|
1581
1472
|
*
|
|
1582
1473
|
* Provide one arm per autocompletable field across the registered commands, keyed by field name. Each
|
|
1583
|
-
* 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
|
|
1584
1475
|
* arms must cover every autocompletable field, a missing field or an unknown key is a compile error. A
|
|
1585
1476
|
* focused field with no arm, only reachable from a stale-deployed command, throws at runtime.
|
|
1586
1477
|
*
|
|
@@ -1602,11 +1493,15 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1602
1493
|
*/
|
|
1603
1494
|
async match(arms) {
|
|
1604
1495
|
const { name, value } = this.focused;
|
|
1605
|
-
const respond = (choices) => this.
|
|
1606
|
-
const arm = arms[name];
|
|
1496
|
+
const respond = (choices) => this.respond(choices);
|
|
1497
|
+
const arm = Object.hasOwn(arms, name) ? arms[name] : void 0;
|
|
1607
1498
|
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.AutocompleteMatchArmMissing, [name]);
|
|
1608
1499
|
return await arm(value, respond);
|
|
1609
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
|
+
}
|
|
1610
1505
|
/** The firing command route, for a field whose completion differs per registered command. */
|
|
1611
1506
|
get route() {
|
|
1612
1507
|
return slashRouteOf(this.event);
|
|
@@ -1614,7 +1509,7 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1614
1509
|
/**
|
|
1615
1510
|
* The already-entered options on this command, restricted to the kinds Discord resolves during autocomplete
|
|
1616
1511
|
* (string, integer, number, boolean) with every read nullable, since a sibling is partial while the user
|
|
1617
|
-
* types the focused field.
|
|
1512
|
+
* types the focused field. The focused field is on `this.focused`.
|
|
1618
1513
|
*/
|
|
1619
1514
|
get options() {
|
|
1620
1515
|
return this.event.options;
|
|
@@ -1626,25 +1521,25 @@ var AutocompleteHandler = class extends BaseHandler {
|
|
|
1626
1521
|
/**
|
|
1627
1522
|
* Shared base the customId-routed component handlers extend.
|
|
1628
1523
|
*
|
|
1629
|
-
* Not a public entry point.
|
|
1630
|
-
*
|
|
1631
|
-
* 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.
|
|
1632
1526
|
*
|
|
1633
1527
|
* @typeParam Event - The component interaction type this handler processes
|
|
1634
1528
|
* @typeParam Defs - The customId route definitions registered on the concrete handler
|
|
1635
1529
|
*/
|
|
1636
1530
|
var ComponentHandler = class extends InteractionHandler {
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
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();
|
|
1641
1538
|
}
|
|
1642
1539
|
decoded;
|
|
1643
1540
|
get route() {
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
this.decoded = decoded;
|
|
1647
|
-
return decoded;
|
|
1541
|
+
this.decoded ??= (0, _seedcord_core_internal.decodeComponentRoute)(this.constructor, this.event.customId);
|
|
1542
|
+
return this.decoded;
|
|
1648
1543
|
}
|
|
1649
1544
|
/**
|
|
1650
1545
|
* The decoded params of the single route this handler is registered for.
|
|
@@ -1663,10 +1558,11 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1663
1558
|
* handler `this.params` is `never`, so match is the only way to read the decoded params.
|
|
1664
1559
|
*
|
|
1665
1560
|
* Provide one arm per registered route, keyed by its prefix, and each arm receives that route's own
|
|
1666
|
-
* decoded params. The arms
|
|
1667
|
-
*
|
|
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.
|
|
1668
1564
|
*
|
|
1669
|
-
* @param arms - One
|
|
1565
|
+
* @param arms - One callback per registered route, keyed by prefix.
|
|
1670
1566
|
* @returns The result of the arm that ran.
|
|
1671
1567
|
*
|
|
1672
1568
|
* @example
|
|
@@ -1675,8 +1571,8 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1675
1571
|
* class ReviewButtons extends ButtonHandler<[typeof Approve, typeof Reject]> {
|
|
1676
1572
|
* async execute() {
|
|
1677
1573
|
* await this.match({
|
|
1678
|
-
* approve: ({ userId }) => this.
|
|
1679
|
-
* reject: ({ userId }) => this.
|
|
1574
|
+
* approve: ({ userId }) => this.reply(`approved <@${userId}>`),
|
|
1575
|
+
* reject: ({ userId }) => this.reply(`rejected <@${userId}>`)
|
|
1680
1576
|
* });
|
|
1681
1577
|
* }
|
|
1682
1578
|
* }
|
|
@@ -1684,7 +1580,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1684
1580
|
*/
|
|
1685
1581
|
async match(arms) {
|
|
1686
1582
|
const { prefix, params } = this.route;
|
|
1687
|
-
const arm = arms[prefix];
|
|
1583
|
+
const arm = Object.hasOwn(arms, prefix) ? arms[prefix] : void 0;
|
|
1688
1584
|
if (!arm) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CustomIdMatchArmMissing, [prefix]);
|
|
1689
1585
|
return await arm(params);
|
|
1690
1586
|
}
|
|
@@ -1696,8 +1592,9 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1696
1592
|
* Base class for a button interaction handler.
|
|
1697
1593
|
*
|
|
1698
1594
|
* Register the customId definitions this handler decodes with `@ButtonRoute`, list the same ones in the
|
|
1699
|
-
* generic, then read `this.params` for a single route or `this.match` for several.
|
|
1700
|
-
*
|
|
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.
|
|
1701
1598
|
*
|
|
1702
1599
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ApproveId]`.
|
|
1703
1600
|
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
@@ -1708,7 +1605,7 @@ var ComponentHandler = class extends InteractionHandler {
|
|
|
1708
1605
|
* class ApproveButton extends ButtonHandler<[typeof ApproveId]> {
|
|
1709
1606
|
* async execute() {
|
|
1710
1607
|
* const { userId } = this.params;
|
|
1711
|
-
* await this.
|
|
1608
|
+
* await this.reply(`approved <@${userId}>`);
|
|
1712
1609
|
* }
|
|
1713
1610
|
* }
|
|
1714
1611
|
* ```
|
|
@@ -1722,7 +1619,8 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1722
1619
|
*
|
|
1723
1620
|
* Register the customId definitions this handler decodes with `@ModalRoute`, list the same ones in the
|
|
1724
1621
|
* generic, then read `this.params` for a single route or `this.match` for several. Read the submitted
|
|
1725
|
-
* 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.
|
|
1726
1624
|
*
|
|
1727
1625
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof ConfigId]`.
|
|
1728
1626
|
* @typeParam Cache - The interaction cache state, `'cached'` by default.
|
|
@@ -1734,7 +1632,7 @@ var ButtonHandler = class extends ComponentHandler {};
|
|
|
1734
1632
|
* async execute() {
|
|
1735
1633
|
* const { guildId } = this.params;
|
|
1736
1634
|
* const name = this.event.fields.getTextInputValue('name');
|
|
1737
|
-
* await this.
|
|
1635
|
+
* await this.reply(`saved ${name} for ${guildId}`);
|
|
1738
1636
|
* }
|
|
1739
1637
|
* }
|
|
1740
1638
|
* ```
|
|
@@ -1748,7 +1646,8 @@ var ModalHandler = class extends ComponentHandler {};
|
|
|
1748
1646
|
*
|
|
1749
1647
|
* Pass the select kind first and the customId definitions second, the same order as `@SelectMenuRoute`,
|
|
1750
1648
|
* so `this.event` and `this.event.values` are narrowed to that kind. Read `this.params` for a single
|
|
1751
|
-
* 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`.
|
|
1752
1651
|
*
|
|
1753
1652
|
* @typeParam Kind - The select kind from {@link SelectMenuKind}, e.g. `SelectMenuKind.User`.
|
|
1754
1653
|
* @typeParam Defs - The customId definitions this handler decodes, e.g. `[typeof AssignId]`.
|
|
@@ -1760,7 +1659,7 @@ var ModalHandler = class extends ComponentHandler {};
|
|
|
1760
1659
|
* class AssignSelect extends SelectMenuHandler<SelectMenuKind.User, [typeof AssignId]> {
|
|
1761
1660
|
* async execute() {
|
|
1762
1661
|
* const { roleId } = this.params;
|
|
1763
|
-
* await this.
|
|
1662
|
+
* await this.reply(`assigning ${this.event.values.length} member(s) to <@&${roleId}>`);
|
|
1764
1663
|
* }
|
|
1765
1664
|
* }
|
|
1766
1665
|
* ```
|
|
@@ -2026,16 +1925,16 @@ var Paginator = class {
|
|
|
2026
1925
|
const loadPage = (ctx, n) => this.page(ctx, n);
|
|
2027
1926
|
this.Handler = class Nav extends ButtonHandler {
|
|
2028
1927
|
async execute() {
|
|
2029
|
-
await this.
|
|
1928
|
+
await this.deferUpdate();
|
|
2030
1929
|
const response = await loadPage(contextOf(this.event, this.core), this.params.page);
|
|
2031
|
-
await
|
|
1930
|
+
await this.update(response);
|
|
2032
1931
|
}
|
|
2033
1932
|
};
|
|
2034
1933
|
}
|
|
2035
1934
|
/** Render page 0 and send it, picking reply or followUp from the interaction's state. */
|
|
2036
1935
|
async start(interaction, core) {
|
|
2037
1936
|
const response = await this.page(contextOf(interaction, core), 0);
|
|
2038
|
-
return new ReplySender(interaction).send(response, this.config.ephemeral ?? false);
|
|
1937
|
+
return new ReplySender(interaction, interactionRoute(interaction)).send(response, { ephemeral: this.config.ephemeral ?? false });
|
|
2039
1938
|
}
|
|
2040
1939
|
/** Render a page as a {@link ReplyResponse}. To post it elsewhere, add `flags: MessageFlags.IsComponentsV2`. */
|
|
2041
1940
|
async page(ctx, n) {
|
|
@@ -2159,19 +2058,184 @@ function Subscribe(subscriber, options) {
|
|
|
2159
2058
|
};
|
|
2160
2059
|
}
|
|
2161
2060
|
|
|
2061
|
+
//#endregion
|
|
2062
|
+
//#region src/subscribers/decorators/WebhookUrl.ts
|
|
2063
|
+
/**
|
|
2064
|
+
* Declares the environment variable a {@link WebhookLog} reporter reads its webhook url from.
|
|
2065
|
+
*
|
|
2066
|
+
* An unset variable disables the reporter with a boot warning. A set but malformed value throws
|
|
2067
|
+
* at boot.
|
|
2068
|
+
*
|
|
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
|
+
* ```
|
|
2081
|
+
*/
|
|
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);
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
|
|
2131
|
+
//#endregion
|
|
2132
|
+
//#region src/subscribers/bases/WebhookSender.ts
|
|
2133
|
+
const HTTP_NOT_FOUND = 404;
|
|
2134
|
+
const HTTP_UNAUTHORIZED = 401;
|
|
2135
|
+
/**
|
|
2136
|
+
* Sends component messages to a Discord webhook through `@discordjs/rest`.
|
|
2137
|
+
*
|
|
2138
|
+
* @throws A `SeedcordError` when the url does not carry a webhook id and token.
|
|
2139
|
+
*/
|
|
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]);
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Fetches the webhook object, sending no message. Returns `missing` on a 404 or 401 from
|
|
2150
|
+
* Discord and `unreachable` on any other failure.
|
|
2151
|
+
*/
|
|
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";
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
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
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
|
|
2162
2186
|
//#endregion
|
|
2163
2187
|
//#region src/subscribers/bases/WebhookLog.ts
|
|
2164
2188
|
/**
|
|
2165
|
-
*
|
|
2189
|
+
* Base class for subscribers that deliver their event to a Discord webhook.
|
|
2166
2190
|
*
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
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.
|
|
2169
2194
|
*
|
|
2170
|
-
* @typeParam KeyOfSubscribers - The
|
|
2195
|
+
* @typeParam KeyOfSubscribers - The subscription key this reporter receives
|
|
2171
2196
|
*/
|
|
2172
|
-
var WebhookLog = class extends Subscriber {
|
|
2173
|
-
|
|
2174
|
-
|
|
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;
|
|
2204
|
+
}
|
|
2205
|
+
const { username, components, files } = await this.report();
|
|
2206
|
+
try {
|
|
2207
|
+
await WebhookLog.senderFor(url).send({
|
|
2208
|
+
flags: flagsFor(false),
|
|
2209
|
+
username: username ?? this.constructor.name,
|
|
2210
|
+
components,
|
|
2211
|
+
files
|
|
2212
|
+
});
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
this.logger.error("Failed to send webhook report", error);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
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);
|
|
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;
|
|
2175
2239
|
}
|
|
2176
2240
|
};
|
|
2177
2241
|
|
|
@@ -2402,246 +2466,79 @@ var CommandRegistry = class {
|
|
|
2402
2466
|
|
|
2403
2467
|
//#endregion
|
|
2404
2468
|
//#region src/miscellaneous/deriveEventActor.ts
|
|
2405
|
-
function deriveEventActor(args) {
|
|
2406
|
-
const tuple = Array.isArray(args) ? args : [args];
|
|
2407
|
-
for (const arg of tuple) {
|
|
2408
|
-
if (arg instanceof discord_js.Message) return {
|
|
2409
|
-
guild: arg.guild,
|
|
2410
|
-
member: arg.member,
|
|
2411
|
-
user: arg.author,
|
|
2412
|
-
channelId: arg.channelId
|
|
2413
|
-
};
|
|
2414
|
-
if (arg instanceof discord_js.GuildMember) return {
|
|
2415
|
-
guild: arg.guild,
|
|
2416
|
-
member: arg,
|
|
2417
|
-
user: arg.user,
|
|
2418
|
-
channelId: null
|
|
2419
|
-
};
|
|
2420
|
-
if (arg instanceof discord_js.User) return {
|
|
2421
|
-
guild: null,
|
|
2422
|
-
member: null,
|
|
2423
|
-
user: arg,
|
|
2424
|
-
channelId: null
|
|
2425
|
-
};
|
|
2426
|
-
}
|
|
2427
|
-
return {
|
|
2428
|
-
guild: null,
|
|
2429
|
-
member: null,
|
|
2430
|
-
user: null,
|
|
2431
|
-
channelId: null
|
|
2432
|
-
};
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
//#endregion
|
|
2436
|
-
//#region src/bot/gates/runGates.ts
|
|
2437
|
-
function interactionGateContext(interaction, core) {
|
|
2438
|
-
const rawMember = interaction.member;
|
|
2439
|
-
const memberRoleIds = rawMember instanceof discord_js.GuildMember ? [...rawMember.roles.cache.keys()].filter((id) => id !== interaction.guildId) : rawMember?.roles ?? [];
|
|
2440
|
-
return {
|
|
2441
|
-
kind: "interaction",
|
|
2442
|
-
interaction,
|
|
2443
|
-
core,
|
|
2444
|
-
user: interaction.user,
|
|
2445
|
-
guild: interaction.guild,
|
|
2446
|
-
member: rawMember instanceof discord_js.GuildMember ? rawMember : null,
|
|
2447
|
-
userId: interaction.user.id,
|
|
2448
|
-
guildId: interaction.guildId,
|
|
2449
|
-
channelId: interaction.channelId,
|
|
2450
|
-
memberRoleIds,
|
|
2451
|
-
memberPermissions: interaction.memberPermissions?.bitfield ?? null,
|
|
2452
|
-
routeId: null
|
|
2453
|
-
};
|
|
2454
|
-
}
|
|
2455
|
-
function eventGateContext(eventName, args, core) {
|
|
2456
|
-
const actor = deriveEventActor(args);
|
|
2457
|
-
return {
|
|
2458
|
-
kind: "event",
|
|
2459
|
-
core,
|
|
2460
|
-
eventName,
|
|
2461
|
-
payload: args,
|
|
2462
|
-
user: actor.user,
|
|
2463
|
-
guild: actor.guild,
|
|
2464
|
-
member: actor.member,
|
|
2465
|
-
userId: actor.user?.id ?? null,
|
|
2466
|
-
guildId: actor.guild?.id ?? null,
|
|
2467
|
-
channelId: actor.channelId,
|
|
2468
|
-
memberRoleIds: actor.member ? [...actor.member.roles.cache.keys()].filter((id) => id !== actor.guild?.id) : [],
|
|
2469
|
-
memberPermissions: actor.member?.permissions.bitfield ?? null,
|
|
2470
|
-
routeId: null
|
|
2471
|
-
};
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
//#endregion
|
|
2475
|
-
//#region src/miscellaneous/FaultThrottle.ts
|
|
2476
|
-
/**
|
|
2477
|
-
* Drops duplicate fault reports inside a fixed window so a recurring fault (a database outage on a
|
|
2478
|
-
* hot path) reports once per window instead of flooding the webhook. Keyed on a structural string, so
|
|
2479
|
-
* the map stays small, and a stale entry is evicted on the next read of its key.
|
|
2480
|
-
*
|
|
2481
|
-
* @internal
|
|
2482
|
-
*/
|
|
2483
|
-
var FaultThrottle = class {
|
|
2484
|
-
windowMs;
|
|
2485
|
-
now;
|
|
2486
|
-
lastReportedAt = /* @__PURE__ */ new Map();
|
|
2487
|
-
constructor(windowMs = 6e4, now = () => Date.now()) {
|
|
2488
|
-
this.windowMs = windowMs;
|
|
2489
|
-
this.now = now;
|
|
2490
|
-
}
|
|
2491
|
-
/** Evicts the entry for this key if it has aged out, so the map never holds stale keys forever. */
|
|
2492
|
-
shouldReport(key) {
|
|
2493
|
-
const last = this.lastReportedAt.get(key);
|
|
2494
|
-
if (last === void 0) return true;
|
|
2495
|
-
if (this.now() - last < this.windowMs) return false;
|
|
2496
|
-
this.lastReportedAt.delete(key);
|
|
2497
|
-
return true;
|
|
2498
|
-
}
|
|
2499
|
-
markReported(key) {
|
|
2500
|
-
this.lastReportedAt.set(key, this.now());
|
|
2501
|
-
}
|
|
2502
|
-
clear() {
|
|
2503
|
-
this.lastReportedAt.clear();
|
|
2504
|
-
}
|
|
2505
|
-
};
|
|
2506
|
-
|
|
2507
|
-
//#endregion
|
|
2508
|
-
//#region src/miscellaneous/extractErrorResponse.ts
|
|
2509
|
-
const logger$2 = new _seedcord_logger.Logger("ErrorsHandling");
|
|
2510
|
-
/**
|
|
2511
|
-
* The single throttle for every fault publish, so a recurring fault reports once per window across both
|
|
2512
|
-
* the interaction and event paths. Exported so tests reset it between cases.
|
|
2513
|
-
*
|
|
2514
|
-
* @internal
|
|
2515
|
-
*/
|
|
2516
|
-
const faultThrottle = new FaultThrottle();
|
|
2517
|
-
/**
|
|
2518
|
-
* Processes an error into a user-facing reply and routes its report.
|
|
2519
|
-
*
|
|
2520
|
-
* A {@link Notice} always renders its own reply. A reporting denial (`report: true`) additionally
|
|
2521
|
-
* publishes `handledException` on the interaction path. A raw, non-denial throw publishes
|
|
2522
|
-
* `unknownException` and renders the configured `defaultError` (or a generic {@link Fault}). One uuid is
|
|
2523
|
-
* threaded into both the reply and the bus payload.
|
|
2524
|
-
*
|
|
2525
|
-
* @internal
|
|
2526
|
-
*/
|
|
2527
|
-
function extractErrorResponse(error, core, origin) {
|
|
2528
|
-
const uuid = node_crypto.randomUUID();
|
|
2529
|
-
const developerUsername = core.config.notifications?.developerUsername;
|
|
2530
|
-
const ctx = developerUsername === void 0 ? { uuid } : {
|
|
2531
|
-
uuid,
|
|
2532
|
-
developerUsername
|
|
2533
|
-
};
|
|
2534
|
-
if (error instanceof _seedcord_core.Notice) {
|
|
2535
|
-
if (error.report) reportFault(error, core, origin, uuid);
|
|
2536
|
-
return {
|
|
2537
|
-
uuid,
|
|
2538
|
-
response: error.render(ctx)
|
|
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
|
|
2539
2489
|
};
|
|
2540
2490
|
}
|
|
2541
|
-
reportRawFault(error, core, origin, uuid);
|
|
2542
|
-
const override = core.config.errors?.defaultError;
|
|
2543
|
-
return {
|
|
2544
|
-
uuid,
|
|
2545
|
-
response: override ? new override(uuid).render(ctx) : new _seedcord_core.Fault().render(ctx)
|
|
2546
|
-
};
|
|
2547
|
-
}
|
|
2548
|
-
function withThrottle(origin, error, publish) {
|
|
2549
|
-
const key = faultKey(origin, error);
|
|
2550
|
-
if (!faultThrottle.shouldReport(key)) {
|
|
2551
|
-
logger$2.info(`throttled duplicate fault ${key}`);
|
|
2552
|
-
return;
|
|
2553
|
-
}
|
|
2554
|
-
publish();
|
|
2555
|
-
faultThrottle.markReported(key);
|
|
2556
|
-
}
|
|
2557
|
-
function reportFault(denial, core, origin, uuid) {
|
|
2558
|
-
withThrottle(origin, denial, () => {
|
|
2559
|
-
logger$2.error(`${denial.name}: ${uuid}`, denial);
|
|
2560
|
-
if (origin.interaction) core.bus.publish("handledException", {
|
|
2561
|
-
denial,
|
|
2562
|
-
uuid,
|
|
2563
|
-
source: buildInteractionSource(origin.interaction)
|
|
2564
|
-
});
|
|
2565
|
-
else if (origin.event) core.bus.publish("handledException", {
|
|
2566
|
-
denial,
|
|
2567
|
-
uuid,
|
|
2568
|
-
source: buildEventSource(origin.event, origin)
|
|
2569
|
-
});
|
|
2570
|
-
else core.bus.publish("unknownException", {
|
|
2571
|
-
uuid,
|
|
2572
|
-
error: denial,
|
|
2573
|
-
guild: origin.guild,
|
|
2574
|
-
user: origin.user,
|
|
2575
|
-
metadata: metadataFor(origin)
|
|
2576
|
-
});
|
|
2577
|
-
});
|
|
2578
|
-
}
|
|
2579
|
-
function reportRawFault(error, core, origin, uuid) {
|
|
2580
|
-
withThrottle(origin, error, () => {
|
|
2581
|
-
if (core.config.errors?.errorStack ?? false) logger$2.error(uuid, error);
|
|
2582
|
-
else logger$2.error(`${uuid} | ${error.message}`);
|
|
2583
|
-
core.bus.publish("unknownException", {
|
|
2584
|
-
uuid,
|
|
2585
|
-
error,
|
|
2586
|
-
guild: origin.guild,
|
|
2587
|
-
user: origin.user,
|
|
2588
|
-
metadata: metadataFor(origin)
|
|
2589
|
-
});
|
|
2590
|
-
});
|
|
2591
|
-
}
|
|
2592
|
-
function metadataFor(origin) {
|
|
2593
|
-
if (origin.event) return {
|
|
2594
|
-
eventName: origin.event.name,
|
|
2595
|
-
handler: origin.event.handler,
|
|
2596
|
-
args: origin.event.args
|
|
2597
|
-
};
|
|
2598
|
-
return origin.metadata;
|
|
2599
|
-
}
|
|
2600
|
-
function buildEventSource(event, origin) {
|
|
2601
2491
|
return {
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
guildId: origin.guild?.id ?? null,
|
|
2607
|
-
channelId: event.channelId,
|
|
2608
|
-
raw: event.args
|
|
2492
|
+
guild: null,
|
|
2493
|
+
member: null,
|
|
2494
|
+
user: null,
|
|
2495
|
+
channelId: null
|
|
2609
2496
|
};
|
|
2610
2497
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
}
|
|
2618
|
-
function interactionRoute(interaction) {
|
|
2619
|
-
if (interaction.isChatInputCommand()) return slashRouteOf(interaction);
|
|
2620
|
-
if (interaction.isContextMenuCommand()) return interaction.commandName;
|
|
2621
|
-
if (interaction.isButton() || interaction.isAnySelectMenu() || interaction.isModalSubmit()) return (0, _seedcord_core_internal.prefixOf)(interaction.customId) || interaction.customId;
|
|
2622
|
-
return "interaction";
|
|
2623
|
-
}
|
|
2624
|
-
function buildInteractionSource(interaction) {
|
|
2625
|
-
const command = interaction.isChatInputCommand() ? slashRouteOf(interaction) : interaction.isContextMenuCommand() ? interaction.commandName : null;
|
|
2626
|
-
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 ?? [];
|
|
2627
2504
|
return {
|
|
2628
2505
|
kind: "interaction",
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2506
|
+
interaction,
|
|
2507
|
+
core,
|
|
2508
|
+
user: interaction.user,
|
|
2509
|
+
guild: interaction.guild,
|
|
2510
|
+
member: rawMember instanceof discord_js.GuildMember ? rawMember : null,
|
|
2632
2511
|
userId: interaction.user.id,
|
|
2633
2512
|
guildId: interaction.guildId,
|
|
2634
2513
|
channelId: interaction.channelId,
|
|
2635
|
-
|
|
2636
|
-
|
|
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
|
|
2637
2520
|
};
|
|
2638
2521
|
}
|
|
2639
|
-
function
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
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
|
+
};
|
|
2645
2542
|
}
|
|
2646
2543
|
|
|
2647
2544
|
//#endregion
|
|
@@ -2656,7 +2553,7 @@ const logger$1 = new _seedcord_logger.Logger("EventBoundary");
|
|
|
2656
2553
|
*/
|
|
2657
2554
|
function handleEventFault(caught, eventName, handlerName, args, core) {
|
|
2658
2555
|
if (caught instanceof _seedcord_core.Silence) {
|
|
2659
|
-
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}`);
|
|
2660
2557
|
return;
|
|
2661
2558
|
}
|
|
2662
2559
|
if (!Error.isError(caught)) throw caught;
|
|
@@ -2846,7 +2743,7 @@ var EventDispatcher = class {
|
|
|
2846
2743
|
};
|
|
2847
2744
|
if (Array.isArray(raw)) for (const entry of raw) register(entry.event, entry.frequency);
|
|
2848
2745
|
else {
|
|
2849
|
-
const names = areRoutes(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
2746
|
+
const names = (0, _seedcord_core_internal.areRoutes)(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
2850
2747
|
for (const name of names) register(name, "on");
|
|
2851
2748
|
}
|
|
2852
2749
|
const from = (0, _seedcord_utils.formatFilePath)(relativePath);
|
|
@@ -2901,35 +2798,55 @@ var EventDispatcher = class {
|
|
|
2901
2798
|
};
|
|
2902
2799
|
|
|
2903
2800
|
//#endregion
|
|
2904
|
-
//#region src/bot/defaults/
|
|
2801
|
+
//#region src/bot/defaults/UnhandledRepliable.ts
|
|
2905
2802
|
/**
|
|
2906
|
-
* Default handler for unhandled interactions.
|
|
2907
|
-
* raw fault, so it needs no decorator of its own.
|
|
2908
|
-
*
|
|
2909
|
-
* @internal
|
|
2803
|
+
* @internal Default handler for unhandled repliable interactions.
|
|
2910
2804
|
*/
|
|
2911
|
-
var
|
|
2805
|
+
var UnhandledRepliable = class extends InteractionHandler {
|
|
2912
2806
|
async execute() {
|
|
2913
|
-
await this.
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
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([]);
|
|
2917
2819
|
}
|
|
2918
2820
|
};
|
|
2919
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
|
+
|
|
2920
2836
|
//#endregion
|
|
2921
2837
|
//#region src/bot/handleInteractionFault.ts
|
|
2922
2838
|
const logger = new _seedcord_logger.Logger("InteractionBoundary");
|
|
2923
2839
|
/**
|
|
2924
2840
|
* The interaction controller boundary. Sorts a throw from any handler-lifecycle phase into the right
|
|
2925
|
-
* user reply plus the right bus event
|
|
2926
|
-
*
|
|
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.
|
|
2927
2844
|
*
|
|
2928
2845
|
* @internal
|
|
2929
2846
|
*/
|
|
2930
|
-
async function handleInteractionFault(caught, interaction, core) {
|
|
2847
|
+
async function handleInteractionFault(caught, interaction, core, sender) {
|
|
2931
2848
|
if (caught instanceof _seedcord_core.Silence) {
|
|
2932
|
-
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}`);
|
|
2933
2850
|
return;
|
|
2934
2851
|
}
|
|
2935
2852
|
if (!Error.isError(caught)) throw caught;
|
|
@@ -2953,7 +2870,18 @@ async function handleInteractionFault(caught, interaction, core) {
|
|
|
2953
2870
|
user: interaction.user,
|
|
2954
2871
|
metadata: interaction
|
|
2955
2872
|
});
|
|
2956
|
-
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
|
+
}
|
|
2957
2885
|
}
|
|
2958
2886
|
|
|
2959
2887
|
//#endregion
|
|
@@ -3022,7 +2950,7 @@ var InteractionDispatcher = class {
|
|
|
3022
2950
|
}
|
|
3023
2951
|
/**
|
|
3024
2952
|
* Warns for each command route with no registered `@SlashRoute` handler, which would fall through to
|
|
3025
|
-
*
|
|
2953
|
+
* UnhandledRepliable at runtime. A bot may route commands outside the registry, so this does not throw.
|
|
3026
2954
|
*
|
|
3027
2955
|
* @internal
|
|
3028
2956
|
*/
|
|
@@ -3030,7 +2958,7 @@ var InteractionDispatcher = class {
|
|
|
3030
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.`);
|
|
3031
2959
|
}
|
|
3032
2960
|
/**
|
|
3033
|
-
* 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
|
|
3034
2962
|
* at runtime. Checked per kind since a user and a message command can share a name.
|
|
3035
2963
|
*
|
|
3036
2964
|
* @internal
|
|
@@ -3043,7 +2971,7 @@ var InteractionDispatcher = class {
|
|
|
3043
2971
|
const artifacts = [];
|
|
3044
2972
|
for (const [routeType] of this.routeTypes) {
|
|
3045
2973
|
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
3046
|
-
if (areRoutes(meta)) artifacts.push({
|
|
2974
|
+
if ((0, _seedcord_core_internal.areRoutes)(meta)) artifacts.push({
|
|
3047
2975
|
routeType,
|
|
3048
2976
|
routes: meta
|
|
3049
2977
|
});
|
|
@@ -3138,7 +3066,7 @@ var InteractionDispatcher = class {
|
|
|
3138
3066
|
const writes = [];
|
|
3139
3067
|
for (const [routeType, map] of this.routeTypes) {
|
|
3140
3068
|
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
3141
|
-
if (!areRoutes(meta)) continue;
|
|
3069
|
+
if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
|
|
3142
3070
|
for (const route of meta) {
|
|
3143
3071
|
const existing = map.get(route);
|
|
3144
3072
|
if (existing && existing !== handlerClass) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.InteractionDuplicateRoute, [
|
|
@@ -3172,7 +3100,7 @@ var InteractionDispatcher = class {
|
|
|
3172
3100
|
}
|
|
3173
3101
|
for (const [routeType, map] of this.routeTypes) {
|
|
3174
3102
|
const meta = Reflect.getMetadata(_seedcord_core_internal.InteractionRouteKeys[routeType], handlerClass);
|
|
3175
|
-
if (!areRoutes(meta)) continue;
|
|
3103
|
+
if (!(0, _seedcord_core_internal.areRoutes)(meta)) continue;
|
|
3176
3104
|
meta.forEach((route) => map.delete(route));
|
|
3177
3105
|
}
|
|
3178
3106
|
}
|
|
@@ -3195,22 +3123,31 @@ var InteractionDispatcher = class {
|
|
|
3195
3123
|
if (!prefix) return this.logger.warn(`${interactionType} has invalid customId: ${interaction.customId}`);
|
|
3196
3124
|
await this.processInteraction(interaction, () => prefix, (key) => getMap().get(key));
|
|
3197
3125
|
}
|
|
3198
|
-
async processInteraction(interaction, extractKey, getHandler) {
|
|
3126
|
+
async processInteraction(interaction, extractKey, getHandler, fallback = UnhandledRepliable) {
|
|
3199
3127
|
const key = extractKey(interaction);
|
|
3128
|
+
let sender;
|
|
3200
3129
|
try {
|
|
3201
3130
|
if (!interaction.isAutocomplete()) for (const { ctor } of this.middlewares) await new ctor(interaction, this.core).execute();
|
|
3202
3131
|
let HandlerCtor = getHandler(key);
|
|
3203
3132
|
if (!HandlerCtor) {
|
|
3204
|
-
this.logger.warn(`No handler found for key ${_seedcord_logger.paint.sky.bold(key)}. Falling back to
|
|
3205
|
-
HandlerCtor =
|
|
3133
|
+
this.logger.warn(`No handler found for key ${_seedcord_logger.paint.sky.bold(key)}. Falling back to ${fallback.name}.`);
|
|
3134
|
+
HandlerCtor = fallback;
|
|
3206
3135
|
}
|
|
3207
3136
|
this.logger.debug(`Processing ${_seedcord_logger.paint.mint.bold(key)} with ${_seedcord_logger.paint.mute(HandlerCtor.name)}`);
|
|
3208
3137
|
const dispatch = new _seedcord_core.DispatchContext((0, _seedcord_core_internal.routeIdOf)(HandlerCtor));
|
|
3209
3138
|
const handler = new HandlerCtor(interaction, this.core, dispatch);
|
|
3210
|
-
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
|
+
}
|
|
3211
3148
|
await handler.execute();
|
|
3212
3149
|
} catch (caught) {
|
|
3213
|
-
await handleInteractionFault(caught, interaction, this.core);
|
|
3150
|
+
await handleInteractionFault(caught, interaction, this.core, sender);
|
|
3214
3151
|
}
|
|
3215
3152
|
}
|
|
3216
3153
|
async handleInteraction(interaction) {
|
|
@@ -3286,12 +3223,7 @@ var InteractionDispatcher = class {
|
|
|
3286
3223
|
}
|
|
3287
3224
|
async handleAutocomplete(interaction) {
|
|
3288
3225
|
const route = slashRouteOf(interaction);
|
|
3289
|
-
|
|
3290
|
-
this.logger.warn(`No autocomplete handler for ${_seedcord_logger.paint.sky.bold(route)}.`);
|
|
3291
|
-
await interaction.respond([]);
|
|
3292
|
-
return;
|
|
3293
|
-
}
|
|
3294
|
-
await this.processInteraction(interaction, () => route, (key) => this.autocompleteMap.get(key));
|
|
3226
|
+
await this.processInteraction(interaction, () => route, (key) => this.autocompleteMap.get(key), UnhandledAutocomplete);
|
|
3295
3227
|
}
|
|
3296
3228
|
};
|
|
3297
3229
|
|
|
@@ -3347,7 +3279,7 @@ var Bot = class extends Plugin {
|
|
|
3347
3279
|
});
|
|
3348
3280
|
}
|
|
3349
3281
|
this.emojiInjector = new EmojiInjector(core);
|
|
3350
|
-
core.shutdown.addTask(
|
|
3282
|
+
core.shutdown.addTask(_seedcord_core_node.ShutdownPhase.DiscordCleanup, "stop-bot", async () => await this.stop(), 2e3);
|
|
3351
3283
|
}
|
|
3352
3284
|
/**
|
|
3353
3285
|
* Initializes Discord client and all controllers
|
|
@@ -3380,7 +3312,9 @@ var Bot = class extends Plugin {
|
|
|
3380
3312
|
* Logs the bot into Discord using the configured token
|
|
3381
3313
|
*/
|
|
3382
3314
|
async login(token) {
|
|
3383
|
-
|
|
3315
|
+
this._client.once(discord_js.Events.ClientReady, () => this.emit("ready"));
|
|
3316
|
+
this._client.login(token);
|
|
3317
|
+
await this.waitFor("ready");
|
|
3384
3318
|
this.logger.info(`Logged in as ${chalk.default.bold.magenta(this._client.user?.username)}!`);
|
|
3385
3319
|
return this;
|
|
3386
3320
|
}
|
|
@@ -3406,157 +3340,70 @@ var Bot = class extends Plugin {
|
|
|
3406
3340
|
};
|
|
3407
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);
|
|
3408
3342
|
|
|
3409
|
-
//#endregion
|
|
3410
|
-
//#region src/subscribers/bases/webhookHelpers.ts
|
|
3411
|
-
const DISCORD_WEBHOOK_REGEX = new RegExp(String.raw`^https?:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[\w$-]+$`);
|
|
3412
|
-
/**
|
|
3413
|
-
* Whether a string is a well-formed Discord webhook URL.
|
|
3414
|
-
*
|
|
3415
|
-
* @internal
|
|
3416
|
-
*/
|
|
3417
|
-
function isDiscordWebhookUrl(value) {
|
|
3418
|
-
return URL.canParse(value) && DISCORD_WEBHOOK_REGEX.test(value);
|
|
3419
|
-
}
|
|
3420
|
-
/**
|
|
3421
|
-
* Serializes a value to a JSON attachment, made circular-safe first.
|
|
3422
|
-
*
|
|
3423
|
-
* @internal
|
|
3424
|
-
*/
|
|
3425
|
-
function jsonAttachment(name, description, data) {
|
|
3426
|
-
const content = (0, _seedcord_utils.filterCirculars)(data);
|
|
3427
|
-
return new discord_js.AttachmentBuilder(Buffer.from(JSON.stringify(content, void 0, 2), "utf8"), {
|
|
3428
|
-
name,
|
|
3429
|
-
description
|
|
3430
|
-
});
|
|
3431
|
-
}
|
|
3432
|
-
/**
|
|
3433
|
-
* The small divider the default webhook reporters place between sections.
|
|
3434
|
-
*
|
|
3435
|
-
* @internal
|
|
3436
|
-
*/
|
|
3437
|
-
var WebhookSeparator = class extends _seedcord_core.BuilderComponent {
|
|
3438
|
-
constructor() {
|
|
3439
|
-
super("separator");
|
|
3440
|
-
this.instance.setSpacing(discord_js.SeparatorSpacingSize.Small).setDivider(true);
|
|
3441
|
-
}
|
|
3442
|
-
};
|
|
3443
|
-
|
|
3444
3343
|
//#endregion
|
|
3445
3344
|
//#region src/subscribers/default/HandledException.ts
|
|
3446
|
-
var _HandledException;
|
|
3447
|
-
function webhookUrlValidator$1(raw) {
|
|
3448
|
-
if (raw !== null && typeof raw !== "string") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigHandledExceptionWebhookInvalid);
|
|
3449
|
-
const value = raw?.trim() ?? "";
|
|
3450
|
-
if (value === "") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigHandledExceptionWebhookMissing);
|
|
3451
|
-
if (!isDiscordWebhookUrl(value)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigHandledExceptionWebhookInvalid);
|
|
3452
|
-
return value;
|
|
3453
|
-
}
|
|
3454
3345
|
let HandledException = class HandledException extends WebhookLog {
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
try {
|
|
3462
|
-
await this.webhook.send({
|
|
3463
|
-
flags: flagsFor(false),
|
|
3464
|
-
withComponents: true,
|
|
3465
|
-
username: "Handled Exception",
|
|
3466
|
-
components: [new HandledExceptionContainer(this.data).component],
|
|
3467
|
-
files: [jsonAttachment("source.json", "The raw source the fault came from", source.raw)]
|
|
3468
|
-
});
|
|
3469
|
-
} catch (error) {
|
|
3470
|
-
this.logger.error("Failed to send handled exception webhook", error);
|
|
3471
|
-
}
|
|
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
|
+
};
|
|
3472
3352
|
}
|
|
3473
3353
|
};
|
|
3474
|
-
__decorate([(
|
|
3475
|
-
HandledException = _HandledException = __decorate([Subscribe("handledException")], HandledException);
|
|
3354
|
+
HandledException = __decorate([Subscribe("handledException"), WebhookUrl("HANDLED_EXCEPTION_WEBHOOK_URL")], HandledException);
|
|
3476
3355
|
var HandledExceptionContainer = class extends _seedcord_core.BuilderComponent {
|
|
3477
3356
|
constructor(data) {
|
|
3478
3357
|
super("container");
|
|
3479
3358
|
const { denial, uuid, source } = data;
|
|
3480
|
-
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"));
|
|
3481
3360
|
}
|
|
3482
3361
|
};
|
|
3483
|
-
/**
|
|
3484
|
-
* The summary block of a fault report, narrowed per source kind so an interaction fault shows its
|
|
3485
|
-
* command and customId while an event fault shows its event name and handler.
|
|
3486
|
-
*
|
|
3487
|
-
* @internal
|
|
3488
|
-
*/
|
|
3489
3362
|
function faultSummary(denial, source) {
|
|
3490
|
-
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`);
|
|
3491
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"}\``;
|
|
3492
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}\``;
|
|
3493
3366
|
}
|
|
3494
|
-
/**
|
|
3495
|
-
* The text shown for a denial's cause in the report. Tolerates a non-serializable cause (a bigint or a
|
|
3496
|
-
* circular object) so building the report never throws.
|
|
3497
|
-
*
|
|
3498
|
-
* @internal
|
|
3499
|
-
*/
|
|
3500
3367
|
function causeStack(denial) {
|
|
3501
3368
|
const { cause } = denial;
|
|
3502
|
-
if (Error.isError(cause)) return cause.stack ?? cause.message;
|
|
3503
|
-
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);
|
|
3504
3371
|
if (typeof cause === "bigint" || typeof cause === "symbol" || typeof cause === "function") return cause.toString();
|
|
3505
3372
|
if (cause !== void 0) try {
|
|
3506
3373
|
return JSON.stringify(cause);
|
|
3507
3374
|
} catch {
|
|
3508
3375
|
return "[unserializable cause]";
|
|
3509
3376
|
}
|
|
3510
|
-
return denial.stack ?? "No cause recorded.";
|
|
3377
|
+
return (0, _seedcord_utils.stripAnsi)(denial.stack ?? "No cause recorded.");
|
|
3511
3378
|
}
|
|
3512
3379
|
|
|
3513
3380
|
//#endregion
|
|
3514
3381
|
//#region src/subscribers/default/UnknownException.ts
|
|
3515
|
-
var _UnknownException;
|
|
3516
|
-
function webhookUrlValidator(raw) {
|
|
3517
|
-
if (raw !== null && typeof raw !== "string") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
|
|
3518
|
-
const value = raw?.trim() ?? "";
|
|
3519
|
-
if (value === "") throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigUnknownExceptionWebhookMissing);
|
|
3520
|
-
if (!isDiscordWebhookUrl(value)) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.ConfigUnknownExceptionWebhookInvalid);
|
|
3521
|
-
return value;
|
|
3522
|
-
}
|
|
3523
3382
|
let UnknownException = class UnknownException extends WebhookLog {
|
|
3524
|
-
|
|
3525
|
-
_UnknownException = this;
|
|
3526
|
-
}
|
|
3527
|
-
webhook = new discord_js.WebhookClient({ url: _UnknownException.unknownExceptionWebhookUrl });
|
|
3528
|
-
async execute() {
|
|
3383
|
+
report() {
|
|
3529
3384
|
const { metadata } = this.data;
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
username: "Unknown Exception",
|
|
3536
|
-
components: [new UnhandledErrorContainer(this.data).component],
|
|
3537
|
-
files: metadataFile ? [metadataFile] : []
|
|
3538
|
-
});
|
|
3539
|
-
} catch (error) {
|
|
3540
|
-
this.logger.error("Failed to send unknown exception webhook", error);
|
|
3541
|
-
}
|
|
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
|
+
};
|
|
3542
3390
|
}
|
|
3543
3391
|
};
|
|
3544
|
-
__decorate([(
|
|
3545
|
-
UnknownException = _UnknownException = __decorate([Subscribe("unknownException")], UnknownException);
|
|
3392
|
+
UnknownException = __decorate([Subscribe("unknownException"), WebhookUrl("UNKNOWN_EXCEPTION_WEBHOOK_URL")], UnknownException);
|
|
3546
3393
|
var UnhandledErrorContainer = class extends _seedcord_core.BuilderComponent {
|
|
3547
3394
|
constructor(data) {
|
|
3548
3395
|
super("container");
|
|
3549
3396
|
const { uuid, error, guild, user, metadata } = data;
|
|
3550
|
-
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)}\`\`\``));
|
|
3551
3398
|
this.addTimestampsIfAvailable(error);
|
|
3552
3399
|
this.addMetadataIfAvailable(metadata);
|
|
3553
3400
|
}
|
|
3554
3401
|
addTimestampsIfAvailable(error) {
|
|
3555
|
-
if (!(error instanceof
|
|
3402
|
+
if (!(error instanceof _discordjs_rest.DiscordAPIError)) return;
|
|
3556
3403
|
const now = Date.now();
|
|
3557
3404
|
const snowflake = error.url.match(/\/interactions\/(\d+)\//)?.[1];
|
|
3558
3405
|
if (!snowflake) return void 0;
|
|
3559
|
-
const interactionTs =
|
|
3406
|
+
const interactionTs = (0, _seedcord_utils.timestampFromSnowflake)(snowflake);
|
|
3560
3407
|
const diff = now - interactionTs;
|
|
3561
3408
|
const seconds = Math.floor(diff / 1e3);
|
|
3562
3409
|
const millis = diff % 1e3;
|
|
@@ -3571,12 +3418,8 @@ var UnhandledErrorContainer = class extends _seedcord_core.BuilderComponent {
|
|
|
3571
3418
|
//#endregion
|
|
3572
3419
|
//#region src/subscribers/Bus.ts
|
|
3573
3420
|
/**
|
|
3574
|
-
*
|
|
3575
|
-
*
|
|
3576
|
-
* Provides a centralized system for registering and executing custom subscribers
|
|
3577
|
-
* throughout the application lifecycle. Bus subscribers are loaded from configured directories
|
|
3578
|
-
* and can be triggered programmatically or by framework events. Accessed via `core.bus`. Do not
|
|
3579
|
-
* 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.
|
|
3580
3423
|
*/
|
|
3581
3424
|
var Bus = class extends Plugin {
|
|
3582
3425
|
core;
|
|
@@ -3586,6 +3429,7 @@ var Bus = class extends Plugin {
|
|
|
3586
3429
|
isInitialized = false;
|
|
3587
3430
|
subscribersMap = /* @__PURE__ */ new Map();
|
|
3588
3431
|
executedOnceHandlers = /* @__PURE__ */ new Set();
|
|
3432
|
+
webhookProbes = /* @__PURE__ */ new Map();
|
|
3589
3433
|
hmrHandler;
|
|
3590
3434
|
constructor(core) {
|
|
3591
3435
|
super(core);
|
|
@@ -3611,7 +3455,6 @@ var Bus = class extends Plugin {
|
|
|
3611
3455
|
this.isInitialized = true;
|
|
3612
3456
|
this.registerSubscriber(UnknownException);
|
|
3613
3457
|
this.registerSubscriber(HandledException);
|
|
3614
|
-
envapt.Envapter.require("UNKNOWN_EXCEPTION_WEBHOOK_URL", "HANDLED_EXCEPTION_WEBHOOK_URL");
|
|
3615
3458
|
const subscribersDir = this.core.config.subscribers.path;
|
|
3616
3459
|
if (subscribersDir) {
|
|
3617
3460
|
this.logger.info(chalk.default.bold(subscribersDir));
|
|
@@ -3619,8 +3462,19 @@ var Bus = class extends Plugin {
|
|
|
3619
3462
|
const totalSubscribers = [...this.subscribersMap.values()].reduce((acc, handlers) => acc + handlers.length, 0);
|
|
3620
3463
|
this.logger.utils.list([`${chalk.default.bold.magenta(totalSubscribers)} subscribers`], chalk.default.bold.green("Loaded"));
|
|
3621
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(", ")]);
|
|
3622
3476
|
}
|
|
3623
|
-
/** @internal
|
|
3477
|
+
/** @internal */
|
|
3624
3478
|
async onHmr(event) {
|
|
3625
3479
|
if (this.hmrHandler) await this.hmrHandler.handle(event);
|
|
3626
3480
|
}
|
|
@@ -3637,6 +3491,17 @@ var Bus = class extends Plugin {
|
|
|
3637
3491
|
}, this.logger);
|
|
3638
3492
|
}
|
|
3639
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
|
+
}
|
|
3640
3505
|
const options = Reflect.getMetadata(_seedcord_core_internal.SubscribeMetadataKey, handler);
|
|
3641
3506
|
let handlers = this.subscribersMap.get(options.subscriber);
|
|
3642
3507
|
if (!handlers) {
|
|
@@ -3696,7 +3561,7 @@ var Bus = class extends Plugin {
|
|
|
3696
3561
|
//#endregion
|
|
3697
3562
|
//#region src/version.ts
|
|
3698
3563
|
/** Package version */
|
|
3699
|
-
const version = "0.1.0-next.
|
|
3564
|
+
const version = "0.1.0-next.3";
|
|
3700
3565
|
|
|
3701
3566
|
//#endregion
|
|
3702
3567
|
//#region src/Seedcord.ts
|
|
@@ -3736,8 +3601,8 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3736
3601
|
constructor(config) {
|
|
3737
3602
|
if (Seedcord.isInstantiated) throw new _seedcord_errors_internal.SeedcordError(_seedcord_errors.SeedcordErrorCode.CoreSingletonViolation);
|
|
3738
3603
|
Seedcord.isInstantiated = true;
|
|
3739
|
-
const shutdown = new
|
|
3740
|
-
const startup = new
|
|
3604
|
+
const shutdown = new _seedcord_core_node.CoordinatedShutdown(config.shutdownEnabled);
|
|
3605
|
+
const startup = new _seedcord_core_node.CoordinatedStartup();
|
|
3741
3606
|
super(shutdown, startup);
|
|
3742
3607
|
this.config = config;
|
|
3743
3608
|
(0, _seedcord_logger_node.installNodeDefaults)(config.logger);
|
|
@@ -3749,7 +3614,7 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3749
3614
|
this.bus = new Bus(this);
|
|
3750
3615
|
this.bot = new Bot(this);
|
|
3751
3616
|
this.rateLimiter = config.store ?? new _seedcord_rate_limiter.MemoryRateLimiter();
|
|
3752
|
-
this.healthCheck = new
|
|
3617
|
+
this.healthCheck = new _seedcord_core_node.HealthCheck(this.shutdown, config.healthCheck);
|
|
3753
3618
|
this.registerStartupTasks();
|
|
3754
3619
|
}
|
|
3755
3620
|
/** The bot's discord username, populated after login. */
|
|
@@ -3762,17 +3627,17 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3762
3627
|
}
|
|
3763
3628
|
registerStartupTasks() {
|
|
3764
3629
|
if (envapt.Envapter.isDevelopment || envapt.Envapter.isTest) this.registerHmrAwareModules();
|
|
3765
|
-
this.startup.addTask(
|
|
3630
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Configuration, "Bus Initialization", async () => {
|
|
3766
3631
|
this.bus.logger.utils.initialization("Subscribers", "start");
|
|
3767
3632
|
await this.bus.init();
|
|
3768
3633
|
this.bus.logger.utils.initialization("Subscribers", "end");
|
|
3769
3634
|
});
|
|
3770
|
-
this.startup.addTask(
|
|
3635
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Instantiation, "Bot Initialization", async () => {
|
|
3771
3636
|
this.bot.logger.utils.initialization("Bot", "start");
|
|
3772
3637
|
await this.bot.init();
|
|
3773
3638
|
this.bot.logger.utils.initialization("Bot", "end");
|
|
3774
3639
|
});
|
|
3775
|
-
this.startup.addTask(
|
|
3640
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Ready, "Health Check", async () => {
|
|
3776
3641
|
this.healthCheck.logger.utils.initialization("HealthCheck", "start");
|
|
3777
3642
|
await this.healthCheck.init();
|
|
3778
3643
|
this.healthCheck.logger.utils.initialization("HealthCheck", "end");
|
|
@@ -3788,7 +3653,7 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3788
3653
|
return this;
|
|
3789
3654
|
}
|
|
3790
3655
|
registerHmrAwareModules() {
|
|
3791
|
-
this.startup.addTask(
|
|
3656
|
+
this.startup.addTask(_seedcord_core_node.StartupPhase.Configuration, "HMR Registration", async () => {
|
|
3792
3657
|
this.hmrManager.register(this.bot);
|
|
3793
3658
|
this.hmrManager.register(this.bus);
|
|
3794
3659
|
for (const plugin of this.plugins) this.hmrManager.register(plugin);
|
|
@@ -3800,12 +3665,9 @@ var Seedcord = class Seedcord extends Pluggable {
|
|
|
3800
3665
|
//#endregion
|
|
3801
3666
|
exports.ArraySource = ArraySource;
|
|
3802
3667
|
exports.AutocompleteHandler = AutocompleteHandler;
|
|
3803
|
-
exports.AutocompleteRoute = AutocompleteRoute;
|
|
3804
3668
|
exports.ButtonHandler = ButtonHandler;
|
|
3805
|
-
exports.ButtonRoute = ButtonRoute;
|
|
3806
3669
|
exports.CommandMentions = CommandMentions;
|
|
3807
3670
|
exports.ContextMenuHandler = ContextMenuHandler;
|
|
3808
|
-
exports.ContextMenuRoute = ContextMenuRoute;
|
|
3809
3671
|
exports.CursorSource = CursorSource;
|
|
3810
3672
|
exports.Emojis = Emojis;
|
|
3811
3673
|
exports.EventHandler = EventHandler;
|
|
@@ -3817,31 +3679,31 @@ exports.InteractionMiddleware = InteractionMiddleware;
|
|
|
3817
3679
|
exports.Middleware = Middleware;
|
|
3818
3680
|
exports.MiddlewareType = MiddlewareType;
|
|
3819
3681
|
exports.ModalHandler = ModalHandler;
|
|
3820
|
-
exports.ModalRoute = ModalRoute;
|
|
3821
3682
|
exports.Nsfw = Nsfw;
|
|
3822
3683
|
exports.Paginator = Paginator;
|
|
3823
3684
|
exports.Pluggable = Pluggable;
|
|
3824
3685
|
exports.Plugin = Plugin;
|
|
3825
|
-
|
|
3686
|
+
exports.RegisterEvent = RegisterEvent;
|
|
3687
|
+
exports.RepliableHandler = RepliableHandler;
|
|
3688
|
+
exports.Seedcord = Seedcord;
|
|
3689
|
+
exports.SelectMenuHandler = SelectMenuHandler;
|
|
3690
|
+
Object.defineProperty(exports, 'ShutdownPhase', {
|
|
3826
3691
|
enumerable: true,
|
|
3827
3692
|
get: function () {
|
|
3828
|
-
return
|
|
3693
|
+
return _seedcord_core_node.ShutdownPhase;
|
|
3829
3694
|
}
|
|
3830
3695
|
});
|
|
3831
|
-
exports.RegisterEvent = RegisterEvent;
|
|
3832
|
-
exports.ReplySender = ReplySender;
|
|
3833
|
-
exports.RequireBotPermissions = RequireBotPermissions;
|
|
3834
|
-
exports.RequirePermissions = RequirePermissions;
|
|
3835
|
-
exports.RequireRole = RequireRole;
|
|
3836
|
-
exports.Seedcord = Seedcord;
|
|
3837
|
-
exports.SelectMenuHandler = SelectMenuHandler;
|
|
3838
|
-
exports.SelectMenuKind = SelectMenuKind;
|
|
3839
|
-
exports.SelectMenuRoute = SelectMenuRoute;
|
|
3840
3696
|
exports.SlashHandler = SlashHandler;
|
|
3841
|
-
exports
|
|
3697
|
+
Object.defineProperty(exports, 'StartupPhase', {
|
|
3698
|
+
enumerable: true,
|
|
3699
|
+
get: function () {
|
|
3700
|
+
return _seedcord_core_node.StartupPhase;
|
|
3701
|
+
}
|
|
3702
|
+
});
|
|
3842
3703
|
exports.Subscribe = Subscribe;
|
|
3843
3704
|
exports.Subscriber = Subscriber;
|
|
3844
3705
|
exports.WebhookLog = WebhookLog;
|
|
3706
|
+
exports.WebhookUrl = WebhookUrl;
|
|
3845
3707
|
exports.checkBotPermissions = checkBotPermissions;
|
|
3846
3708
|
exports.checkPermissions = checkPermissions;
|
|
3847
3709
|
exports.fetchGuildMember = fetchGuildMember;
|
|
@@ -3890,13 +3752,6 @@ Object.keys(_seedcord_rate_limiter).forEach(function (k) {
|
|
|
3890
3752
|
});
|
|
3891
3753
|
});
|
|
3892
3754
|
|
|
3893
|
-
Object.keys(_seedcord_services).forEach(function (k) {
|
|
3894
|
-
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
3895
|
-
enumerable: true,
|
|
3896
|
-
get: function () { return _seedcord_services[k]; }
|
|
3897
|
-
});
|
|
3898
|
-
});
|
|
3899
|
-
|
|
3900
3755
|
Object.keys(_seedcord_utils).forEach(function (k) {
|
|
3901
3756
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
3902
3757
|
enumerable: true,
|