@seedcord/core 0.1.0-next.4 → 0.1.0-next.5

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.
@@ -1,7 +1,37 @@
1
+ import { t as IsNever } from "./is-never-Ddaba1Su.mjs";
2
+ import { APIMessageTopLevelComponent, APIModalInteractionResponseCallbackData, ApplicationCommandType, ChannelType, Snowflake } from "discord-api-types/v10";
1
3
  import { ActionRowBuilder, ButtonBuilder, ChannelSelectMenuBuilder, CheckboxBuilder, CheckboxGroupBuilder, CheckboxGroupOptionBuilder, ContainerBuilder, ContextMenuCommandBuilder, EmbedBuilder, FileBuilder, FileUploadBuilder, LabelBuilder, MediaGalleryBuilder, MentionableSelectMenuBuilder, ModalBuilder, RadioGroupBuilder, RadioGroupOptionBuilder, RoleSelectMenuBuilder, SectionBuilder, SeparatorBuilder, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextDisplayBuilder, TextInputBuilder, UserSelectMenuBuilder } from "@discordjs/builders";
2
- import { Snowflake } from "discord-api-types/v10";
3
- import { Config, IRateLimiter, RenderContext, ReplyResponse } from "@seedcord/types";
4
+ import { Config, DeferOpts, IRateLimiter, RenderContext, ReplyResponse, SendOpts } from "@seedcord/types";
4
5
 
6
+ //#region src/registries/SlashOptionRegistry.d.ts
7
+ /** A slash option kind as a plain string union (`string`, `channel`, ...), keyed to the registry. */
8
+ type OptionKind = 'string' | 'integer' | 'number' | 'boolean' | 'user' | 'channel' | 'role' | 'mentionable' | 'attachment';
9
+ /** The type-relevant shape of one slash option, as emitted by `seedcord codegen` into a registry row. */
10
+ interface SlashOption {
11
+ kind: OptionKind;
12
+ required: boolean;
13
+ choices?: readonly (string | number)[];
14
+ autocomplete?: true;
15
+ channelTypes?: readonly ChannelType[];
16
+ }
17
+ /**
18
+ * Maps each slash route string to its option table. Populated by `seedcord codegen`, which reads every
19
+ * command's `toJSON()` and emits a `declare module '@seedcord/gateway'` augmentation, so `keyof SlashOptionRegistry`
20
+ * is the compile-time union of every registered route. Do not augment it by hand, run `seedcord codegen`.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // seedcord-gen.d.ts (emitted, committed)
25
+ * declare module '@seedcord/gateway' {
26
+ * interface SlashOptionRegistry {
27
+ * ban: { target: { kind: 'user'; required: true }; reason: { kind: 'string'; required: false } };
28
+ * 'demo/setup': { channel: { kind: 'channel'; required: true } };
29
+ * }
30
+ * }
31
+ * ```
32
+ */
33
+ interface SlashOptionRegistry {}
34
+ //#endregion
5
35
  //#region src/components/builderTypes.d.ts
6
36
  /**
7
37
  * Available Discord.js builder classes for use with BuilderComponent for commands, embeds, modals, etc.
@@ -189,6 +219,214 @@ declare function RegisterCommand(scope: 'global'): (ctor: CommandCtor) => void;
189
219
  */
190
220
  declare function RegisterCommand(scope: 'guild', guilds: string[]): (ctor: CommandCtor) => void;
191
221
  //#endregion
