@seedcord/core 0.12.0-next.0 → 0.12.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +216 -71
- package/dist/index.mjs +215 -100
- 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
|
|
|
@@ -742,10 +757,10 @@ function scopeValue(ctx, per) {
|
|
|
742
757
|
* so a later refusal never burns the cooldown.
|
|
743
758
|
* That split also means two requests racing the same key can both pass before either charges the slot.
|
|
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,46 @@ 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)}`;
|
|
801
816
|
return defineEffectGate("Cooldown", async (ctx) => {
|
|
802
817
|
const result = await ctx.core.rateLimiter.peek(keyOf(ctx), window);
|
|
803
818
|
if (!result.limited) return;
|
|
804
|
-
|
|
805
|
-
throw new OnCooldown(result.resetAt, options?.message?.(result.resetAt));
|
|
819
|
+
throw new ((options?.notice) ?? OnCooldown)(result.resetAt);
|
|
806
820
|
}, async (ctx) => {
|
|
807
821
|
await ctx.core.rateLimiter.charge(keyOf(ctx), window);
|
|
808
822
|
});
|
|
809
823
|
}
|
|
810
824
|
|
|
825
|
+
//#endregion
|
|
826
|
+
//#region src/permissions/bits.ts
|
|
827
|
+
const PermissionNames = new Map(Object.entries(PermissionFlagsBits).map(([key, bit]) => [bit, prettify(key)]));
|
|
828
|
+
const ADMINISTRATOR = PermissionFlagsBits.Administrator;
|
|
829
|
+
function nameOf(bit) {
|
|
830
|
+
return PermissionNames.get(bit) ?? String(bit);
|
|
831
|
+
}
|
|
832
|
+
function holds(held, bit) {
|
|
833
|
+
return (held & ADMINISTRATOR) === ADMINISTRATOR || (held & bit) === bit;
|
|
834
|
+
}
|
|
835
|
+
function namesWhere(held, scope, isHeld) {
|
|
836
|
+
return scope.reduce((names, bit) => {
|
|
837
|
+
if (holds(held, bit) === isHeld) names.push(nameOf(bit));
|
|
838
|
+
return names;
|
|
839
|
+
}, []);
|
|
840
|
+
}
|
|
841
|
+
/** @internal */
|
|
842
|
+
function missingNames(held, scope) {
|
|
843
|
+
return namesWhere(held, scope, false);
|
|
844
|
+
}
|
|
845
|
+
/** @internal */
|
|
846
|
+
function presentNames(held, scope) {
|
|
847
|
+
return namesWhere(held, scope, true);
|
|
848
|
+
}
|
|
849
|
+
|
|
811
850
|
//#endregion
|
|
812
851
|
//#region src/permissions/assert.ts
|
|
813
852
|
/**
|
|
814
853
|
* 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
|
|
854
|
+
* The Administrator bit passes any scope, and with `inverse` it reports every bit in `scope`.
|
|
816
855
|
*
|
|
817
856
|
* The caller computes `permissions`, gateway off a discord.js role, member, or channel, and HTTP off the
|
|
818
857
|
* bitfield the interaction payload carries.
|
|
@@ -857,14 +896,12 @@ function Cooldown(duration, options) {
|
|
|
857
896
|
* ```ts
|
|
858
897
|
* import { Notice } from '@seedcord/core';
|
|
859
898
|
*
|
|
860
|
-
* // a replacement notice takes the same three arguments the default one does
|
|
861
899
|
* class CannotBan extends Notice {
|
|
862
900
|
* public constructor(
|
|
863
|
-
*
|
|
864
|
-
* private readonly subject: string,
|
|
901
|
+
* private readonly subject: string | null,
|
|
865
902
|
* private readonly missing: readonly string[]
|
|
866
903
|
* ) {
|
|
867
|
-
* super(
|
|
904
|
+
* super('You cannot ban here.');
|
|
868
905
|
* }
|
|
869
906
|
*
|
|
870
907
|
* public render(): ReplyResponse {
|
|
@@ -876,19 +913,97 @@ function Cooldown(duration, options) {
|
|
|
876
913
|
* subject: `<@${member.id}>`,
|
|
877
914
|
* permissions: member.permissions.bitfield,
|
|
878
915
|
* scope: [PermissionFlagsBits.BanMembers],
|
|
879
|
-
*
|
|
916
|
+
* notice: CannotBan
|
|
880
917
|
* });
|
|
881
918
|
* ```
|
|
882
919
|
*/
|
|
883
|
-
function assertPermissions(
|
|
884
|
-
const
|
|
885
|
-
if (
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
920
|
+
function assertPermissions({ subject, permissions, scope, inverse = false, notice }) {
|
|
921
|
+
const [refused, Default] = inverse ? [presentNames(permissions, scope), HasPresentPermissions] : [missingNames(permissions, scope), MissingPermissions];
|
|
922
|
+
if (refused.length === 0) return;
|
|
923
|
+
throw new (notice ?? Default)(subject, refused);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
//#endregion
|
|
927
|
+
//#region src/gates/catalog/permissions.ts
|
|
928
|
+
const BOT_SUBJECT = "The bot";
|
|
929
|
+
function requireHeld({ guildId, subject, readOrThrow }, scope, options) {
|
|
930
|
+
if (!guildId) throw new ((options?.notInGuild) ?? NotInGuild)();
|
|
931
|
+
const permissions = readOrThrow(guildId);
|
|
932
|
+
assertPermissions({
|
|
933
|
+
subject,
|
|
934
|
+
permissions,
|
|
935
|
+
scope,
|
|
936
|
+
notice: options?.missing
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
function botMemberMissing(guildId) {
|
|
940
|
+
throw new SeedcordError(SeedcordErrorCode.CoreBotMemberMissing, [guildId]);
|
|
941
|
+
}
|
|
942
|
+
function RequirePermissions(scope, options) {
|
|
943
|
+
const resolved = (held) => {
|
|
944
|
+
if (held === null) throw new ((options?.unresolved) ?? PermissionsUnresolved)();
|
|
945
|
+
return held;
|
|
946
|
+
};
|
|
947
|
+
if (options?.in === "guild") return defineGate("RequirePermissions", (ctx) => {
|
|
948
|
+
const readOrThrow = () => resolved(ctx.memberGuildPermissions);
|
|
949
|
+
requireHeld({
|
|
950
|
+
guildId: ctx.guildId,
|
|
951
|
+
subject: null,
|
|
952
|
+
readOrThrow
|
|
953
|
+
}, scope, options);
|
|
954
|
+
});
|
|
955
|
+
return defineGate("RequirePermissions", (ctx) => {
|
|
956
|
+
const readOrThrow = () => resolved(ctx.memberPermissions);
|
|
957
|
+
requireHeld({
|
|
958
|
+
guildId: ctx.guildId,
|
|
959
|
+
subject: null,
|
|
960
|
+
readOrThrow
|
|
961
|
+
}, scope, options);
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
function RequireBotPermissions(scope, options) {
|
|
965
|
+
if (options?.in === "guild") return defineGate("RequireBotPermissions", (ctx) => {
|
|
966
|
+
const readOrThrow = (guildId) => ctx.appGuildPermissions ?? botMemberMissing(guildId);
|
|
967
|
+
requireHeld({
|
|
968
|
+
guildId: ctx.guildId,
|
|
969
|
+
subject: BOT_SUBJECT,
|
|
970
|
+
readOrThrow
|
|
971
|
+
}, scope, options);
|
|
972
|
+
});
|
|
973
|
+
return defineGate("RequireBotPermissions", (ctx) => {
|
|
974
|
+
requireHeld({
|
|
975
|
+
guildId: ctx.guildId,
|
|
976
|
+
subject: BOT_SUBJECT,
|
|
977
|
+
readOrThrow: () => ctx.appPermissions
|
|
978
|
+
}, scope, options);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Requires the caller to hold `roleId`, read from the member's role ids. Refuses outside a guild first,
|
|
983
|
+
* then when the role is missing. Roles are guild-global, so there is no scope dimension.
|
|
984
|
+
*
|
|
985
|
+
* @param roleId - The role snowflake the caller must hold.
|
|
986
|
+
* @param options - Replace a refusal with a Notice class as `notInGuild` or `missingRole`.
|
|
987
|
+
*
|
|
988
|
+
* @see the `@Gated` decorator from your transport package
|
|
989
|
+
*
|
|
990
|
+
* @example
|
|
991
|
+
* ```ts
|
|
992
|
+
* \@Gated(RequireRole('123456789012345678'))
|
|
993
|
+
* \@SlashRoute('vip')
|
|
994
|
+
* class VipHandler extends SlashHandler<'vip'> {
|
|
995
|
+
* async execute() {
|
|
996
|
+
* // ...
|
|
997
|
+
* }
|
|
998
|
+
* }
|
|
999
|
+
* ```
|
|
1000
|
+
*/
|
|
1001
|
+
function RequireRole(roleId, options) {
|
|
1002
|
+
return defineGate("RequireRole", (ctx) => {
|
|
1003
|
+
if (!ctx.guildId) throw new ((options?.notInGuild) ?? NotInGuild)();
|
|
1004
|
+
if (ctx.memberRoleIds.includes(roleId)) return;
|
|
1005
|
+
throw new ((options?.missingRole) ?? MissingRole)(roleId);
|
|
1006
|
+
});
|
|
892
1007
|
}
|
|
893
1008
|
|
|
894
1009
|
//#endregion
|
|
@@ -1255,8 +1370,8 @@ function buildKey(prefix, ...parts) {
|
|
|
1255
1370
|
//#endregion
|
|
1256
1371
|
//#region src/index.ts
|
|
1257
1372
|
/** Package version */
|
|
1258
|
-
const version = "0.12.0-next.
|
|
1373
|
+
const version = "0.12.0-next.2";
|
|
1259
1374
|
|
|
1260
1375
|
//#endregion
|
|
1261
|
-
export { ArraySourceBase, AutocompleteRoute, BaseHandler, BaseReplySender, BuilderComponent, Bus, ButtonRoute, ChannelMenuRoute, Commands, ContextMenus, Cooldown, CursorSourceBase, CustomId,
|
|
1376
|
+
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
1377
|
//# sourceMappingURL=index.mjs.map
|