@seedcord/core 0.12.0-next.0 → 0.12.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/README.md +3 -3
- package/dist/{Bus-W1K-uMeO.mjs → Bus-VJw-QpoY.mjs} +110 -54
- package/dist/Bus-VJw-QpoY.mjs.map +1 -0
- package/dist/{CoordinatedShutdown-BVtNNSsF.d.mts → CoordinatedShutdown-fPKLWjc5.d.mts} +2 -2
- package/dist/{CoordinatedStartup-DA3gW8op.mjs → CoordinatedStartup-Zw_Lk7Zk.mjs} +3 -3
- package/dist/{CoordinatedStartup-DA3gW8op.mjs.map → CoordinatedStartup-Zw_Lk7Zk.mjs.map} +1 -1
- package/dist/HmrModuleHandler-Btgu8r96.mjs +165 -0
- package/dist/HmrModuleHandler-Btgu8r96.mjs.map +1 -0
- package/dist/{PluginHost-DWBgBS6Q.d.mts → PluginHost-DFnkIldj.d.mts} +10 -9
- package/dist/{PluginHost-CYe-UVpp.mjs → PluginHost-DTtxWRiL.mjs} +4 -4
- package/dist/PluginHost-DTtxWRiL.mjs.map +1 -0
- package/dist/{PluginLifecycle-DdwRzcDj.mjs → PluginLifecycle-hx5zUc-P.mjs} +4 -4
- package/dist/PluginLifecycle-hx5zUc-P.mjs.map +1 -0
- package/dist/{ServerHost-whpI0h4d.d.mts → ServerHost-B5o3ScCF.d.mts} +3 -3
- package/dist/{ServerHost-DHg-Y2_q.mjs → ServerHost-CDaEDR4C.mjs} +4 -4
- package/dist/{ServerHost-DHg-Y2_q.mjs.map → ServerHost-CDaEDR4C.mjs.map} +1 -1
- package/dist/hmr.index.d.mts +20 -20
- package/dist/hmr.index.mjs +1 -1
- package/dist/{index-Boo33hh3.d.mts → index-DGHng1IN.d.mts} +108 -193
- package/dist/index.d.mts +221 -74
- package/dist/index.mjs +222 -103
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +204 -82
- package/dist/internal.index.mjs +572 -73
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +4 -3
- package/dist/node-internal.index.mjs +3 -3
- package/dist/node.index.d.mts +2 -2
- package/dist/node.index.mjs +4 -4
- package/dist/{phases-Dojnr-ng.d.mts → phases-C2QCSqRu.d.mts} +60 -50
- package/dist/plugin.index.d.mts +1 -1
- package/dist/plugin.index.mjs +1 -1
- package/dist/{subscribers-D4_HZcgw.mjs → subscribers-CGuVjbrg.mjs} +139 -253
- package/dist/subscribers-CGuVjbrg.mjs.map +1 -0
- package/package.json +10 -9
- package/dist/Bus-W1K-uMeO.mjs.map +0 -1
- package/dist/HmrModuleHandler-BMD7dg8N.mjs +0 -162
- package/dist/HmrModuleHandler-BMD7dg8N.mjs.map +0 -1
- package/dist/PluginHost-CYe-UVpp.mjs.map +0 -1
- package/dist/PluginLifecycle-DdwRzcDj.mjs.map +0 -1
- package/dist/subscribers-D4_HZcgw.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { M as
|
|
1
|
+
import { D as InteractionKind, N as BuilderComponent, P as RowComponent, b as StartupPhase, d as WebhookLog, f as Subscriber, l as WebhookUrl, t as Bus, u as Subscribe, v as MemoryDriver, y as ShutdownPhase } from "./Bus-VJw-QpoY.mjs";
|
|
2
|
+
import { A as RegisterInteractionMiddleware, B as RegisterCommand, C as attemptWrite, D as AckTrace, F as storeComponentRoute, H as Commands, I as storeInteractionRoute, M as contextMenuRouteOf, O as checkAckLegality, S as translateSerializationError, U as ContextMenus, _ as NotInGuild, at as Notice, b as PermissionsUnresolved, c as runCheck, d as HasPresentPermissions, f as MissingPermissions, g as NotInDm, h as NotAllowed, i as Silence, k as sendTarget, m as NeedsAny, n as pageCursor, o as markCommits, ot as NoticeCard, p as MissingRole, r as ResolvedEmoji, s as rollbackCommits, t as PAGE_MAX, u as Fault, v as NotOwner, w as publishResponse, x as serializeReply, y as OnCooldown } from "./subscribers-CGuVjbrg.mjs";
|
|
3
3
|
import "reflect-metadata";
|
|
4
4
|
import { CustomId, decodeFor, prefixOf, setCustomIdErrors, setCustomIdErrors as setCustomIdErrors$1 } from "@seedcord/custom-id";
|
|
5
5
|
import { ActionRowBuilder, ButtonBuilder, TextDisplayBuilder } from "@discordjs/builders";
|
|
6
|
-
import { ApplicationCommandType, ButtonStyle } from "discord-api-types/v10";
|
|
6
|
+
import { ApplicationCommandType, ButtonStyle, PermissionFlagsBits } from "discord-api-types/v10";
|
|
7
7
|
import { SeedcordErrorCode } from "@seedcord/errors";
|
|
8
8
|
import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
|
|
9
9
|
import { Logger } from "@seedcord/logger";
|
|
10
|
-
import { parseDuration } from "@seedcord/utils";
|
|
10
|
+
import { parseDuration, prettify } from "@seedcord/utils";
|
|
11
11
|
|
|
12
12
|
//#region src/customId/Errors.ts
|
|
13
13
|
/**
|
|
@@ -47,8 +47,8 @@ setCustomIdErrors$1({
|
|
|
47
47
|
/**
|
|
48
48
|
* Routes one or more slash commands to a `SlashHandler`.
|
|
49
49
|
*
|
|
50
|
-
* Pass the same route string(s) as the handler's generic.
|
|
51
|
-
*
|
|
50
|
+
* Pass the same route string(s) as the handler's generic. On a handler for one command, read
|
|
51
|
+
* `this.options`. On a handler for several, branch with `this.match`. The decorator routes and the generic must match exactly. Listing
|
|
52
52
|
* fewer or more than the handler declares is a compile error. Subcommands use a slash path.
|
|
53
53
|
*
|
|
54
54
|
* @param routes - The route string(s) this handler serves, e.g. `'ban'`, `'ban', 'kick'`, or `'demo/setup'`.
|
|
@@ -72,7 +72,7 @@ function SlashRoute(...routes) {
|
|
|
72
72
|
* Routes one or more commands' autocomplete to an `AutocompleteHandler`.
|
|
73
73
|
*
|
|
74
74
|
* Pass the same command route string(s) as the handler's generic. One command branches with `this.match`
|
|
75
|
-
* over its autocompletable fields, several commands share one handler whose
|
|
75
|
+
* over its autocompletable fields, several commands share one handler whose cases span every command's
|
|
76
76
|
* fields. Listing fewer or more commands than the handler declares is a compile error, since the
|
|
77
77
|
* decorator routes and the generic must match exactly.
|
|
78
78
|
*
|
|
@@ -174,7 +174,7 @@ function ModalRoute(...defs) {
|
|
|
174
174
|
* Routes string select menu interactions to a `StringMenuHandler`.
|
|
175
175
|
*
|
|
176
176
|
* Pass the {@link CustomId} definition(s) this handler decodes and list the same ones in the handler's
|
|
177
|
-
* generic.
|
|
177
|
+
* generic. Read the chosen option values from `this.values`.
|
|
178
178
|
*
|
|
179
179
|
* @param defs - The customId definition(s) this handler decodes, one per route.
|
|
180
180
|
* @decorator
|
|
@@ -289,38 +289,6 @@ function MentionableMenuRoute(...defs) {
|
|
|
289
289
|
};
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
//#endregion
|
|
293
|
-
//#region src/dispatch/DispatchContext.ts
|
|
294
|
-
/** State shared by middleware and handlers during one dispatch. */
|
|
295
|
-
var DispatchContext = class {
|
|
296
|
-
state = Object.create(null);
|
|
297
|
-
id = crypto.randomUUID();
|
|
298
|
-
/**
|
|
299
|
-
* The dispatch identifier, such as `slash:search/docs`, `button:approve`, or `event:messageCreate`.
|
|
300
|
-
* Component identifiers contain the custom-ID prefix without encoded params.
|
|
301
|
-
*/
|
|
302
|
-
routeId;
|
|
303
|
-
constructor(routeId) {
|
|
304
|
-
this.routeId = routeId;
|
|
305
|
-
}
|
|
306
|
-
set(key, value) {
|
|
307
|
-
this.state[key] = value;
|
|
308
|
-
}
|
|
309
|
-
get(key) {
|
|
310
|
-
return this.state[key];
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Read a key that must be present, with `undefined` stripped from the result.
|
|
314
|
-
*
|
|
315
|
-
* @throws A **SeedcordError** when nothing set the key on this dispatch.
|
|
316
|
-
*/
|
|
317
|
-
require(key) {
|
|
318
|
-
const value = this.state[key];
|
|
319
|
-
if (value === void 0) throw new SeedcordError(SeedcordErrorCode.DispatchStateMissing, [String(key)]);
|
|
320
|
-
return value;
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
|
|
324
292
|
//#endregion
|
|
325
293
|
//#region src/handlers/BaseHandler.ts
|
|
326
294
|
/**
|
|
@@ -336,11 +304,14 @@ var BaseHandler = class {
|
|
|
336
304
|
logger;
|
|
337
305
|
/** The bag for this dispatch. Each interaction gets its own. Every handler for one event shares a single bag. */
|
|
338
306
|
dispatch;
|
|
307
|
+
/** The route that started this dispatch. Each handler base narrows it to its own routes. */
|
|
308
|
+
route;
|
|
339
309
|
constructor(event, core, dispatch, channel) {
|
|
340
310
|
this.core = core;
|
|
341
311
|
this.event = event;
|
|
342
312
|
this.logger = new Logger(this.constructor.name, { channel });
|
|
343
313
|
this.dispatch = dispatch;
|
|
314
|
+
this.route = dispatch.route;
|
|
344
315
|
}
|
|
345
316
|
};
|
|
346
317
|
|
|
@@ -348,7 +319,7 @@ var BaseHandler = class {
|
|
|
348
319
|
//#region src/handlers/RepliableHandler.ts
|
|
349
320
|
/**
|
|
350
321
|
* Shared base the repliable handlers on both transports extend. Defines the reply members over one sender.
|
|
351
|
-
*
|
|
322
|
+
* The dispatch run builds that sender and passes it to the handler and each middleware.
|
|
352
323
|
*
|
|
353
324
|
* @typeParam Event - The repliable interaction type this handler processes
|
|
354
325
|
* @typeParam TCore - The transport's Core
|
|
@@ -361,7 +332,7 @@ var RepliableHandler = class extends BaseHandler {
|
|
|
361
332
|
sender;
|
|
362
333
|
constructor(event, core, dispatch, sender) {
|
|
363
334
|
super(event, core, dispatch, "interactions");
|
|
364
|
-
this.sender = sender
|
|
335
|
+
this.sender = sender;
|
|
365
336
|
}
|
|
366
337
|
/** Send the initial response, exactly once. */
|
|
367
338
|
reply(response, opts) {
|
|
@@ -557,44 +528,88 @@ var BaseReplySender = class {
|
|
|
557
528
|
};
|
|
558
529
|
|
|
559
530
|
//#endregion
|
|
560
|
-
//#region src/
|
|
531
|
+
//#region src/gates/Gate.ts
|
|
561
532
|
/**
|
|
562
|
-
*
|
|
533
|
+
* Builds a {@link Gate} from a check. The check refuses by throwing a Notice or a Silence and passes
|
|
534
|
+
* by returning. The required context is inferred from `ctx`, so annotate it as narrowly as the fields
|
|
535
|
+
* the check reads. No annotation defaults to {@link GateContextBase}, an agnostic gate that fits every
|
|
536
|
+
* handler.
|
|
537
|
+
*
|
|
538
|
+
* @typeParam Name - The gate's name, captured as a literal for mismatch errors.
|
|
539
|
+
* @typeParam Ctx - The context the check reads, inferred from the `ctx` annotation.
|
|
563
540
|
*
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
*
|
|
541
|
+
* @param name - The gate's name, used in mismatch errors.
|
|
542
|
+
* @param fn - The check, which refuses by throwing a Notice or a Silence and passes by returning.
|
|
543
|
+
*
|
|
544
|
+
* @see {@link defineEffectGate}
|
|
545
|
+
* @see the `@Gated` decorator from your transport package
|
|
569
546
|
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```ts
|
|
549
|
+
* // a gate factory that takes its own option and closes over it
|
|
550
|
+
* function MinRoles(min: number): Gate<GateContextBase, 'MinRoles'> {
|
|
551
|
+
* return defineGate('MinRoles', (ctx) => {
|
|
552
|
+
* if (ctx.memberRoleIds.length < min) throw new NotEnoughRoles(min);
|
|
553
|
+
* });
|
|
554
|
+
* }
|
|
555
|
+
* ```
|
|
572
556
|
*
|
|
573
557
|
* @example
|
|
574
558
|
* ```ts
|
|
575
|
-
*
|
|
559
|
+
* // agnostic gate (no ctx annotation): fits any handler
|
|
560
|
+
* const Owner = defineGate('owner', (ctx) => {
|
|
561
|
+
* if (!ctx.userId) throw new NotOwner();
|
|
562
|
+
* });
|
|
563
|
+
* ```
|
|
564
|
+
*/
|
|
565
|
+
function defineGate(name, fn) {
|
|
566
|
+
return {
|
|
567
|
+
name,
|
|
568
|
+
check: async (ctx) => fn(ctx)
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Builds an {@link EffectGate} from a `check` and a `commit`. `check` peeks and refuses by throwing, and
|
|
573
|
+
* `commit` applies the side effect, running only once the whole gate set passes so a later refusal never
|
|
574
|
+
* commits. In an `or`, seedcord rolls back the commit a refusing gate queued. This is how {@link Cooldown} peeks
|
|
575
|
+
* in `check` and charges the slot in `commit`.
|
|
576
|
+
*
|
|
577
|
+
* @typeParam Name - The gate's literal name, used to name a mismatch error.
|
|
578
|
+
* @typeParam Ctx - The context both `check` and `commit` read, inferred from the `ctx` annotation.
|
|
579
|
+
*
|
|
580
|
+
* @param name - The gate's name, used in mismatch errors.
|
|
581
|
+
* @param check - Peeks and refuses by throwing, without applying the side effect.
|
|
582
|
+
* @param commit - Applies the side effect, running only after the whole gate set passes.
|
|
576
583
|
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* // user sees the generic reply with the uuid, and the real error rides along as cause for the webhook
|
|
581
|
-
* throw new Fault({ cause });
|
|
584
|
+
* @see {@link defineGate}
|
|
585
|
+
* @see the `@Gated` decorator from your transport package
|
|
586
|
+
* @see {@link Cooldown}
|
|
582
587
|
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```ts
|
|
590
|
+
* // an effect-gate factory that takes its own limit and closes over it
|
|
591
|
+
* function UsesPerDay(max: number): EffectGate<GateContextBase, 'UsesPerDay'> {
|
|
592
|
+
* return defineEffectGate(
|
|
593
|
+
* 'UsesPerDay',
|
|
594
|
+
* (ctx) => {
|
|
595
|
+
* // peek and refuse by throwing. do not mutate yet
|
|
596
|
+
* if (usedToday(ctx.userId) >= max) throw new OutOfUsesNotice(max);
|
|
597
|
+
* },
|
|
598
|
+
* (ctx) => {
|
|
599
|
+
* // runs only after the whole gate set passes
|
|
600
|
+
* recordUse(ctx.userId);
|
|
601
|
+
* }
|
|
602
|
+
* );
|
|
585
603
|
* }
|
|
586
604
|
* ```
|
|
587
605
|
*/
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
return { components: [new NoticeCard(`Something went wrong. Please reach out to ${contact} with a way to reproduce the error and the following:\n### UUID: \`${ctx.uuid}\``, "Error").component] };
|
|
596
|
-
}
|
|
597
|
-
};
|
|
606
|
+
function defineEffectGate(name, check, commit) {
|
|
607
|
+
return {
|
|
608
|
+
name,
|
|
609
|
+
check: async (ctx) => check(ctx),
|
|
610
|
+
commit: async (ctx) => commit(ctx)
|
|
611
|
+
};
|
|
612
|
+
}
|
|
598
613
|
|
|
599
614
|
//#endregion
|
|
600
615
|
//#region src/gates/combinators.ts
|
|
@@ -643,9 +658,9 @@ function or(...args) {
|
|
|
643
658
|
/**
|
|
644
659
|
* Passes only for a user id listed in `config.ownerIds`, else refuses.
|
|
645
660
|
*
|
|
646
|
-
* Agnostic, so it attaches to interaction and event handlers alike. With no `ownerIds` configured it refuses every caller. Pass
|
|
661
|
+
* Agnostic, so it attaches to interaction and event handlers alike. With no `ownerIds` configured it refuses every caller. Pass a Notice class as `notice` to replace the refusal.
|
|
647
662
|
*
|
|
648
|
-
* @param options -
|
|
663
|
+
* @param options - Replace the refusal with a Notice class as `notice`.
|
|
649
664
|
*
|
|
650
665
|
* @see the `@Gated` decorator from your transport package
|
|
651
666
|
*
|
|
@@ -662,15 +677,15 @@ function or(...args) {
|
|
|
662
677
|
*
|
|
663
678
|
* @example
|
|
664
679
|
* ```ts
|
|
665
|
-
* //
|
|
666
|
-
* OwnerOnly({
|
|
680
|
+
* // OwnersOnly is your own Notice class
|
|
681
|
+
* OwnerOnly({ notice: OwnersOnly });
|
|
667
682
|
* ```
|
|
668
683
|
*/
|
|
669
684
|
function OwnerOnly(options) {
|
|
670
685
|
return defineGate("OwnerOnly", (ctx) => {
|
|
671
686
|
const owners = ctx.core.config.ownerIds ?? [];
|
|
672
687
|
if (ctx.userId !== null && owners.includes(ctx.userId)) return;
|
|
673
|
-
throw
|
|
688
|
+
throw new ((options?.notice) ?? NotOwner)();
|
|
674
689
|
});
|
|
675
690
|
}
|
|
676
691
|
/**
|
|
@@ -678,7 +693,7 @@ function OwnerOnly(options) {
|
|
|
678
693
|
*
|
|
679
694
|
* Agnostic. Attaches to any handler kind. Often paired with `RequirePermissions` in an {@link and}.
|
|
680
695
|
*
|
|
681
|
-
* @param options -
|
|
696
|
+
* @param options - Replace the refusal with a Notice class as `notice`.
|
|
682
697
|
*
|
|
683
698
|
* @see the `@Gated` decorator from your transport package
|
|
684
699
|
*
|
|
@@ -698,7 +713,7 @@ function OwnerOnly(options) {
|
|
|
698
713
|
function GuildOnly(options) {
|
|
699
714
|
return defineGate("GuildOnly", (ctx) => {
|
|
700
715
|
if (ctx.guildId) return;
|
|
701
|
-
throw
|
|
716
|
+
throw new ((options?.notice) ?? NotInGuild)();
|
|
702
717
|
});
|
|
703
718
|
}
|
|
704
719
|
/**
|
|
@@ -706,7 +721,7 @@ function GuildOnly(options) {
|
|
|
706
721
|
*
|
|
707
722
|
* Agnostic, fitting any handler kind. The inverse of {@link GuildOnly}, so combining the two in an {@link and} can never pass.
|
|
708
723
|
*
|
|
709
|
-
* @param options -
|
|
724
|
+
* @param options - Replace the refusal with a Notice class as `notice`.
|
|
710
725
|
*
|
|
711
726
|
* @see the `@Gated` decorator from your transport package
|
|
712
727
|
*
|
|
@@ -724,7 +739,7 @@ function GuildOnly(options) {
|
|
|
724
739
|
function DmOnly(options) {
|
|
725
740
|
return defineGate("DmOnly", (ctx) => {
|
|
726
741
|
if (!ctx.guildId) return;
|
|
727
|
-
throw
|
|
742
|
+
throw new ((options?.notice) ?? NotInDm)();
|
|
728
743
|
});
|
|
729
744
|
}
|
|
730
745
|
|
|
@@ -740,12 +755,12 @@ function scopeValue(ctx, per) {
|
|
|
740
755
|
* Allows `limit` uses per window, scoped by `per`. A number `duration` is **seconds**, and a string is
|
|
741
756
|
* a duration like `30m` or `24h`. The slot is charged in commit, only after the whole gate set passes,
|
|
742
757
|
* so a later refusal never burns the cooldown.
|
|
743
|
-
*
|
|
758
|
+
* If two requests in one process pass the check together, the commit refuses the one that goes over `limit`.
|
|
744
759
|
* The key combines the handler's route with the window settings, keeping two different handlers on separate
|
|
745
|
-
* windows.
|
|
760
|
+
* windows. Replace the refusal with {@link CooldownOptions.notice}.
|
|
746
761
|
*
|
|
747
762
|
* @param duration - A number is seconds, and a string is a duration like `30m` or `24h`.
|
|
748
|
-
* @param options - Sets the scope with `per`, the uses per window with `limit`, and the refusal
|
|
763
|
+
* @param options - Sets the scope with `per`, the uses per window with `limit`, and the refusal with `notice`.
|
|
749
764
|
* @throws A **SeedcordTypeError** when `duration` is a string that doesn't parse.
|
|
750
765
|
* @throws A **SeedcordRangeError** when `duration` is zero or negative.
|
|
751
766
|
*
|
|
@@ -797,22 +812,47 @@ function Cooldown(duration, options) {
|
|
|
797
812
|
limit: options.limit
|
|
798
813
|
};
|
|
799
814
|
const anonId = anonSeq++;
|
|
800
|
-
const keyOf = (ctx) => `cooldown:${ctx.declaredRoute ?? `anon${anonId}`}:${per}:w${windowMs}:l${options?.limit ?? 1}:${scopeValue(ctx, per)}`;
|
|
815
|
+
const keyOf = (ctx) => `cooldown:${ctx.dispatch.declaredRoute ?? `anon${anonId}`}:${per}:w${windowMs}:l${options?.limit ?? 1}:${scopeValue(ctx, per)}`;
|
|
816
|
+
const Refusal = options?.notice ?? OnCooldown;
|
|
801
817
|
return defineEffectGate("Cooldown", async (ctx) => {
|
|
802
818
|
const result = await ctx.core.rateLimiter.peek(keyOf(ctx), window);
|
|
803
|
-
if (
|
|
804
|
-
if (options?.notice) throw options.notice(result.resetAt);
|
|
805
|
-
throw new OnCooldown(result.resetAt, options?.message?.(result.resetAt));
|
|
819
|
+
if (result.limited) throw new Refusal(result.resetAt);
|
|
806
820
|
}, async (ctx) => {
|
|
807
|
-
await ctx.core.rateLimiter.charge(keyOf(ctx), window);
|
|
821
|
+
const result = await ctx.core.rateLimiter.charge(keyOf(ctx), window);
|
|
822
|
+
if (result.limited) throw new Refusal(result.resetAt);
|
|
808
823
|
});
|
|
809
824
|
}
|
|
810
825
|
|
|
826
|
+
//#endregion
|
|
827
|
+
//#region src/permissions/bits.ts
|
|
828
|
+
const PermissionNames = new Map(Object.entries(PermissionFlagsBits).map(([key, bit]) => [bit, prettify(key)]));
|
|
829
|
+
const ADMINISTRATOR = PermissionFlagsBits.Administrator;
|
|
830
|
+
function nameOf(bit) {
|
|
831
|
+
return PermissionNames.get(bit) ?? String(bit);
|
|
832
|
+
}
|
|
833
|
+
function holds(held, bit) {
|
|
834
|
+
return (held & ADMINISTRATOR) === ADMINISTRATOR || (held & bit) === bit;
|
|
835
|
+
}
|
|
836
|
+
function namesWhere(held, scope, isHeld) {
|
|
837
|
+
return scope.reduce((names, bit) => {
|
|
838
|
+
if (holds(held, bit) === isHeld) names.push(nameOf(bit));
|
|
839
|
+
return names;
|
|
840
|
+
}, []);
|
|
841
|
+
}
|
|
842
|
+
/** @internal */
|
|
843
|
+
function missingNames(held, scope) {
|
|
844
|
+
return namesWhere(held, scope, false);
|
|
845
|
+
}
|
|
846
|
+
/** @internal */
|
|
847
|
+
function presentNames(held, scope) {
|
|
848
|
+
return namesWhere(held, scope, true);
|
|
849
|
+
}
|
|
850
|
+
|
|
811
851
|
//#endregion
|
|
812
852
|
//#region src/permissions/assert.ts
|
|
813
853
|
/**
|
|
814
854
|
* Refuses when the subject is missing a permission in `scope`, or with `inverse` when it holds one.
|
|
815
|
-
* The Administrator bit passes any scope, and
|
|
855
|
+
* The Administrator bit passes any scope, and with `inverse` it reports every bit in `scope`.
|
|
816
856
|
*
|
|
817
857
|
* The caller computes `permissions`, gateway off a discord.js role, member, or channel, and HTTP off the
|
|
818
858
|
* bitfield the interaction payload carries.
|
|
@@ -857,14 +897,12 @@ function Cooldown(duration, options) {
|
|
|
857
897
|
* ```ts
|
|
858
898
|
* import { Notice } from '@seedcord/core';
|
|
859
899
|
*
|
|
860
|
-
* // a replacement notice takes the same three arguments the default one does
|
|
861
900
|
* class CannotBan extends Notice {
|
|
862
901
|
* public constructor(
|
|
863
|
-
*
|
|
864
|
-
* private readonly subject: string,
|
|
902
|
+
* private readonly subject: string | null,
|
|
865
903
|
* private readonly missing: readonly string[]
|
|
866
904
|
* ) {
|
|
867
|
-
* super(
|
|
905
|
+
* super('You cannot ban here.');
|
|
868
906
|
* }
|
|
869
907
|
*
|
|
870
908
|
* public render(): ReplyResponse {
|
|
@@ -876,19 +914,97 @@ function Cooldown(duration, options) {
|
|
|
876
914
|
* subject: `<@${member.id}>`,
|
|
877
915
|
* permissions: member.permissions.bitfield,
|
|
878
916
|
* scope: [PermissionFlagsBits.BanMembers],
|
|
879
|
-
*
|
|
917
|
+
* notice: CannotBan
|
|
880
918
|
* });
|
|
881
919
|
* ```
|
|
882
920
|
*/
|
|
883
|
-
function assertPermissions(
|
|
884
|
-
const
|
|
885
|
-
if (
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
921
|
+
function assertPermissions({ subject, permissions, scope, inverse = false, notice }) {
|
|
922
|
+
const [refused, Default] = inverse ? [presentNames(permissions, scope), HasPresentPermissions] : [missingNames(permissions, scope), MissingPermissions];
|
|
923
|
+
if (refused.length === 0) return;
|
|
924
|
+
throw new (notice ?? Default)(subject, refused);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region src/gates/catalog/permissions.ts
|
|
929
|
+
const BOT_SUBJECT = "The bot";
|
|
930
|
+
function requireHeld({ guildId, subject, readOrThrow }, scope, options) {
|
|
931
|
+
if (!guildId) throw new ((options?.notInGuild) ?? NotInGuild)();
|
|
932
|
+
const permissions = readOrThrow(guildId);
|
|
933
|
+
assertPermissions({
|
|
934
|
+
subject,
|
|
935
|
+
permissions,
|
|
936
|
+
scope,
|
|
937
|
+
notice: options?.missing
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function botMemberMissing(guildId) {
|
|
941
|
+
throw new SeedcordError(SeedcordErrorCode.CoreBotMemberMissing, [guildId]);
|
|
942
|
+
}
|
|
943
|
+
function RequirePermissions(scope, options) {
|
|
944
|
+
const resolved = (held) => {
|
|
945
|
+
if (held === null) throw new ((options?.unresolved) ?? PermissionsUnresolved)();
|
|
946
|
+
return held;
|
|
947
|
+
};
|
|
948
|
+
if (options?.in === "guild") return defineGate("RequirePermissions", (ctx) => {
|
|
949
|
+
const readOrThrow = () => resolved(ctx.memberGuildPermissions);
|
|
950
|
+
requireHeld({
|
|
951
|
+
guildId: ctx.guildId,
|
|
952
|
+
subject: null,
|
|
953
|
+
readOrThrow
|
|
954
|
+
}, scope, options);
|
|
955
|
+
});
|
|
956
|
+
return defineGate("RequirePermissions", (ctx) => {
|
|
957
|
+
const readOrThrow = () => resolved(ctx.memberPermissions);
|
|
958
|
+
requireHeld({
|
|
959
|
+
guildId: ctx.guildId,
|
|
960
|
+
subject: null,
|
|
961
|
+
readOrThrow
|
|
962
|
+
}, scope, options);
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
function RequireBotPermissions(scope, options) {
|
|
966
|
+
if (options?.in === "guild") return defineGate("RequireBotPermissions", (ctx) => {
|
|
967
|
+
const readOrThrow = (guildId) => ctx.appGuildPermissions ?? botMemberMissing(guildId);
|
|
968
|
+
requireHeld({
|
|
969
|
+
guildId: ctx.guildId,
|
|
970
|
+
subject: BOT_SUBJECT,
|
|
971
|
+
readOrThrow
|
|
972
|
+
}, scope, options);
|
|
973
|
+
});
|
|
974
|
+
return defineGate("RequireBotPermissions", (ctx) => {
|
|
975
|
+
requireHeld({
|
|
976
|
+
guildId: ctx.guildId,
|
|
977
|
+
subject: BOT_SUBJECT,
|
|
978
|
+
readOrThrow: () => ctx.appPermissions
|
|
979
|
+
}, scope, options);
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Requires the caller to hold `roleId`, read from the member's role ids. Refuses outside a guild first,
|
|
984
|
+
* then when the role is missing. Roles are guild-global, so there is no scope dimension.
|
|
985
|
+
*
|
|
986
|
+
* @param roleId - The role snowflake the caller must hold.
|
|
987
|
+
* @param options - Replace a refusal with a Notice class as `notInGuild` or `missingRole`.
|
|
988
|
+
*
|
|
989
|
+
* @see the `@Gated` decorator from your transport package
|
|
990
|
+
*
|
|
991
|
+
* @example
|
|
992
|
+
* ```ts
|
|
993
|
+
* \@Gated(RequireRole('123456789012345678'))
|
|
994
|
+
* \@SlashRoute('vip')
|
|
995
|
+
* class VipHandler extends SlashHandler<'vip'> {
|
|
996
|
+
* async execute() {
|
|
997
|
+
* // ...
|
|
998
|
+
* }
|
|
999
|
+
* }
|
|
1000
|
+
* ```
|
|
1001
|
+
*/
|
|
1002
|
+
function RequireRole(roleId, options) {
|
|
1003
|
+
return defineGate("RequireRole", (ctx) => {
|
|
1004
|
+
if (!ctx.guildId) throw new ((options?.notInGuild) ?? NotInGuild)();
|
|
1005
|
+
if (ctx.memberRoleIds.includes(roleId)) return;
|
|
1006
|
+
throw new ((options?.missingRole) ?? MissingRole)(roleId);
|
|
1007
|
+
});
|
|
892
1008
|
}
|
|
893
1009
|
|
|
894
1010
|
//#endregion
|
|
@@ -1081,6 +1197,9 @@ var PaginatorBase = class {
|
|
|
1081
1197
|
this.cursor = pageCursor(config.prefix);
|
|
1082
1198
|
this.config = config;
|
|
1083
1199
|
}
|
|
1200
|
+
get ephemeral() {
|
|
1201
|
+
return this.config.ephemeral ?? true;
|
|
1202
|
+
}
|
|
1084
1203
|
/** Build one page as a {@link ReplyResponse}. Each transport wraps this in a `page()` taking its handler. */
|
|
1085
1204
|
async buildPage(ctx, n) {
|
|
1086
1205
|
const view = await this.config.source.page(ctx, n);
|
|
@@ -1255,8 +1374,8 @@ function buildKey(prefix, ...parts) {
|
|
|
1255
1374
|
//#endregion
|
|
1256
1375
|
//#region src/index.ts
|
|
1257
1376
|
/** Package version */
|
|
1258
|
-
const version = "0.12.0-next.
|
|
1377
|
+
const version = "0.12.0-next.3";
|
|
1259
1378
|
|
|
1260
1379
|
//#endregion
|
|
1261
|
-
export { ArraySourceBase, AutocompleteRoute, BaseHandler, BaseReplySender, BuilderComponent, Bus, ButtonRoute, ChannelMenuRoute, Commands, ContextMenus, Cooldown, CursorSourceBase, CustomId,
|
|
1380
|
+
export { ArraySourceBase, AutocompleteRoute, BaseHandler, BaseReplySender, BuilderComponent, Bus, ButtonRoute, ChannelMenuRoute, Commands, ContextMenus, Cooldown, CursorSourceBase, CustomId, DmOnly, Fault, GuildOnly, InteractionKind, KeyvDriver, MemoryDriver, MentionableMenuRoute, MessageContextMenuRoute, ModalRoute, Notice, OwnerOnly, PaginatorBase, RegisterCommand, RegisterInteractionMiddleware, RepliableHandler, RequireBotPermissions, RequirePermissions, RequireRole, ResolvedEmoji, RoleMenuRoute, RowComponent, ShutdownPhase, Silence, SlashRoute, StartupPhase, StringMenuRoute, Subscribe, Subscriber, UnstorageDriver, UserContextMenuRoute, UserMenuRoute, WebhookLog, WebhookUrl, and, assertPermissions, buildKey, decodeFor, defineEffectGate, defineGate, mergeRoles, or, paginate, prefixOf, setCustomIdErrors, version };
|
|
1262
1381
|
//# sourceMappingURL=index.mjs.map
|