222
+ //#region src/metadataKeys.d.ts
223
+ declare enum InteractionRoutes {
224
+ Slash = "slash",
225
+ Button = "button",
226
+ Modal = "modal",
227
+ StringMenu = "stringMenu",
228
+ UserMenu = "userMenu",
229
+ RoleMenu = "roleMenu",
230
+ ChannelMenu = "channelMenu",
231
+ MentionableMenu = "mentionableMenu",
232
+ MessageContextMenu = "messageContextMenu",
233
+ UserContextMenu = "userContextMenu",
234
+ Autocomplete = "autocomplete"
235
+ }
236
+ declare const InteractionMetadataKey: unique symbol;
237
+ declare const InteractionRouteKeys: Record<InteractionRoutes, symbol>;
238
+ declare const MiddlewareMetadataKey: unique symbol;
239
+ declare const EventMetadataKey: unique symbol;
240
+ declare const CommandMetadataKey: unique symbol;
241
+ declare const GatedMetadataKey: unique symbol;
242
+ declare const SubscribeMetadataKey: unique symbol;
243
+ declare const WebhookUrlMetadataKey: unique symbol;
244
+ //#endregion
245
+ //#region src/customId/Field.d.ts
246
+ /**
247
+ * One field in a customId shape, its wire kind plus the type it decodes to.
248
+ *
249
+ * @internal
250
+ */
251
+ interface CustomIdField<Decoded> {
252
+ /** Which wire encoding this field uses. */
253
+ readonly kind: 'snowflake' | 'uuid' | 'int' | 'bool' | 'oneOf' | 'string';
254
+ /** Lower bound, for a bounded int field. */
255
+ readonly min?: number;
256
+ /** Upper bound, for a bounded int field. */
257
+ readonly max?: number;
258
+ /** The allowed values, for a oneOf field. */
259
+ readonly choices?: readonly string[];
260
+ /** Phantom only, carries the decoded type and is never set at runtime. */
261
+ readonly decoded?: Decoded;
262
+ }
263
+ /**
264
+ * The set of fields a customId carries, keyed by name.
265
+ *
266
+ * @internal
267
+ */
268
+ type CustomIdShape = Record<string, CustomIdField<unknown>>;
269
+ /**
270
+ * The decoded result, each field name mapped to its decoded type.
271
+ *
272
+ * @internal
273
+ */
274
+ type DecodedParams<Shape extends CustomIdShape> = { [Name in keyof Shape]: Shape[Name] extends CustomIdField<infer Decoded> ? Decoded : never };
275
+ //#endregion
276
+ //#region src/customId/CustomId.d.ts
277
+ /** Strip the layout hash off the routeKey to recover the stable prefix the controller routes by. @internal */
278
+ declare function prefixOf(wire: string): string;
279
+ /**
280
+ * A typed customId. The single source of truth shared by the component that mints it and the handler
281
+ * that reads it. This gives you typed reads on the `.customId` field in components. Values are packed into a compact wire string rather than plain stringified tokens, so the 100-char Discord limit goes further. More string per string, basically.
282
+ *
283
+ * @typeParam Prefix - The stable route prefix, e.g. 'approve'.
284
+ * @typeParam Shape - The accumulated fields, filled in by the chain.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * const ApproveId = new CustomId('approve')
289
+ * .snowflake('userId')
290
+ * .oneOf('action', ['approve', 'deny']);
291
+ *
292
+ * // Set the custom id on a button when creating it.
293
+ * new ButtonBuilder().setCustomId(ApproveId.encode({ userId: '123', action: 'approve' }));
294
+ *
295
+ * // reading in the handler: userId comes back a string
296
+ * const { userId, action } = this.params; // userId: string, action: 'approve' | 'deny'
297
+ * await this.event.guild?.members.fetch(userId);
298
+ * ```
299
+ */
300
+ declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}> {
301
+ /** The stable route prefix, e.g. 'approve'. */
302
+ readonly prefix: Prefix;
303
+ /** Like a zod shape */
304
+ readonly shape: Shape;
305
+ /** The prefix plus a short hash of the shape, the part of the wire before the colon. */
306
+ readonly routeKey: string;
307
+ constructor(prefix: Prefix, shape?: Shape);
308
+ private add;
309
+ /**
310
+ * Add a Discord ID field, decoded as a string (the `Snowflake` type from discord-api-types).
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * new CustomId('ban').snowflake('userId');
315
+ * ```
316
+ */
317
+ snowflake<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<Snowflake>>>;
318
+ /**
319
+ * Add a UUID field, decoded as a lowercase uuid string.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * new CustomId('ticket').uuid('ticketId');
324
+ * ```
325
+ */
326
+ uuid<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
327
+ /**
328
+ * Add an integer field with no bounds, for a value up to 2^53.
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * new CustomId('shop').int('amount');
333
+ * ```
334
+ */
335
+ int<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
336
+ /**
337
+ * Add an integer field bounded by min and max, so it packs into fewer characters on the wire.
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * new CustomId('paginate').int('page', 1, 50);
342
+ * ```
343
+ */
344
+ int<Name extends string>(name: Name, min: number, max: number): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
345
+ /**
346
+ * Add a boolean flag.
347
+ *
348
+ * @example
349
+ * ```ts
350
+ * new CustomId('settings').bool('silent');
351
+ * ```
352
+ */
353
+ bool<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<boolean>>>;
354
+ /**
355
+ * Add a field that is one value from a fixed list, decoded as the literal union. No `as const` needed.
356
+ *
357
+ * @example
358
+ * ```ts
359
+ * new CustomId('poll').oneOf('choice', ['yes', 'no', 'abstain']);
360
+ * ```
361
+ */
362
+ oneOf<Name extends string, const Choices extends NonEmptyTuple<string>>(name: Name, choices: Choices): CustomId<Prefix, Shape & Record<Name, CustomIdField<Choices[number]>>>;
363
+ /**
364
+ * Add a free short text field. Avoid it where possible, it cannot be packed so it costs the most wire space.
365
+ *
366
+ * @example
367
+ * ```ts
368
+ * new CustomId('note').str('message');
369
+ * ```
370
+ */
371
+ str<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
372
+ /**
373
+ * Mint a wire string from values. Throws if a value is out of its field's range or the wire is over 100 chars.
374
+ *
375
+ * @param values - One value per field, typed by the chain.
376
+ * @returns The wire string to put on the component's customId.
377
+ */
378
+ encode(values: DecodedParams<Shape>): string;
379
+ /**
380
+ * Read a wire string back into values.
381
+ *
382
+ * - This will refuse with a default Notice when the shape changed since the wire was minted.
383
+ * - Or refuse with a different default Notice on a corrupt or foreign wire.
384
+ *
385
+ * @param wire - The customId string from the interaction.
386
+ * @returns The decoded values, typed by the chain.
387
+ */
388
+ decode(wire: string): DecodedParams<Shape>;
389
+ /** True if this wire was minted from this customId's prefix, ignoring the shape hash. */
390
+ owns(wire: string): boolean;
391
+ }
392
+ /**
393
+ * Any customId, for places where the exact prefix and shape do not matter.
394
+ *
395
+ * @internal
396
+ */
397
+ type AnyCustomId = CustomId<string, CustomIdShape>;
398
+ /**
399
+ * The outcome of decoding a wire against several customIds, the matched prefix paired with its values.
400
+ *
401
+ * @internal
402
+ */
403
+ type DecodedRoute<Defs extends readonly AnyCustomId[]> = { [Index in keyof Defs]: Defs[Index] extends AnyCustomId ? {
404
+ readonly prefix: Defs[Index]['prefix'];
405
+ readonly params: DecodedParams<Defs[Index]['shape']>;
406
+ } : never }[number];
407
+ /**
408
+ * Find the customId whose prefix owns this wire, decode against it, and report which one matched.
409
+ *
410
+ * @internal
411
+ */
412
+ declare function decodeFor<Defs extends readonly AnyCustomId[]>(defs: Defs, wire: string): DecodedRoute<Defs>;
413
+ //#endregion
414
+ //#region src/decorators/interactionRoutes.d.ts
415
+ /** The select menu kinds the select route decorators take. */
416
+ declare enum SelectMenuKind {
417
+ String = "string",
418
+ User = "user",
419
+ Role = "role",
420
+ Channel = "channel",
421
+ Mentionable = "mentionable"
422
+ }
423
+ type RoutableConstructor = new (...args: never[]) => unknown;
424
+ declare function areRoutes(routes: unknown): routes is string[];
425
+ declare function storeInteractionRoute(route: InteractionRoutes, routes: string | readonly string[], constructor: RoutableConstructor): void;
426
+ declare function storeComponentRoute(route: InteractionRoutes, defs: readonly AnyCustomId[], constructor: RoutableConstructor): void;
427
+ declare function selectMenuRouteOf(kind: SelectMenuKind): InteractionRoutes;
428
+ declare function contextMenuRouteOf(kind: ApplicationCommandType.User | ApplicationCommandType.Message): InteractionRoutes;
429
+ //#endregion
192
430
  //#region src/interfaces/CoreBase.d.ts
193
431
  /**
194
432
  * The transport-agnostic slice of the running framework, what core code (gates, notices, plugins'
@@ -202,11 +440,90 @@ interface CoreBase {
202
440
  readonly rateLimiter: IRateLimiter;
203
441
  }
204
442
  //#endregion
443
+ //#region src/reply/AckTrace.d.ts
444
+ /** The first ack's stack, passed as the throw cause so a double-ack points to both lines. */
445
+ declare class AckTrace extends Error {
446
+ constructor(method: ReplyMethod);
447
+ }
448
+ //#endregion
449
+ //#region src/reply/ackLegality.d.ts
450
+ type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';
451
+ type ReplyMethod = 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';
452
+ /** Throws before any API call. `ackedBy` becomes the throw cause, its stack points at the first ack. */
453
+ declare function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void;
454
+ declare function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp';
455
+ //#endregion
456
+ //#region src/reply/serializeReply.d.ts
457
+ /** Serialized message data the transport wire writers consume. */
458
+ interface SerializedReply {
459
+ readonly components: APIMessageTopLevelComponent[];
460
+ readonly allowedMentions?: ReplyResponse['allowedMentions'];
461
+ readonly files?: ReplyResponse['files'];
462
+ }
463
+ /** Wraps a string in a TextDisplay component. */
464
+ declare function serializeReply(response: ReplyResponse | string, routeId: string): SerializedReply;
465
+ //#endregion
466
+ //#region src/reply/BaseReplySender.d.ts
467
+ /** The shape djs modal builders emit from `toJSON()`. */
468
+ interface ModalLike {
469
+ toJSON(): APIModalInteractionResponseCallbackData;
470
+ }
471
+ /**
472
+ * Tracks acknowledgement state and throws a translated `SeedcordError` on an illegal verb before any
473
+ * transport call. Verbs execute the legality check, control the transport wire writer, then update the
474
+ * state and ack trace. Each transport binds `TMessage` to its created-message lens and supplies the
475
+ * writers.
476
+ */
477
+ declare abstract class BaseReplySender<TMessage extends {
478
+ id: string;
479
+ }> {
480
+ protected readonly routeId: string;
481
+ private state;
482
+ private readonly sent;
483
+ private ackedBy?;
484
+ protected constructor(routeId: string, initialState?: AckState);
485
+ reply(response: ReplyResponse | string, opts?: SendOpts): Promise<TMessage>;
486
+ defer(opts?: DeferOpts): Promise<void>;
487
+ deferUpdate(): Promise<void>;
488
+ /**
489
+ * After a deferUpdate the state stays deferred-update, so the rewrite repeats. The returned message
490
+ * is the source message, editable only through a bare `update` or `edit`. A targeted `edit` or `delete`
491
+ * of it throws the foreign-target error.
492
+ */
493
+ update(response: ReplyResponse | string): Promise<TMessage>;
494
+ followUp(response: ReplyResponse | string, opts?: SendOpts): Promise<TMessage>;
495
+ edit(response: ReplyResponse | string): Promise<TMessage>;
496
+ edit(target: TMessage, response: ReplyResponse | string): Promise<TMessage>;
497
+ delete(): Promise<void>;
498
+ delete(target: TMessage): Promise<void>;
499
+ /** Routes to the verb the current ack state permits. Every state has a route, so the illegal-ack throw is unreachable. */
500
+ send(response: ReplyResponse | string, opts?: SendOpts): Promise<TMessage>;
501
+ /** Must be the initial response. */
502
+ showModal(modal: ModalLike): Promise<void>;
503
+ protected serialize(response: ReplyResponse | string): SerializedReply;
504
+ protected remember(created: TMessage): TMessage;
505
+ private requireMessage;
506
+ private editOriginal;
507
+ private checkLegality;
508
+ private transition;
509
+ protected guardModalSource(): void;
510
+ protected abstract writeReply(response: ReplyResponse | string, opts?: SendOpts): Promise<TMessage | undefined>;
511
+ protected abstract writeDefer(opts?: DeferOpts): Promise<void>;
512
+ protected abstract writeDeferUpdate(): Promise<void>;
513
+ protected abstract writeUpdate(response: ReplyResponse | string): Promise<TMessage | undefined>;
514
+ protected abstract writeFollowUp(response: ReplyResponse | string, opts?: SendOpts): Promise<TMessage>;
515
+ protected abstract writeEditOriginal(response: ReplyResponse | string): Promise<TMessage>;
516
+ protected abstract writeEditTarget(targetId: string, response: ReplyResponse | string): Promise<TMessage>;
517
+ protected abstract writeDeleteOriginal(): Promise<void>;
518
+ protected abstract writeDeleteTarget(targetId: string): Promise<void>;
519
+ protected abstract writeModal(data: APIModalInteractionResponseCallbackData): Promise<void>;
520
+ }
521
+ //#endregion
205
522
  //#region src/gates/Gate.d.ts
206
523
  /**
207
524
  * The transport-agnostic gate context, the scalar identity of the acting user resolvable from any
208
525
  * transport's payload. Every field except `core` is nullable, since an event or DM may carry no
209
- * guild or member. Each transport extends it with its own rich arm. For e.g., the gateway adds the djs
526
+ * guild or member. Each transport extends it with its own arm. For example, the gateway adds the djs
210
527
  * `user`/`guild`/`member` objects and the live interaction or event payload.
211
528
  *
212
529
  * @example
@@ -236,13 +553,39 @@ interface GateContextBase {
236
553
  * channel-scoped permissions the payload contains, on a gateway event the member's guild-level ones.
237
554
  */
238
555
  readonly memberPermissions: bigint | null;
556
+ /**
557
+ * The bot's permissions in the invoked channel, the wire `app_permissions` value. Null on gateway events.
558
+ */
559
+ readonly appPermissions: bigint | null;
239
560
  /**
240
561
  * The dispatched handler as `kind:route` (`slash:daily`, `button:confirm`), or null off a route (a
241
- * plain event handler, or a gate run outside a handler). `runHandlerGates` populates it from the handler's
562
+ * plain event handler, or a gate run outside a handler). `runHandlerGates` sets it from the handler's
242
563
  * metadata. `Cooldown` uses it so its window is stable across restarts and isolates.
243
564
  */
244
565
  readonly routeId: string | null;
245
566
  }
567
+ /**
568
+ * The context the guild-scoped permission gates require. It adds the base permission sets computed from
569
+ * roles alone, which only the gateway transport can provide from its cache. The http transport's context
570
+ * does not define these fields.
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * // in:'guild' returns a Gate<GuildPermissionsContext>, which fits only a gateway handler
575
+ * \@Gated(RequirePermissions([PermissionFlagsBits.BanMembers], { in: 'guild' }))
576
+ * ```
577
+ */
578
+ interface GuildPermissionsContext extends GateContextBase {
579
+ /**
580
+ * The member's base permission set, roles only, matching djs `GuildMember.permissions`. Null when the
581
+ * member is unavailable.
582
+ */
583
+ readonly memberGuildPermissions: bigint | null;
584
+ /**
585
+ * The bot's base permission set from the transport's cache. Null when the cached member is unavailable.
586
+ */
587
+ readonly appGuildPermissions: bigint | null;
588
+ }
246
589
  declare const GateBrand: unique symbol;
247
590
  /**
248
591
  * A precondition attached to a handler. It refuses by throwing a `Notice` (a reply) or a `Silence`
@@ -462,10 +805,9 @@ type GateFitsWith<Provided, ProvidedLabel extends string, TGate, ReqLabel extend
462
805
  //#endregion
463
806
  //#region src/gates/catalog/options.d.ts
464
807
  /**
465
- * The override a catalog gate accepts, a one-line `message` reword or a full `notice` replacement. Accepted by
466
- * the agnostic gates ({@link OwnerOnly}, {@link GuildOnly}, {@link DmOnly}, {@link Cooldown}) and the gateway's
467
- * `RequireRole` and `Nsfw`. The gateway's `RequirePermissions` and `RequireBotPermissions` take
468
- * `RequirePermissionsOptions` instead.
808
+ * The override a catalog gate accepts. A one-line `message` reword or a full `notice` replacement. Accepted by
809
+ * the agnostic catalog gates and, nested per refusal, by {@link RequireRole}. {@link RequirePermissions} and
810
+ * {@link RequireBotPermissions} take `RequirePermissionsOptions` instead.
469
811
  *
470
812
  * @example
471
813
  * ```ts
@@ -487,174 +829,172 @@ interface GateNoticeOptions {
487
829
  }
488
830
  declare function pickNotice(options: GateNoticeOptions | undefined, makeDefault: (message?: string) => Notice): Notice;
489
831
  //#endregion
490
- //#region src/customId/Field.d.ts
832
+ //#region src/gates/catalog/permissions.d.ts
491
833
  /**
492
- * One field in a customId shape, its wire kind plus the type it decodes to.
493
- *
494
- * @internal
834
+ * The permission flag bits a caller or the bot must hold. Pass the `PermissionFlagsBits` values from
835
+ * `discord-api-types/v10` (or `discord.js`).
495
836
  */
496
- interface CustomIdField<Decoded> {
497
- /** Which wire encoding this field uses. */
498
- readonly kind: 'snowflake' | 'uuid' | 'int' | 'bool' | 'oneOf' | 'string';
499
- /** Lower bound, for a bounded int field. */
500
- readonly min?: number;
501
- /** Upper bound, for a bounded int field. */
502
- readonly max?: number;
503
- /** The allowed values, for a oneOf field. */
504
- readonly choices?: readonly string[];
505
- /** Phantom only, carries the decoded type and is never set at runtime. */
506
- readonly decoded?: Decoded;
507
- }
837
+ type PermissionScope = readonly bigint[];
838
+ /** Permission bit to its prettified human-readable name, for the missing-permission refusal. */
839
+ declare const PermissionNames: Map<bigint, string>;
508
840
  /**
509
- * The set of fields a customId carries, keyed by name.
510
- *
511
- * @internal
841
+ * Options for {@link RequirePermissions} and {@link RequireBotPermissions}.
512
842
  */
513
- type CustomIdShape = Record<string, CustomIdField<unknown>>;
843
+ interface RequirePermissionsOptions {
844
+ /**
845
+ * The set the check reads. `'channel'` reads the effective channel set the payload carries, what
846
+ * Discord enforces in the invoked channel. `'guild'` reads the base set from roles, available on the
847
+ * gateway transport only.
848
+ *
849
+ * @defaultValue `'channel'`
850
+ */
851
+ in?: 'channel' | 'guild';
852
+ /** Reword or replace the refusal shown when the set is unavailable (outside a guild, or unresolved inside one). */
853
+ notInGuild?: GateNoticeOptions;
854
+ /** Reword or replace the refusal shown when a permission is missing. */
855
+ missing?: GateNoticeOptions;
856
+ }
514
857
  /**
515
- * The decoded result, each field name mapped to its decoded type.
516
- *
517
- * @internal
858
+ * Options for {@link RequireRole}.
518
859
  */
519
- type DecodedParams<Shape extends CustomIdShape> = { [Name in keyof Shape]: Shape[Name] extends CustomIdField<infer Decoded> ? Decoded : never };
520
- //#endregion
521
- //#region src/customId/CustomId.d.ts
522
- /** Strip the layout hash off the routeKey to recover the stable prefix the controller routes by. @internal */
523
- declare function prefixOf(wire: string): string;
860
+ interface RequireRoleOptions {
861
+ /** Reword or replace the refusal shown outside a guild. */
862
+ notInGuild?: GateNoticeOptions;
863
+ /** Reword or replace the refusal shown when the caller lacks the role. */
864
+ missingRole?: GateNoticeOptions;
865
+ }
524
866
  /**
525
- * A typed customId. The single source of truth shared by the component that mints it and the handler
526
- * that reads it. This gives you typed reads on the `.customId` field in components. Values are packed into a compact wire string rather than plain stringified tokens, so the 100-char Discord limit goes further. More string per string, basically.
867
+ * Requires the caller to hold every permission in `scope`. Refuses outside a guild first, then when a
868
+ * permission is missing. The Administrator bit passes any scope.
527
869
  *
528
- * @typeParam Prefix - The stable route prefix, e.g. 'approve'.
529
- * @typeParam Shape - The accumulated fields, filled in by the chain.
870
+ * The default `'channel'` scope reads the effective channel set the payload carries, what Discord enforces
871
+ * in the invoked channel. The `'guild'` scope reads the base set from roles and requires a
872
+ * {@link GuildPermissionsContext}, so it fits a gateway handler only.
873
+ *
874
+ * @param scope - The permission flag bits the caller must all hold.
875
+ * @param options - Pick the scope with `in`, and override each refusal with `notInGuild` or `missing`.
876
+ *
877
+ * @see the `@Gated` decorator from `seedcord`
530
878
  *
531
879
  * @example
532
880
  * ```ts
533
- * const ApproveId = new CustomId('approve')
534
- * .snowflake('userId')
535
- * .oneOf('action', ['approve', 'deny']);
881
+ * import { PermissionFlagsBits } from 'discord-api-types/v10';
536
882
  *
537
- * // Set the custom id on a button when creating it.
538
- * new ButtonBuilder().setCustomId(ApproveId.encode({ userId: '123', action: 'approve' }));
883
+ * \@Gated(RequirePermissions([PermissionFlagsBits.BanMembers]))
884
+ * \@SlashRoute('ban')
885
+ * class BanHandler extends SlashHandler<'ban'> {
886
+ * async execute() {
887
+ * // ...
888
+ * }
889
+ * }
890
+ * ```
539
891
  *
540
- * // reading in the handler: userId comes back a string
541
- * const { userId, action } = this.params; // userId: string, action: 'approve' | 'deny'
542
- * await this.event.guild?.members.fetch(userId);
892
+ * @example
893
+ * ```ts
894
+ * // the base set from roles, on a gateway handler
895
+ * RequirePermissions([PermissionFlagsBits.BanMembers], { in: 'guild' });
543
896
  * ```
544
897
  */
545
- declare class CustomId<Prefix extends string, Shape extends CustomIdShape = {}> {
546
- /** The stable route prefix, e.g. 'approve'. */
547
- readonly prefix: Prefix;
548
- /** Like a zod shape */
549
- readonly shape: Shape;
550
- /** The prefix plus a short hash of the shape, the part of the wire before the colon. */
551
- readonly routeKey: string;
552
- constructor(prefix: Prefix, shape?: Shape);
553
- private add;
554
- /**
555
- * Add a Discord ID field, decoded as a string (the `Snowflake` type from discord-api-types).
556
- *
557
- * @example
558
- * ```ts
559
- * new CustomId('ban').snowflake('userId');
560
- * ```
561
- */
562
- snowflake<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<Snowflake>>>;
563
- /**
564
- * Add a UUID field, decoded as a lowercase uuid string.
565
- *
566
- * @example
567
- * ```ts
568
- * new CustomId('ticket').uuid('ticketId');
569
- * ```
570
- */
571
- uuid<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
572
- /**
573
- * Add an integer field with no bounds, for a value up to 2^53.
574
- *
575
- * @example
576
- * ```ts
577
- * new CustomId('shop').int('amount');
578
- * ```
579
- */
580
- int<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
581
- /**
582
- * Add an integer field bounded by min and max, so it packs into fewer characters on the wire.
583
- *
584
- * @example
585
- * ```ts
586
- * new CustomId('paginate').int('page', 1, 50);
587
- * ```
588
- */
589
- int<Name extends string>(name: Name, min: number, max: number): CustomId<Prefix, Shape & Record<Name, CustomIdField<number>>>;
590
- /**
591
- * Add a boolean flag.
592
- *
593
- * @example
594
- * ```ts
595
- * new CustomId('settings').bool('silent');
596
- * ```
597
- */
598
- bool<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<boolean>>>;
599
- /**
600
- * Add a field that is one value from a fixed list, decoded as the literal union. No `as const` needed.
601
- *
602
- * @example
603
- * ```ts
604
- * new CustomId('poll').oneOf('choice', ['yes', 'no', 'abstain']);
605
- * ```
606
- */
607
- oneOf<Name extends string, const Choices extends NonEmptyTuple<string>>(name: Name, choices: Choices): CustomId<Prefix, Shape & Record<Name, CustomIdField<Choices[number]>>>;
608
- /**
609
- * Add a free short text field. Avoid it where possible, it cannot be packed so it costs the most wire space.
610
- *
611
- * @example
612
- * ```ts
613
- * new CustomId('note').str('message');
614
- * ```
615
- */
616
- str<Name extends string>(name: Name): CustomId<Prefix, Shape & Record<Name, CustomIdField<string>>>;
617
- /**
618
- * Mint a wire string from values. Throws if a value is out of its field's range or the wire is over 100 chars.
619
- *
620
- * @param values - One value per field, typed by the chain.
621
- * @returns The wire string to put on the component's customId.
622
- */
623
- encode(values: DecodedParams<Shape>): string;
624
- /**
625
- * Read a wire string back into values.
626
- *
627
- * - This will refuse with a default Notice when the shape changed since the wire was minted.
628
- * - Or refuse with a different default Notice on a corrupt or foreign wire.
629
- *
630
- * @param wire - The customId string from the interaction.
631
- * @returns The decoded values, typed by the chain.
632
- */
633
- decode(wire: string): DecodedParams<Shape>;
634
- /** True if this wire was minted from this customId's prefix, ignoring the shape hash. */
635
- owns(wire: string): boolean;
636
- }
898
+ declare function RequirePermissions(scope: PermissionScope, options: RequirePermissionsOptions & {
899
+ in: 'guild';
900
+ }): Gate<GuildPermissionsContext, 'RequirePermissions'>;
901
+ declare function RequirePermissions(scope: PermissionScope, options?: RequirePermissionsOptions & {
902
+ in?: 'channel';
903
+ }): Gate<GateContextBase, 'RequirePermissions'>;
637
904
  /**
638
- * Any customId, for places where the exact prefix and shape do not matter.
905
+ * Requires the bot to hold every permission in `scope`. Refuses outside a guild first, then when a
906
+ * permission is missing. The Administrator bit passes any scope.
639
907
  *
640
- * @internal
908
+ * The default `'channel'` scope reads the bot's effective channel set (wire `app_permissions`), what
909
+ * Discord enforces in the invoked channel. The `'guild'` scope reads the bot's base set from roles and
910
+ * requires a {@link GuildPermissionsContext}, so it fits a gateway handler only. `app_permissions` is
911
+ * absent on gateway events, the channel scope refuses there as unresolved, use `in: 'guild'` on an
912
+ * event handler.
913
+ *
914
+ * @param scope - The permission flag bits the bot must all hold.
915
+ * @param options - Pick the scope with `in`, and override each refusal with `notInGuild` or `missing`.
916
+ *
917
+ * @see the `@Gated` decorator from `seedcord`
918
+ *
919
+ * @example
920
+ * ```ts
921
+ * import { PermissionFlagsBits } from 'discord-api-types/v10';
922
+ *
923
+ * \@Gated(RequireBotPermissions([PermissionFlagsBits.ManageMessages]))
924
+ * \@SlashRoute('purge')
925
+ * class PurgeHandler extends SlashHandler<'purge'> {
926
+ * async execute() {
927
+ * // ...
928
+ * }
929
+ * }
930
+ * ```
641
931
  */
642
- type AnyCustomId = CustomId<string, CustomIdShape>;
932
+ declare function RequireBotPermissions(scope: PermissionScope, options: RequirePermissionsOptions & {
933
+ in: 'guild';
934
+ }): Gate<GuildPermissionsContext, 'RequireBotPermissions'>;
935
+ declare function RequireBotPermissions(scope: PermissionScope, options?: RequirePermissionsOptions & {
936
+ in?: 'channel';
937
+ }): Gate<GateContextBase, 'RequireBotPermissions'>;
643
938
  /**
644
- * The outcome of decoding a wire against several customIds, the matched prefix paired with its values.
939
+ * Requires the caller to hold `roleId`, read from the member's role ids. Refuses outside a guild first,
940
+ * then when the role is missing. Roles are guild-global, so there is no scope dimension.
645
941
  *
646
- * @internal
942
+ * @param roleId - The role snowflake the caller must hold.
943
+ * @param options - Override each refusal, the outside-guild one with `notInGuild` and the missing-role one with `missingRole`.
944
+ *
945
+ * @see the `@Gated` decorator from `seedcord`
946
+ *
947
+ * @example
948
+ * ```ts
949
+ * \@Gated(RequireRole('123456789012345678'))
950
+ * \@SlashRoute('vip')
951
+ * class VipHandler extends SlashHandler<'vip'> {
952
+ * async execute() {
953
+ * // ...
954
+ * }
955
+ * }
956
+ * ```
647
957
  */
648
- type DecodedRoute<Defs extends readonly AnyCustomId[]> = { [Index in keyof Defs]: Defs[Index] extends AnyCustomId ? {
649
- readonly prefix: Defs[Index]['prefix'];
650
- readonly params: DecodedParams<Defs[Index]['shape']>;
651
- } : never }[number];
958
+ declare function RequireRole(roleId: string, options?: RequireRoleOptions): Gate<GateContextBase, 'RequireRole'>;
959
+ //#endregion
960
+ //#region src/inputs/AutocompleteOptions.d.ts
961
+ type Row<Route extends keyof SlashOptionRegistry> = SlashOptionRegistry[Route];
962
+ type EntryFor<Route extends keyof SlashOptionRegistry, Name extends PropertyKey> = Route extends unknown ? Name extends keyof Row<Route> ? Row<Route>[Name] : never : never;
963
+ type AutocompletableNames<Route extends keyof SlashOptionRegistry> = Route extends unknown ? { [Name in keyof Row<Route>]: Row<Route>[Name] extends {
964
+ autocomplete: true;
965
+ } ? Name : never }[keyof Row<Route>] : never;
966
+ type ChoiceValueOf<Entry> = Entry extends {
967
+ kind: 'string';
968
+ } ? string : Entry extends {
969
+ kind: 'integer' | 'number';
970
+ } ? number : never;
971
+ /** The focused option. `value` is the raw partial Discord delivers mid-type, always a string regardless of kind. */
972
+ interface FocusedField<Route extends keyof SlashOptionRegistry> {
973
+ name: AutocompletableNames<Route>;
974
+ value: string;
975
+ }
976
+ type NamesOfKind<Route extends keyof SlashOptionRegistry, Kind extends OptionKind> = Route extends unknown ? { [Name in keyof Row<Route>]: Row<Route>[Name] extends {
977
+ kind: Kind;
978
+ } ? Name : never }[keyof Row<Route>] : never;
979
+ type ResolvedValue<Entry> = Entry extends {
980
+ choices: readonly (infer Choice)[];
981
+ } ? Choice : Entry extends {
982
+ kind: 'string';
983
+ } ? string : Entry extends {
984
+ kind: 'integer' | 'number';
985
+ } ? number : Entry extends {
986
+ kind: 'boolean';
987
+ } ? boolean : never;
988
+ type Getter<Route extends keyof SlashOptionRegistry, Kind extends OptionKind, Method extends string> = IsNever<NamesOfKind<Route, Kind>> extends true ? unknown : Record<Method, <Name extends NamesOfKind<Route, Kind>>(name: Name) => ResolvedValue<EntryFor<Route, Name>> | null>;
652
989
  /**
653
- * Find the customId whose prefix owns this wire, decode against it, and report which one matched.
990
+ * The sibling-option view for an autocomplete handler. Only the four kinds Discord resolves during
991
+ * autocomplete appear (string, integer, number, boolean), every read is `T | null` because a sibling is
992
+ * partial while the user types the focused field, and a getter appears only when a registered command has
993
+ * an option of that kind.
654
994
  *
655
- * @internal
995
+ * @typeParam Route - One or more route keys from {@link SlashOptionRegistry}.
656
996
  */
657
- declare function decodeFor<Defs extends readonly AnyCustomId[]>(defs: Defs, wire: string): DecodedRoute<Defs>;
997
+ type AutocompleteOptions<Route extends keyof SlashOptionRegistry> = Getter<Route, 'string', 'getString'> & Getter<Route, 'integer', 'getInteger'> & Getter<Route, 'number', 'getNumber'> & Getter<Route, 'boolean', 'getBoolean'>;
658
998
  //#endregion
659
- export { BuilderComponent as C, RowType as E, RegisterCommand as S, BuilderType as T, RequiredOf as _, CustomIdField as a, CoreBase as b, pickNotice as c, JoinNames as d, TwoOrMore as f, GateContextBase as g, Gate as h, prefixOf as i, GateFitsWith as l, EffectGate as m, CustomId as n, DecodedParams as o, Notice as p, decodeFor as r, GateNoticeOptions as s, AnyCustomId as t, IntersectRequired as u, defineEffectGate as v, RowComponent as w, CommandMeta as x, defineGate as y };
660
- //# sourceMappingURL=CustomId--QizzBOn.d.mts.map
999
+ export { InteractionMetadataKey as $, serializeReply as A, contextMenuRouteOf as B, GuildPermissionsContext as C, BaseReplySender as D, defineGate as E, AckTrace as F, CustomId as G, storeComponentRoute as H, CoreBase as I, CustomIdField as J, decodeFor as K, RoutableConstructor as L, ReplyMethod as M, checkAckLegality as N, ModalLike as O, sendTarget as P, GatedMetadataKey as Q, SelectMenuKind as R, GateContextBase as S, defineEffectGate as T, storeInteractionRoute as U, selectMenuRouteOf as V, AnyCustomId as W, CommandMetadataKey as X, DecodedParams as Y, EventMetadataKey as Z, JoinNames as _, FocusedField as a, CommandMeta as at, EffectGate as b, RequireBotPermissions as c, BuilderComponent as ct, RequireRole as d, RowType as dt, InteractionRouteKeys as et, RequireRoleOptions as f, OptionKind as ft, IntersectRequired as g, GateFitsWith as h, EntryFor as i, WebhookUrlMetadataKey as it, AckState as j, SerializedReply as k, RequirePermissions as l, RowComponent as lt, pickNotice as m, SlashOptionRegistry as mt, AutocompleteOptions as n, MiddlewareMetadataKey as nt, PermissionNames as o, RegisterCommand as ot, GateNoticeOptions as p, SlashOption as pt, prefixOf as q, ChoiceValueOf as r, SubscribeMetadataKey as rt, PermissionScope as s, Constructor as st, AutocompletableNames as t, InteractionRoutes as tt, RequirePermissionsOptions as u, BuilderType as ut, TwoOrMore as v, RequiredOf as w, Gate as x, Notice as y, areRoutes as z };
1000
+ //# sourceMappingURL=AutocompleteOptions-eB87SZ4a.d.mts.map